Fixture 是测试的重要组成部分。它们的主要目的是将环境设置为固定/已知状态,以便您的测试具有可重复性和预期的方式运行。Yii 提供了一个 Fixture 框架,允许您精确地定义 Fixture 并轻松地使用它们,无论是在使用 Codeception 运行测试时还是独立运行时。
Yii Fixture 框架中的一个关键概念是所谓的Fixture 对象。Fixture 对象表示测试环境的特定方面,并且是 yii\test\Fixture 或其子类的实例。例如,您可以使用 UserFixture
确保用户数据库表包含一组固定的数据。您可以在运行测试之前加载一个或多个 Fixture 对象,并在完成后卸载它们。
一个 Fixture 可能依赖于其他 Fixture,通过其 yii\test\Fixture::$depends 属性指定。当加载一个 Fixture 时,它所依赖的 Fixture 将在该 Fixture 之前自动加载;当卸载 Fixture 时,依赖的 Fixture 将在该 Fixture 之后卸载。
要定义一个 Fixture,请创建一个新的类,扩展 yii\test\Fixture 或 yii\test\ActiveFixture。前者最适合通用 Fixture,而后者具有专门设计用于与数据库和 ActiveRecord 协同工作的增强功能。
以下代码定义了一个关于 User
ActiveRecord 和相应用户表的 Fixture。
<?php
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserFixture extends ActiveFixture
{
public $modelClass = 'app\models\User';
}
提示:每个
ActiveFixture
都是关于为测试目的准备一个数据库表。您可以通过设置 yii\test\ActiveFixture::$tableName 属性或 yii\test\ActiveFixture::$modelClass 属性来指定该表。如果是后者,表名将取自由modelClass
指定的ActiveRecord
类。
注意:yii\test\ActiveFixture 仅适用于 SQL 数据库。对于 NoSQL 数据库,Yii 提供了以下
ActiveFixture
类
- Mongo DB:yii\mongodb\ActiveFixture
- Elasticsearch:yii\elasticsearch\ActiveFixture(自 2.0.2 版起)
ActiveFixture
Fixture 的 Fixture 数据通常在一个位于 fixturepath/data/tablename.php
的文件中提供,其中 fixturepath
代表包含 Fixture 类文件的目录,tablename
是与 Fixture 关联的表的名称。在上面的示例中,文件应为 @app/tests/fixtures/data/user.php
。数据文件应返回要插入用户表的数据行数组。例如,
<?php
return [
'user1' => [
'username' => 'lmayert',
'email' => '[email protected]',
'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
],
'user2' => [
'username' => 'napoleon69',
'email' => '[email protected]',
'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
],
];
您可以为一行数据指定一个别名,以便稍后在测试中,您可以通过别名引用该行数据。在上面的示例中,这两行分别被指定为 user1
和 user2
别名。
此外,您不需要为自动增量列指定数据。当加载 Fixture 时,Yii 会自动将实际值填充到行中。
提示:您可以通过设置 yii\test\ActiveFixture::$dataFile 属性来自定义数据文件的位置。您还可以覆盖 yii\test\ActiveFixture::getData() 来提供数据。
如前所述,一个 Fixture 可能依赖于其他 Fixture。例如,一个 UserProfileFixture
可能需要依赖于 UserFixture
,因为用户资料表包含一个指向用户表的外部键。依赖关系通过 yii\test\Fixture::$depends 属性指定,如下所示:
namespace app\tests\fixtures;
use yii\test\ActiveFixture;
class UserProfileFixture extends ActiveFixture
{
public $modelClass = 'app\models\UserProfile';
public $depends = ['app\tests\fixtures\UserFixture'];
}
依赖关系还确保 Fixture 以明确定义的顺序加载和卸载。在上面的例子中,UserFixture
将始终在 UserProfileFixture
之前加载,以确保所有外部键引用都存在,并且将在 UserProfileFixture
卸载后卸载,原因相同。
在上面,我们展示了如何定义关于数据库表的 Fixture。要定义一个与数据库无关的 Fixture(例如,关于某些文件和目录的 Fixture),您可以扩展更通用的基类 yii\test\Fixture 并覆盖 load() 和 unload() 方法。
如果您使用 Codeception 来测试您的代码,您可以使用其内置支持来加载和访问 Fixture。
如果您使用其他测试框架,您可以在您的测试用例中使用 yii\test\FixtureTrait 来实现相同目标。
下面我们将描述如何使用 Codeception 编写一个 UserProfile
单元测试类。
在您的单元测试类(扩展 \Codeception\Test\Unit
)中,要么在 _fixtures()
方法中声明您想要使用的 Fixture,要么直接使用 Actor 的 haveFixtures()
方法。例如:
namespace app\tests\unit\models;
use app\tests\fixtures\UserProfileFixture;
class UserProfileTest extends \Codeception\Test\Unit
{
public function _fixtures()
{
return [
'profiles' => [
'class' => UserProfileFixture::class,
// fixture data located in tests/_data/user.php
'dataFile' => codecept_data_dir() . 'user.php'
],
];
}
// ...test methods...
}
在 _fixtures()
方法中列出的 Fixture 将在测试执行之前自动加载。正如我们之前描述的那样,当一个 Fixture 被加载时,所有其依赖的 Fixture 将首先被自动加载。在上面的例子中,因为 UserProfileFixture
依赖于 UserFixture
,所以在运行测试类中的任何测试方法时,两个 Fixture 将按顺序加载:UserFixture
和 UserProfileFixture
。
在为 _fixtures()
和 haveFixtures()
指定 Fixture 时,您可以使用类名或配置数组来引用 Fixture。配置数组允许您在加载 Fixture 时自定义其属性。
您还可以为 Fixture 指定别名。在上面的例子中,UserProfileFixture
被指定为别名 profiles
。然后,在测试方法中,您可以使用其别名在 grabFixture()
方法中访问 Fixture 对象。例如:
$profile = $I->grabFixture('profiles');
将返回 UserProfileFixture
对象。
因为 UserProfileFixture
扩展自 ActiveFixture
,所以您可以进一步使用以下语法来访问 Fixture 提供的数据:
// returns the UserProfile model corresponding to the data row aliased as 'user1'
$profile = $I->grabFixture('profiles', 'user1');
// traverse data in the fixture
foreach ($I->grabFixture('profiles') as $profile) ...
默认情况下,Fixture 类会在 data
文件夹下查找相应的数据文件,该文件夹是包含 Fixture 类文件的文件夹的子文件夹。在处理简单项目时,您可以遵循此约定。对于大型项目,您可能经常需要为不同的测试切换同一个 Fixture 类的不同数据文件。因此,我们建议您以类似于类命名空间的分层方式组织数据文件。例如:
# under folder tests\unit\fixtures
data\
components\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
models\
fixture_data_file1.php
fixture_data_file2.php
...
fixture_data_fileN.php
# and so on
这样,您就可以避免测试之间 Fixture 数据文件的冲突,并根据需要使用它们。
注意:在上面的例子中,Fixture 文件的命名仅用于示例目的。在实际应用中,您应该根据您的 Fixture 类扩展自哪个类来命名它们。例如,如果您扩展自 yii\test\ActiveFixture 用于数据库 Fixture,则应使用数据库表名作为 Fixture 数据文件名;如果您扩展自 yii\mongodb\ActiveFixture 用于 MongoDB Fixture,则应使用集合名作为文件名。
类似的分层结构可用于组织 Fixture 类文件。您可以使用 fixtures
作为根目录而不是 data
,以避免与数据文件冲突。
yii fixture
管理 Fixture ¶Yii 通过 yii fixture
命令行工具支持 Fixture。此工具支持:
假设我们有要加载的 Fixture 数据:
#users.php file under fixtures data path, by default @tests\unit\fixtures\data
return [
[
'name' => 'Chase',
'login' => 'lmayert',
'email' => '[email protected]',
'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
],
[
'name' => 'Celestine',
'login' => 'napoleon69',
'email' => '[email protected]',
'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
],
];
如果我们使用将数据加载到数据库的 Fixture,则这些行将应用于 users
表。如果我们使用 NoSQL Fixture,例如 mongodb
Fixture,则此数据将应用于 users
MongoDB 集合。要了解有关实现各种加载策略等的更多信息,请参阅官方 文档。上面的 Fixture 示例是由 yii2-faker
扩展自动生成的,请在这些 部分 中了解更多信息。Fixture 类名不应为复数。
Fixture 类应以 Fixture
结尾。默认情况下,Fixture 将在 tests\unit\fixtures
命名空间下搜索,您可以使用配置或命令选项更改此行为。您可以通过在 Fixture 名称前指定 -
来排除某些 Fixture 的加载或卸载,例如 -User
。
要加载 Fixture,请运行以下命令:
注意:在加载数据之前,会执行卸载序列。通常,这会导致清除先前 Fixture 执行插入的所有现有数据。
yii fixture/load <fixture_name>
必需的 fixture_name
参数指定将加载其数据的一个 Fixture 名称。您可以一次加载多个 Fixture。以下是此命令的正确格式:
// load `User` fixture
yii fixture/load User
// same as above, because default action of "fixture" command is "load"
yii fixture User
// load several fixtures
yii fixture "User, UserProfile"
// load all fixtures
yii fixture/load "*"
// same as above
yii fixture "*"
// load all fixtures except ones
yii fixture "*, -DoNotLoadThisOne"
// load fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures.
yii fixture User --namespace='alias\my\custom\namespace'
// load global fixture `some\name\space\CustomFixture` before other fixtures will be loaded.
// By default this option is set to `InitDbFixture` to disable/enable integrity checks. You can specify several
// global fixtures separated by comma.
yii fixture User --globalFixtures='some\name\space\Custom'
要卸载 Fixture,请运行以下命令:
// unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture).
yii fixture/unload User
// Unload several fixtures
yii fixture/unload "User, UserProfile"
// unload all fixtures
yii fixture/unload "*"
// unload all fixtures except ones
yii fixture/unload "*, -DoNotUnloadThisOne"
相同的命令选项(如:namespace
、globalFixtures
)也可以应用于此命令。
虽然命令行选项允许我们动态配置 Fixture 命令,但有时我们可能希望为所有情况配置一次命令。例如,您可以配置不同的 Fixture 路径,如下所示:
'controllerMap' => [
'fixture' => [
'class' => 'yii\console\controllers\FixtureController',
'namespace' => 'myalias\some\custom\namespace',
'globalFixtures' => [
'some\name\space\Foo',
'other\name\space\Bar'
],
],
]
Yii 还可以根据某些模板自动为您生成 Fixture。您可以使用不同语言和格式生成具有不同数据的 Fixture。此功能由 Faker 库和 yii2-faker
扩展实现。请参阅扩展 指南 获取更多文档。
在上面,我们描述了如何定义和使用 Fixture。下面我们总结了运行与数据库相关的单元测试的典型工作流程:
yii migrate
工具将您的测试数据库升级到最新版本;发现错别字或您认为此页面需要改进?
在 github 上编辑它 !
为了发表评论,请 注册 或 登录。