0 关注者

Fixture

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

要定义一个 Fixture,请创建一个新的类,扩展 yii\test\Fixtureyii\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',
    ],
];

您可以为一行数据指定一个别名,以便稍后在测试中,您可以通过别名引用该行数据。在上面的示例中,这两行分别被指定为 user1user2 别名。

此外,您不需要为自动增量列指定数据。当加载 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() 方法。

使用 Fixture

如果您使用 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 将按顺序加载:UserFixtureUserProfileFixture

在为 _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 类和数据文件

默认情况下,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 加载到不同的存储中,例如:RDBMS、NoSQL 等;
  • 以不同的方式卸载 Fixture(通常是清除存储);
  • 自动生成 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 结尾。默认情况下,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

要卸载 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"

相同的命令选项(如:namespaceglobalFixtures)也可以应用于此命令。

全局配置命令

虽然命令行选项允许我们动态配置 Fixture 命令,但有时我们可能希望为所有情况配置一次命令。例如,您可以配置不同的 Fixture 路径,如下所示:

'controllerMap' => [
    'fixture' => [
        'class' => 'yii\console\controllers\FixtureController',
        'namespace' => 'myalias\some\custom\namespace',
        'globalFixtures' => [
            'some\name\space\Foo',
            'other\name\space\Bar'
        ],
    ],
]

自动生成 Fixture

Yii 还可以根据某些模板自动为您生成 Fixture。您可以使用不同语言和格式生成具有不同数据的 Fixture。此功能由 Faker 库和 yii2-faker 扩展实现。请参阅扩展 指南 获取更多文档。

总结

在上面,我们描述了如何定义和使用 Fixture。下面我们总结了运行与数据库相关的单元测试的典型工作流程:

  1. 使用 yii migrate 工具将您的测试数据库升级到最新版本;
  2. 运行测试用例:
    • 加载 Fixture:清理相关的数据库表并使用 Fixture 数据填充它们;
    • 执行实际测试;
    • 卸载 Fixture。
  3. 重复步骤 2,直到所有测试完成。

发现错别字或您认为此页面需要改进?
在 github 上编辑它 !