測(cè)試夾具(Fixtures)

2018-02-24 15:40 更新

Fixtures

Fixtures 是測(cè)試中非常重要的一部分。他們的主要目的是建立一個(gè)固定/已知的環(huán)境狀態(tài)以確保 測(cè)試可重復(fù)并且按照預(yù)期方式運(yùn)行。Yii 提供一個(gè)簡單可用的 Fixure 框架 允許你精確的定義你的 Fixtures 。

Yii 的 Fixture 框架的核心概念稱之為?fixture object?。一個(gè) Fixture object 代表 一個(gè)測(cè)試環(huán)境的某個(gè)特定方面,它是 yii\test\Fixture 或者其子類的實(shí)例。 比如,你可以使用?UserFixture?來確保用戶DB表包含固定的數(shù)據(jù)。 你在運(yùn)行一個(gè)測(cè)試之前加載一個(gè)或者多個(gè) fixture object,并在結(jié)束后卸載他們。

一個(gè) Fixture 可能依賴于其他的 Fixtures ,通過它的 yii\test\Fixture::depends 來指定。 當(dāng)一個(gè) Fixture 被加載前,它依賴的 Fixture 會(huì)被自動(dòng)的加載;同樣,當(dāng)某個(gè) Fixture 被卸載后, 它依賴的 Fixtures 也會(huì)被自動(dòng)的卸載。

定義一個(gè) Fixture

為了定義一個(gè) Fixture,你需要?jiǎng)?chuàng)建一個(gè)新的 class 繼承自 yii\test\Fixture 或者 yii\test\ActiveFixture 。前一個(gè)類對(duì)于一般用途的 Fixture 比較適合, 而后者則有一些增強(qiáng)功能專用于與數(shù)據(jù)庫和 ActiveRecord 一起協(xié)作。

下面的代碼定義一個(gè)關(guān)于?User?ActiveRecord 和相關(guān)的用戶表的 Fixture:

<?php
namespace app\tests\fixtures;

use yii\test\ActiveFixture;

class UserFixture extends ActiveFixture
{
    public $modelClass = 'app\models\User';
}

技巧:每個(gè)?ActiveFixture?都會(huì)準(zhǔn)備一個(gè) DB 表用來測(cè)試。你可以通過設(shè)置 yii\test\ActiveFixture::tableName 或 yii\test\ActiveFixture::modelClass 屬性來指定具體的表。如果是后者, 表名會(huì)從?modleClass?指定的?ActiveRecord?中獲取。

注意:yii\test\ActiveFixture 僅限于 SQL 數(shù)據(jù)庫,對(duì)于 NoSQL 數(shù)據(jù)庫, Yii 提供以下?ActiveFixture?類:

  • Mongo DB: yii\mongodb\ActiveFixture
  • Elasticsearch: yii\elasticsearch\ActiveFixture (since version 2.0.2)

提供給?ActiveFixture?的 fixture data 通常放在一個(gè)路徑為?FixturePath/data/TableName.php?的文件中, 其中?FixturePath代表 Fixture 類所在的路徑,?TableName?則是和 Fixture 關(guān)聯(lián)的表。在以上的例子中, 這個(gè)文件應(yīng)該是@app/tests/fixtures/data/user.php?。 data 文件返回一個(gè)包含要被插入用戶表中的數(shù)據(jù)文件,比如:

<?php
return [
    'user1' => [
        'username' => 'lmayert',
        'email' => 'strosin.vernice@jerde.com',
        'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
        'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
    ],
    'user2' => [
        'username' => 'napoleon69',
        'email' => 'aileen.barton@heaneyschumm.com',
        'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
        'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
    ],
];

你可以給某行指定一個(gè) alias 別名,這樣在你以后的測(cè)試中,你可以通過別名來確定某行。 在上面的例子中,這兩行指定別名為user1?和?user2

同樣,你不需要特別的為自動(dòng)增長(auto-incremental)的列指定數(shù)據(jù), Yii 將會(huì)在 Fixture 被加載時(shí)自動(dòng)的填充正確的列值到這些行中。

