genesis / sql-data-mods
SQL/PDO 扩展 - 扩展了原始 SQL API 扩展的功能。
Requires
- php: ~5.5|~7.0
- genesis/behat-sql-extension: ~8.4
Requires (Dev)
- behat/behat: ^3.0
- behat/mink-extension: ^2.3
- behat/mink-goutte-driver: ^1.2
- ciaranmcnulty/behat-localwebserverextension: ^1.1
- genesis/behat-fail-aid: ^1.3
- phpunit/phpunit: ~4.8
Suggests
- genesis/behat-fail-aid: Give your failures clarity and save time.
- genesis/behat-stats-logger: Find exactly where your behat test suite is impacting performance.
- genesis/db-backup-restore: Quickly backup and restore your database.
- genesis/test-routing: Simplistic routing that can extend main app routing for testing purposes.
- dev-master
- 4.20.0
- 4.19.1
- 4.19.0
- 4.18.0
- 4.17.0
- 4.16.0
- 4.15.1
- 4.15.0
- 4.14.0
- 4.13.0
- 4.12.0
- 4.11.0
- 4.10.2
- 4.10.1
- 4.10.0
- 4.9.1
- 4.9.0
- 4.8.0
- 4.7.0
- 4.6.1
- 4.6.0
- 4.5.0
- 4.3.0
- 4.2.2
- 4.2.1
- 4.2.0
- 4.1.0
- 4.0.1
- 4.0.0
- 3.4.1
- 3.4.0
- 3.3.0
- 3.2.0
- 3.1.0
- 3.0.0
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.3
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.0
- 2.1.0
- 2.0.0
- 1.6.1
- 1.6.0
- 1.5.0
- 1.4.0
- 1.3.1
- 1.3.0
- 1.2.0
- 1.1.0
- 1.0.0
- dev-dependabot/composer/guzzlehttp/psr7-1.9.1
- dev-dependabot/composer/guzzlehttp/guzzle-6.5.8
This package is not auto-updated.
Last update: 2024-09-17 13:43:42 UTC
README
需要固定数据但种子数据造成头痛?即时创建固定种子数据,在场景中保持可见,自动生成数据并处理复杂的数据关系。
在功能文件夹中找到示例用法。
发布详情
主要:允许定义多个数据源。
次要
- 允许从步骤定义中指定 uniqueColumn,而不是假设第一个列。
- 通过 dataMod::count() 调用获取计数,现在截断调用是公开的。
- 将基于域的默认值注入到数据模块中。
- 如果为套件启用 FailureContext,则失败辅助集成。
- 数据模块周围使用的字符串值的常量。
- 简化特性以提供声明的替代语法。
补丁
- 修复 count 步骤定义以不创建额外记录。
- 修订了额外的固定步骤定义的英文。
此包提供的工具
- 定义多个数据源,例如 mssql、mysql 等。
- DataModSQLContext - 使用此类提供的步骤定义直接使用您的数据模块。只需在 behat.yml 文件中注册即可。
- 装饰 API BaseProvider 类 - 用于与数据模块的高级和简单集成。
- DataRetriever 类 - 以稳健的方式检索数据,并快速为您的测试框架奠定坚实基础。
- CLI 命令以方便调试。
- 自动生成带或不带数据库支持的数据模块文件。
CLI 命令
仅调试 SQL
- 要查看由扩展执行的 SQL 语句,请使用
--debug-sql
标志。
调试所有
- 要查看所有活动以及执行的语句,请使用
--debug-sql-all
标志。
生成数据模块
- 要生成简单的基数据模块,请使用
--dm-generate <以逗号分隔的表>
标志。 - 要生成带有数据库支持,请使用
--dm-generate=<以逗号分隔的表> --dm-connection=<连接名称>
标志。 - 您还可以从帮助菜单中探索
--dm-path
和--dm-namespace
标志。
DataModSQLContext
# Insert single entry for a datamod. Given I have a "User" fixture # OR with specific data Given I have a "User" fixture with the following data set: | name | Wahab Qureshi | | email | its.inevitable@hotmail.com | # Insert multiple entries for a datamod. Given I have multiple "User" fixtures with the following data sets: | name | email | | Wahab Qureshi | its.inevitable@hotmail.com | | Sabhat Qureshi | next-gen-coder@hotmail.com | | Jawad Qureshi | to-be-coder@hotmail.com | | Another name | [Users.Email\|Name:Another] |
列表中最后一个值使用外部引用来获取要插入的值。您可以通过阅读 behat-sql-extension 扩展中的“引用外表值”主题来了解更多信息。在这个调用中,我们需要转义管道字符,以免在使用表节点时出现语法错误。
createFixture 调用将尝试在创建另一个之前删除现有的记录,因此您始终会得到一个全新的副本。听起来很简单,但外键约束可能不允许这样做。在这种情况下,您可以在测试数据库上禁用外键检查(大多数情况下您不需要这样做)。
安装
composer require --dev genesis/sql-data-mods
behat.yml 文件中的示例配置
default: suites: default: contexts: - Genesis\SQLExtensionWrapper\DataModSQLContext: debug: false # 1 for all debug, 2 for only SQL queries. userUniqueRef: aq # Optional extensions: Genesis\SQLExtensionWrapper\Extension: connections: mysql: engine: mysql # mssql, pgsql, sqlite... host: '%MYSQL_HOST%' # Resolve environment variable port: 3306 dbname: mydb_mysql username: root password: root schema: myschema prefix: dev_ mssql: engine: mssql # mssql, pgsql, sqlite... host: '%MSSQL_HOST%' # Resolve environment variable port: 1433 dbname: mydb_mssql username: root password: root schema: myschema prefix: dev_ dataModMapping: # Optional "*": \QuickPack\DataMod\ # Configure path for all data mods using *. "User": \QuickPack\DataMod\User\User # Configure single data mod. domainModMapping: # Optional "*": \QuickPack\DomainMod\ "User": \QuickPack\DomainMod\User\User FailAid: # If behat-fail-aid is enabled. output: enabled: true select: true insert: true update: true delete: true
debug - 打开或关闭调试。userUniqueRef: 如果它是字符串,则将其附加到提供给固定步骤定义的数据的第一个列上。这样,每个用户都有其自己的唯一数据,如果多个用户针对单个数据库。connections: 您的数据库连接详细信息。dataModMapping: 对您的数据模块的自动加载命名空间引用。(可选)domainModMapping: 您的域模块的自动加载命名空间引用。(可选)
请注意:此扩展程序期望您的dataMods位于features/bootstrap/DataMod
文件夹中。如果您有不同的映射方式,您必须在composer.json文件中定义您的autoload策略,或者手动require文件。您可以在PHP中设置映射,如下所示
您也可以通过PHP注册上下文文件。
use Behat\Testwork\Hook\Scope\BeforeSuiteScope; use Genesis\SQLExtensionWrapper\DataModSQLContext; use Genesis\SQLExtensionWrapper\BaseProvider; class FeatureContext { /** * @BeforeSuite */ public static function loadDataModSQLContext(BeforeSuiteScope $scope) { BaseProvider::setCredentials([ 'mssql' => [ 'engine' => 'dblib', 'name' => 'databaseName', 'schema' => 'dbo', 'prefix' => 'dev_', 'host' => 'myhost', 'port' => '1433', 'username' => 'myUsername', 'password' => 'myPassword' ] ]); // Default path is \\DataMod\\ which points to features/DataMod/, override this way. DataModSQLContext::setDataModMapping([ '*' => '\\Custom\\DataMod\\' ]); DataModSQLContext::setDomainModMapping([ '*' => '\\Custom\\DomainMod\\' ]); $scope->getEnvironment()->registerContextClass( DataModSQLContext::class, ['debug' => false] ); } }
BaseProvide类
该包装器提供了围绕behat-sql-extension API类的强大工具。提供的方法
- createFixture(array $data = [], string $uniqueColumn = null) // 为新鲜使用重新创建一条记录。可由data mod覆盖。
- getSingle(array $where) // 返回由映射定义的单条记录。
- getColumn(string $column, array $where) // 从数据库返回单个列值。
- getValue(string $key) // 根据映射获取键值。
- truncate() // 截断表。
- subSelect(string $column, array $where) // 为任何查询提供子查询列的能力。
- rawSubSelect(string $table, string $column, array $where) // 为任何查询提供子查询列的能力,无需data mod。
- saveSession(string $primaryKey) // 保存当前会话以供以后重用。
- restoreSession() // 恢复由saveSession保存的会话。
- getRequiredData(array $data, string $key, boolean $format) // 扩展:从数组中提取值。
- getOptionalData(array $data, string $key, mixed $default = null, boolean $format = false) // 扩展:从数组中获取可选值,否则提供默认值。
- getFieldMapping(string $key) // 扩展:获取getDataMapping方法中提供的字段映射。
- getKeyword(string $key) // 获取映射键的键词。
注意:包装器提供的所有方法都是静态的,因为它们具有全局状态 - 我们不需要实例化此包装器。
示例用法
创建一个DataMod用于在您的上下文文件中使用。这就像从您的dataMods扩展BaseProvider类一样简单。
# User.php <?php namespace QuickPack\DataMod\User; use Genesis\DataMods\Traits\SimplifiedDeclarations; use Genesis\SQLExtensionWrapper\Contract\DataModInterface; use Genesis\SQLExtensionWrapper\BaseProvider; class User extends BaseProvider { // Provides an alternate syntax for declarations. use SimplifiedDeclarations; /** * Returns the base table to interact with. */ private static $baseTable = 'MySuperApplication.MyUsersNew'; /** * Returns the data mapping for the base table. This is the data that is allowed to be passed in * to the data mod. <input> => <mapping> * * Note any column mapped to '*' is excluded from the queries and only is a part of the data passed around. * * @return array */ private static $dataMapping = [ 'id' => 'user_id', 'name' => 'f_name', 'email' => 'electronic_address', 'dateOfBirth' => 'd_o_b', 'gender' => 'gender', 'status' => 'real_status', 'anythingElse' => DataModInterface::NOT_MAPPED, 'somethingElse' => DataModInterface::NOT_MAPPED, ]; }
在PHP代码中使用DataMods
现在您可以使用上面提到的方式或直接在步骤定义中使用PHP代码。在上下文文件中使用UserDataMod。
# FeatureContext.php <?php use Exception; use QuickPack\DataMod\User\User; /** * Ideally you would want to separate the data step definitions from interactive/assertive step definitions. * This is for demonstration only. */ class FeatureContext { /** * @Given I have a User * * Use the API to create a fixture user. */ public function createUser() { // This will create a fixture user. // The name will be set to 'Wahab Qureshi'. The rest of the fields if required by the database will be autofilled // with fixture data, if they are nullable, null will be stored. // If the record exists already, it will be deleted based on the 'name' key provided. User::createFixture([ 'name' => 'Wahab Qureshi' ], 'name'); } /** * @Given I have (number) User(s) * * Use the API to create random 10 users. */ public function create10Users($count) { // Save this user's session. User::saveSession('id'); // Create 10 random users. for($i = 0; $i < 10; $i++) { // Store the ids created for these users maybe? $this->userIds[] = User::createFixture(); } // Restore session of the user we created above. User::restoreSession(); } /** * @Given I should see a User * * Use the API to retrieve the user created. */ public function assertUserOnPage() { // Assumptions - we ran the following before running this command: // Given I have a User // And I have 10 Users // Retrieve data created, this will reference the user created by 'Given I have a User' as the session was preserved. $id = User::getValue('id'); $name = User::getValue('name'); $dateOfBirth = User::getValue('dateOfBirth'); $gender = User::getValue('gender'); // Assert that data is on the page. $this->assertSession()->assertTextOnPage($id); $this->assertSession()->assertTextOnPage($name); $this->assertSession()->assertTextOnPage($dateOfBirth); $this->assertSession()->assertTextOnPage($gender); } /** * @Given I should see (number) User(s) in the list * * Consumption of the users created above. For illustration purposes only. */ public function assertUserOnPage($number) { $usersList = $this->getSession()->getPage()->find('css', '#usersListContainer li'); $actualCount = count($usersList); if ($number !== $actualCount) { throw new Exception("Expected to have '$number' users, got '$actualCount'"); } } }
高级DataModding
您可以通过其他方法进一步扩展您的DataMod,如下所示
<?php namespace QuickPack\DataMod\User; use Genesis\SQLExtensionWrapper\BaseProvider; class User extends BaseProvider { ... /** * If you've defined multiple connections, you can specify which connection to use for each of your * data mods. * * @return string */ public static function getConnectionName() { return 'mssql'; } /** * Special Method: Use this method to create auxiliary data off the initial create. This is suitable * for creating data where the tables are fragmented. * * @param int $id The id of the created record. * @param array $data The data that was originally passed to create. * * @return void */ public static function postCreateHook($id, array $data) { Meta::createFixture(...); } /** * Special Method: This method if implemented is merged with the data provided. * Any data provided overwrites the default data. * This is a good opportunity to set foreign key values using the subSelect call. * * Similar methods are available: * - getSelectDefaults() * - getUpdateDefaults() * - getDeleteDefaults() * * @param array $data The data passed in to the data mod. * * @return array */ public static function getInsertDefaults(array $data) { return [ 'dateOfBirth' => '1989-05-10', 'gender' => Gender::subSelect('type', ['id' => 1]) ]; } /** * Method uses subSelect to intelligently select the Id of the status and updates the user record. * This is a common case where you want your feature files to be descriptive and won't just pass in id's, use * descriptive names instead and infer values in the lower layers. * * @param string $status The status name (enabled/disabled). * @param int $userId The user to update. * * @return void */ public static function updateStatusById($status, $userId) { self::update(self::getBaseTable(), [ 'status' => BaseProvider::rawSubSelect('Status', 'id', ['name' => $status]) ], [ 'id' => $userId ]) } }
getInsertDefaults()方法特别,如果存在,将自动调用。它允许您为任何列设置默认值。一个例子可能是一个布尔标志,您不希望反复定义或可选项地覆盖。另一个例子可能是设置外键或对某些操作提出要求。您为每个操作类型(即选择、更新、插入和删除)都有此方法。
组合Data mods(领域Mod)
有时我们的数据分散在几个表中,但我们不希望这种分散渗透到我们的测试文件中。为了方便这种情况,我们有领域mods。
<?php namespace App\Tests\Behaviour\DomainMod; use App\Tests\Behaviour\DataMod; use Genesis\SQLExtensionWrapper\Contract\DomainModInterface; class User implements DomainModInterface { public static function getDataMods() { return [ DataMod\User::class, DataMod\UserExt::class, ]; } }
支持此功能的步骤定义如下
Scenario: ... Given I have a "Ship" domain fixture Given I have an additional "Ship" domain fixture Given I have a "Ship" domain fixture with the following data set: | name | ABC |
您可以通过领域mods注入data mod默认值,这些默认值优先于data mod默认值,但低于步骤定义提供的值。
<?php namespace App\Tests\Behaviour\DomainMod; use App\Tests\Behaviour\DataMod; use Genesis\SQLExtensionWrapper\Contract\DomainModInterface; class User implements DomainModInterface { public static function getInsertDefaults() { return [ DataMod\User::class => [ 'age' => 31 ], DataMod\UserExt::class => [ 'name' => 'Jack' ], ]; } public static function getDataMods() { return [ DataMod\User::class, DataMod\UserExt::class, ]; } }
构建动态URL
您可以使用BaseProvider类提供的getKeyword调用来获取在dataMod上定义的键的引用。例如
// We want to create a user and have its id placed in the URL such as '/user/<id>/', so we can visit the page. // Normally with the above data mod configuration and behat-sql-extension you need to do the following: $routes = [ 'user' => '/user/{MySuperApplication.MyUsersNew.user_id}/' ]; // Having a data mod gives you a way to abstract any table information // by just referencing the data mod itself. The above can be re-written as: $routes = [ 'user' => '/user/' . User::getKeyword('id') . '/' ];
继续使用标准的访问页面步骤定义,使用genesis/test-routing
/** * @Given I am on the :arg1 page * @Given I visit the :arg1 page */ public function iAmOnThePage($arg1) { $url = Routing::getRoute($arg1, function ($url) { return BaseProvider::getApi()->get('keyStore')->parseKeywordsInString($url); }); $this->getMink()->getSession()->visit($url); }
高级集成
要使用Api的不同版本,您必须充分利用多态性。在您的项目中扩展BaseProvider并实现抽象方法getAPI()。此方法需要返回一个实现了Genesis\SQLExtension\Context\Interfaces\APIInterface的对象。
# BaseDataMod.php <?php use Genesis\SQLExtensionWrapper\BaseProvider; use Genesis\SQLExtension\Context; /** * Serves as a base class for your own project, makes refactoring easier if you decide to inject your own version of * the API. */ abstract class BaseDataMod extends BaseProvider { /** * @var array The connection details the API expects. */ public static $connectionDetails; /** * @var Context\Interfaces\APIInterface */ private static $sqlApi; /** * @return Context\Interfaces\APIInterface */ public static function getAPI() { if (! self::$sqlApi) { self::$sqlApi = new Context\API( new Context\DBManager(Context\DatabaseProviders\Factory(), self::$connectionDetails), new Context\SQLBuilder(), new Context\LocalKeyStore(), new Context\SQLHistory() ); } return self::$sqlApi; } }
然后从上面的类扩展您的data mods。
数据检索类
数据检索类简化了与测试数据集的工作,并提供足够的上下文来传递参数。我们都知道使用数组很痛苦。为了稍微减轻痛苦,我们提供了以下调用
- getRequiredData($searchArray, $key) // 隐式数据转换,当数据未提供时抛出异常。
- getOptionalData($searchArray, $key, $defaultValue, $format) // 显式数据转换。
为了简化与TableNodes的工作,以下是一些调用
- loopMultiTable($tableNode, callbackFunction)
- loopSingleTable($tableNode, callbackFunction)
- loopPageFieldsTable($tableNode, callbackFunction)
- transformTableNodeToSingleDataSet($tableNode)
- transformTableNodeToMultiDataSets($tableNode)
内置大多数常见数据类型的数据转换
- getFormattedValue($value, $fieldName) // 遵循以下规则
| Fieldname | Conversion | More info | | %Date% | Format to Y-m-d H:i:s | This is particularly useful with dynamic dates such as yesterday | | %Amount% | To pence, Multiply by 100 | User friendly input such as 100 amount equals 10000 pence |
使用桥接器
您还可以在框架数据模块和包装器之间设置桥接器。您的桥接器必须实现Genesis\SQLExtensionWrapper\Contract\BridgeInterface才能工作。您可以如下注册您的桥接器
class FeatureContext { public function __construct() { $bridgeObject = new DoctrineBridge(); DataModSQLContext::registerBridge($bridgeObject); } }
开发
要开始开发此项目
部署器 https://github.com/forceedge01/deployer
在项目根目录下运行
dep use
然后运行
dep project:dev
上述操作将初始化并下载vagrant box作为子模块,启动box,并在内部执行composer install。
运行单元测试
dep project:test
这将运行vagrant box内的单元测试。