srathbone / sql-data-mods
SQL/PDO 扩展 - 扩展原始 SQL API 扩展提供的能力。
Requires
- php: ~5.5|~7.0
- genesis/behat-sql-extension: ~6.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
This package is not auto-updated.
Last update: 2024-09-15 03:08:38 UTC
README
该想法是通过将数据库中的数据操作逻辑与网络界面的交互分离,来简化框架。此扩展提供了一个框架,您可以在此框架中配置如何与数据库表交互,并提供一个非常简单的上下文类,该类利用此配置来帮助您操作数据。
版本详情
主要更新:通过扩展通道使用初始化器初始化上下文。
次要更新:引入了创建后挂钩。允许为辅助记录提供额外数据。
修补程序:无。
此包提供的工具
- DataModSQLContext - 使用此类提供的步骤定义直接使用您的数据修改。只需在 behat.yml 文件中注册即可。
- 装饰的 API BaseProvider 类 - 用于与数据模块的高级和简单集成。
- DataRetriever 类 - 以稳健的方式检索数据,并快速为您测试框架提供一个坚实的基础。
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 |
创建固定单元调用将在创建新记录之前尝试删除现有记录,因此您始终获得一个全新的副本。听起来很简单,但外键约束可能不允许这样做。在这种情况下,您可以禁用测试数据库上的外键检查(大多数情况下您不需要这样做)。
安装
composer require --dev genesis/sql-api-wrapper
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: connection: engine: mysql # mssql, pgsql, sqlite host: localhost port: 1234 dbname: mydb username: root password: root schema: myschema dbprefix: dev_ dataModMapping: # Optional "*": \QuickPack\DataMod\ # Configure path for all data mods using *. "User": \QuickPack\DataMod\User\User # Configure single data mod.
debug - 打开或关闭调试。userUniqueRef:如果提供的是字符串,则将其附加到固定单元步骤定义提供的数据的第一列。这样,每个用户都会有其自己的唯一数据,如果多个用户针对单个数据库,则可以这样做。connectionDetails:您的数据库连接详细信息。dataModMapping:您的数据修改通过命名空间指向的位置。(可选)
请注意:该扩展期望您将数据修改放置在 features/bootstrap/DataMod
文件夹中。如果您有不同的映射,您必须在 composer.json 文件中定义您的自动加载策略或手动引入文件。您可以在 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([ '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\\' ]); $scope->getEnvironment()->registerContextClass( DataModSQLContext::class, ['debug' => false] ); } }
BaseProvide 类
包装器提供了一组围绕 behat-sql-extension API 类的强大工具。提供的方法
- createFixture(array $data = [], string $uniqueColumn = null) // 重新创建一个记录以供新鲜使用。可由数据模块覆盖。
- getSingle(array $where) // 返回由映射定义的单个记录。
- getColumn(string $column, array $where) // 从数据库返回单个列值。
- getValue(string $key) // 根据映射获取键值。
- truncate() // 截断一个表。
- subSelect(string $column, array $where) // 提供了对任何查询子选择列的能力。
- rawSubSelect(string $table, string $column, array $where) // 提供了在无需数据模块的情况下对任何查询子选择列的能力。
- 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\SQLExtensionWrapper\BaseProvider; class User extends BaseProvider { /** * Returns the base table to interact with. * * @return string */ public static function getBaseTable() { // Ridiculous naming as we find with most databases. return '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 */ public static function getDataMapping() { return [ 'id' => 'user_id', 'name' => 'f_name', 'email' => 'electronic_address', 'dateOfBirth' => 'd_o_b', 'gender' => 'gender', 'status' => 'real_status', 'anythingElse' => '*', 'somethingElse' => '*', ]; } }
在PHP代码中使用DataMod
现在您可以使用上面的数据mod,或者直接在步骤定义中使用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'"); } } }
高级DataMod
您可以通过以下方式进一步扩展您的DataMod
<?php namespace QuickPack\DataMod\User; use Genesis\SQLExtensionWrapper\BaseProvider; class User extends BaseProvider { ... /** * 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. * * @param array $data The data passed in to the data mod. * * @return array */ public static function getDefaults(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 ]) } }
getDefaults()方法是特殊的,如果存在,它将被自动调用。这允许您为任何列设置默认值。一个例子可能是某种布尔标志,您不希望不断定义或可选地覆盖。另一个例子可能是正确设置外键。
构建动态URL
您可以使用BaseProvider类提供的getKeyword调用来获取数据Mod上定义的键的引用。例如
// 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') . '/' ];
只需继续使用标准的visit页面步骤定义,使用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; } }
然后从上述类扩展您的数据mod。
数据检索器类
数据检索器类使得处理测试数据集和提供足够参数的上下文变得容易。我们都知道使用数组很痛苦。为了稍微减轻痛苦,我们有以下调用
- 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\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内的单元测试。