技巧:你可以通過設(shè)置 yii\test\ActiveFixture::dataFile 屬性來自定義 data 文件的位置。 同樣,你可以重寫 yii\test\ActiveFixture::getData() 來提供數(shù)據(jù)。

如之前所述,一個(gè) Fixture 可以依賴于其他的 Fixture 。比如一個(gè)?UserProfileFixture?可能需要依賴于?UserFixture, 因?yàn)?user profile 表包括一個(gè)指向 user 表的外鍵。那么, 這個(gè)依賴關(guān)系可以通過 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'];
}

依賴關(guān)系確保所有的 Fixtures 能夠以正常的順序被加載和卸載。在以上的例子中, 為確保外鍵存在,?UserFixture?會(huì)在UserProfileFixture?之前加載, 同樣,也會(huì)在其卸載后同步卸載。

在上面,我們展示了如何定義一個(gè) DB 表的 Fixture 。為了定義一個(gè)與 DB 無關(guān)的 Fixture (比如一個(gè)fixture關(guān)于文件和路徑的),你可以從一個(gè)更通用的基類 yii\test\Fixture 繼承, 并重寫 yii\test\Fixture::load() 和 yii\test\Fixture::unload() 方法。

使用 Fixtures

如果你使用?CodeCeption?作為你的 Yii 代碼測(cè)試框架, 你需要考慮使用?yii2-codeception?擴(kuò)展,這個(gè)擴(kuò)展包含內(nèi)置的機(jī)制來支持加載和訪問 Fixtures。 如果你使用其他的測(cè)試框架,為了達(dá)到加載和訪問 Fixture 的目的, 你需要在你的測(cè)試用例中使用 yii\test\FixtureTrait。

在以下示例中,我們會(huì)展示如何通過?yii2-codeception?寫一個(gè)?UserProfile?單元來測(cè)試某個(gè) class。

在一個(gè)繼承自 yii\codeception\DbTestCase 或者 yii\codeception\TestCase 的單元測(cè)試類中, 你可以在 yii\test\FixtureTrait::fixtures() 方法中聲明你希望使用哪個(gè) Fixture。比如:

namespace app\tests\unit\models;

use yii\codeception\DbTestCase;
use app\tests\fixtures\UserProfileFixture;

class UserProfileTest extends DbTestCase
{
    public function fixtures()
    {
        return [
            'profiles' => UserProfileFixture::className(),
        ];
    }

    // ...test methods...
}

在測(cè)試用例的每個(gè)測(cè)試方法運(yùn)行前?fixtures()?方法列表返回的 Fixture 會(huì)被自動(dòng)的加載, 并在結(jié)束后自動(dòng)的卸載。同樣,如前面所述,當(dāng)一個(gè) Fixture 被加載之前, 所有它依賴的 Fixture 也會(huì)被自動(dòng)的加載。在上面的例子中,因?yàn)?UserProfileFixture?依賴于UserFixtrue,當(dāng)運(yùn)行測(cè)試類中的任意測(cè)試方法時(shí), 兩個(gè) Fixture,UserFixture?和?UserProfileFixture?會(huì)被依序加載。

當(dāng)我們通過?fixtures()?方法指定需要加載的 Fixture 時(shí),我們既可以使用一個(gè)類名, 也可以使用一個(gè)配置數(shù)組。配置數(shù)組可以讓你自定義加載的 fixture 的屬性名。

你同樣可以給一個(gè) Fixture 指定一個(gè)別名(alias),在上面的例子中,UserProfileFixture?的別名為?profiles?。 在測(cè)試方法中,你可以通過別名來訪問一個(gè) Fixture 對(duì)象。比如,?$this->profiles?將會(huì)返回?UserProfileFixture?對(duì)象。

因?yàn)?UserProfileFixture?從?ActiveFixture?處繼承, 在后面,你可以通過如下的語法形式來訪問 Fixture 提供的數(shù)據(jù):

// returns the data row aliased as 'user1'
$row = $this->profiles['user1'];
// returns the UserProfile model corresponding to the data row aliased as 'user1'
$profile = $this->profiles('user1');
// traverse every data row in the fixture
foreach ($this->profiles as $row) ...

注:?$this->profiles?的類型仍為?UserProfileFixture, 以上的例子是通過 PHP 魔術(shù)方法來實(shí)現(xiàn)的。

定義和使用全局 Fixtures

以上示例的 Fixture 主要用于單個(gè)的測(cè)試用例, 在許多情況下,你需要使用一些全局的 Fixture 來讓所有或者大量的測(cè)試用例使用。 yii\test\InitDbFixture 就是這樣的一個(gè)例子,它主要做兩個(gè)事情:

  • 它通過執(zhí)行在?@app/tests/fixtures/initdb.php?里的腳本來做一些通用的初始化任務(wù);
  • 在加載其他的 DB Fixture 之前禁用數(shù)據(jù)庫完整性校驗(yàn),同時(shí)在其他的 DB Fixture 卸載后啟用數(shù)據(jù)庫完整性校驗(yàn)。

全局的 Fixture 和非全局的用法類似,唯一的區(qū)別是你在 yii\codeception\TestCase::globalFixtures() 中聲明它, 而非?fixtures()?方法。當(dāng)一個(gè)測(cè)試用例加載 Fixture 時(shí), 它首先加載全局的 Fixture,然后才是非全局的。

yii\codeception\DbTestCase 默認(rèn)已經(jīng)在其?globalFixtures()?方法中聲明了?InitDbFixture, 這意味著如果你想要在每個(gè)測(cè)試之前執(zhí)行一些初始化工作,你只需要調(diào)整?@app/tests/fixtures/initdb.php?中的代碼即可。 你只需要簡單的集中精力中開發(fā)單個(gè)的測(cè)試用例和相關(guān)的 Fixture。

組織 Fixture 類和相關(guān)的數(shù)據(jù)文件

默認(rèn)情況下,F(xiàn)ixture 類會(huì)在其所在的目錄下面的?data?子目錄尋找相關(guān)的數(shù)據(jù)文件。 在一些簡單的項(xiàng)目中,你可以遵循此范例。對(duì)于一些大項(xiàng)目, 您可能經(jīng)常為同一個(gè)?Fixture?類的不同測(cè)試而切換不同的數(shù)據(jù)文件。 在這種情況下,我們推薦你按照一種類似于命名空間 的方式有層次地組織你的數(shù)據(jù)文件,比如:

# 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

這樣,你就可以避免在測(cè)試用例之間產(chǎn)生沖突,并根據(jù)你的需要使用它們。

注意:在以上的例子中,F(xiàn)ixture 文件只用于示例目的。在真實(shí)的環(huán)境下,你需要根據(jù)你的 Fixture 類繼承的基類來決定它們的命名。 比如,如果你從 yii\test\ActiveFixture 繼承了一個(gè) DB Fixture, 你需要用數(shù)據(jù)庫表名字作為 Fixture 的數(shù)據(jù)文件名;如果你從 yii\mongodb\ActiveFixture 繼承了一個(gè) MongoDB Fixture, 你需要使用 collection 名作為文件名。

組織 Fixuture 類名的方式同樣可以使用前述的層次組織法,但是,為了避免跟數(shù)據(jù)文件產(chǎn)生沖突, 你需要用?fixtures?作為根目錄而非?data。

總結(jié)

注意:本節(jié)內(nèi)容正在開發(fā)中。

在上面,我們描述了如何定義和使用 Fixture,在下面,我們將總結(jié)出一個(gè) 標(biāo)準(zhǔn)地運(yùn)行與 DB 有關(guān)的單元測(cè)試的規(guī)范工作流程:

  1. 使用?yii migrate?工具來讓你的測(cè)試數(shù)據(jù)庫更新到最新的版本;
  2. 運(yùn)行一個(gè)測(cè)試:
    • 加載 Fixture:清空所有的相關(guān)表結(jié)構(gòu),并用 Fixture 數(shù)據(jù)填充
    • 執(zhí)行真實(shí)的測(cè)試用例
    • 卸載 Fixture
  3. 重復(fù)步驟2直到所有的測(cè)試結(jié)束。

以下部分即將被清除

管理 Fixture

注: 本章節(jié)正在開發(fā)中 > > todo: 以下部分將會(huì)與 test-fixtures.md 合并。

Fixture 是測(cè)試中很很重要的一個(gè)部分,它們的主要目的是為你提供不同的測(cè)試所需要的數(shù)據(jù)。 因?yàn)檫@些數(shù)據(jù),你的測(cè)試將會(huì)更高效和有用。

Yii 通過?yii fixture?命令行工具來支持 Fixture,這個(gè)工具支持:

  • 把 Fixture 加載到不同的存儲(chǔ)工具中,比如:RDBMS,NoSQL 等;
  • 以不同的方式卸載 Fixture(通常是清空存儲(chǔ));
  • 自動(dòng)的生成 Fixutre 并用隨機(jī)數(shù)據(jù)填充。

Fixtures 格式

Fixtures 是不同方法和配置的對(duì)象, 官方引用documentation?。 讓我們假設(shè)有 Fixtures 數(shù)據(jù)加載:

#Fixtures 數(shù)據(jù)目錄的 users.php 文件,默認(rèn) @tests\unit\fixtures\data

return [
    [
        'name' => 'Chase',
        'login' => 'lmayert',
        'email' => 'strosin.vernice@jerde.com',
        'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV',
        'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2',
    ],
    [
        'name' => 'Celestine',
        'login' => 'napoleon69',
        'email' => 'aileen.barton@heaneyschumm.com',
        'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q',
        'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6',
    ],
];

如果我們使用 Fixture 將數(shù)據(jù)加載到數(shù)據(jù)庫,那么這些行將被應(yīng)用于?users?表。 如果我們使用 nosql Fixtures ,例如?mongodbfixture,那么這些數(shù)據(jù)將應(yīng)用于?users?mongodb 集合。 為了了解更多實(shí)現(xiàn)各種加載策略,訪問官網(wǎng)?documentation。 上面的 Fixture 案例是由?yii2-faker?擴(kuò)展自動(dòng)生成的, 在這里了解更多?section。 Fixture 類名不應(yīng)該是復(fù)數(shù)形式。

加載 Fixtures

Fixture 類應(yīng)該以?Fixture?類作為后綴。默認(rèn)的 Fixtures 能在?tests\unit\fixtures?命名空間下被搜索到, 你可以通過配置和命名行選項(xiàng)來更改這個(gè)行為,你可以通過加載或者卸載指定它名字前面的-來排除一些 Fixtures,像?-User。

運(yùn)行如下命令去加載 Fixture:

yii fixture/load <fixture_name>

必需參數(shù)?fixture_name?指定一個(gè)將被加載數(shù)據(jù)的 Fixture 名字。 你可以同時(shí)加載多個(gè) Fixtures 。 以下是這個(gè)命令的正確格式:

// 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'

卸載 Fixtures

運(yùn)行如下命名去卸載 Fixtures:

// 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

同樣的命名選項(xiàng):?namespace,?globalFixtures?也可以應(yīng)用于該命令。

全局命令配置

當(dāng)命令行選項(xiàng)允許我們配置遷移命令, 有時(shí)我們只想配置一次。例如, 你可以按照如下配置遷移目錄:

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

自動(dòng)生成 fixtures

Yii 還可以為你自動(dòng)生成一些基于一些模板的 Fixtures。 你能夠以不同語言格式用不同的數(shù)據(jù)生成你的 Fixtures。 這些特征由?Faker?庫和?yii2-faker?擴(kuò)展完成。 關(guān)注?guide?擴(kuò)展獲取更多的文檔。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)