codesleeve / fixture
一个框架无关的、简单(但优雅)的 PHP 测试用例库。
Requires
- php: >=5.3.0
- fzaninotto/faker: 1.3.0
Requires (Dev)
- illuminate/database: ~4
- illuminate/support: ~4
- mockery/mockery: dev-master
This package is not auto-updated.
Last update: 2022-02-01 12:28:09 UTC
README
一个框架无关的、简单(但优雅)的 PHP 测试用例库。
Fixture 由 Travis Bennett 创建。
需求
- php >= 5.3
- 数据库连接的 PDO 对象实例。
- 数据库表的主键应具有名为 'id' 的列名。
- 数据库表的外键应由关联表的单数名称加上后缀 '_id' 组成(例如,blog_id 将是名为 blogs 的表的外键名称)。
安装
Fixture 以 composer 包的形式分发,这是在您的应用程序中使用它的方式。
使用 Composer 安装此包。编辑您的项目 composer.json
文件以要求 codesleeve/fixture
。
"require": { "codesleeve/fixture": "dev-master" }
概述
为了创建针对数据库特定应用程序逻辑的良好测试,在运行测试之前通常需要在测试数据库中用假数据填充。此包允许您通过使用数据库测试用例(测试用例只是“测试数据”的另一种说法)来实现这一点。测试用例可以使用原生 PHP 数组语法创建,并且不依赖于任何特定的关系型数据库管理系统。简而言之,此包允许您将以下内容
class fooTest extends PHPUnit_Framework_TestCase { /** * A PDO connection instance. * * @var PDO */ protected $db; /** * Initialize our test state. * * @return void */ public function setUp() { // create a pdo instance if (!$this->db) { $this->db = new PDO('sqlite::memory:'); } // populate the users table $sql = 'INSERT INTO users (email, password, status, created_at, updated_at) values (?, ?, ?, ?, ?)'; $sth = $this->prepare($sql); $sth->execute(array('john@doe.com', 'abc12345$%^', 1, date('Y-m-d'), date('Y-m-d'))); $sql = 'INSERT INTO users (email, password, status, created_at, updated_at) values (?, ?, ?, ?, ?)'; $sth = $this->prepare($sql); $sth->execute(array('Jane', 'Doe', 'jane@doe.com', 1, 'abc12345$%^', date('Y-m-d'), date('Y-m-d'))); $sql = 'INSERT INTO users (email, password, status, created_at, updated_at) values (?, ?, ?, ?, ?)'; $sth = $this->prepare($sql); $sth->execute(array('Jim', 'Doe', 'jim@doe.com', 0, 'abc12345$%^', date('Y-m-d'), date('Y-m-d'))); // populate the roles table $sql = 'INSERT INTO roles (name, created_at, updated_at) values (?, ?, ?)'; $sth = $this->prepare($sql); $sth->execute(array('admin', date('Y-m-d'), date('Y-m-d'))); $sql = 'INSERT INTO roles (name, created_at, updated_at) values (?, ?, ?)'; $sth = $this->prepare($sql); $sth->execute(array('user', date('Y-m-d'), date('Y-m-d'))); // populate the roles_users table $sql = 'INSERT INTO roles_users (role_id, user_id) values (?, ?)'; $sth = $this->prepare($sql); $sth->execute(array(1, 1)); $sql = 'INSERT INTO roles_users (role_id, user_id) values (?, ?)'; $sth = $this->prepare($sql); $sth->execute(array(1, 2)); $sql = 'INSERT INTO roles_users (role_id, user_id) values (?, ?)'; $sth = $this->prepare($sql); $sth->execute(array(2, 3)); } /** * Reset our test state. * * @return void */ public function tearDown { $this->db->query("TRUNCATE TABLE users"); $this->db->query("TRUNCATE TABLE roles"); $this->db->query("TRUNCATE TABLE roles_users"); } /** * A database integration test of some sort. * * @test * @return void */ public function it_should_be_able_to_do_some_query() { // Test that some query is working correctly. } }
转换为以下内容
class fooTest extends PHPUnit_Framework_TestCase { use Codesleeve\Fixture\Fixture; /** * The fixture instance. * * @var Fixture */ protected $fixture; /** * Initialize our test state. * * @return void */ public function setUp() { // set the fixture instance $this->fixture = Fixture::getInstance(); // populate our tables $this->fixture->up(); } /** * Reset our test state. * * @return void */ public function tearDown { $this->fixture->down(); } /** * A database integration test of some sort. * * @test * @return void */ public function it_should_be_able_to_do_some_query() { // Test that some query is working correctly. } }
驱动程序
Fixture 目前支持两种驱动程序
- 标准驱动程序 - 这是此包中最基本的驱动程序。它不需要 ORM,也没有关系概念。
- 优雅驱动器 - 此驱动器允许完全使用Eloquent ORM。在创建测试数据时,可以使用Eloquent关系来轻松管理测试数据中的外键。
配置
为了使用测试数据,首先需要对其进行初始化。一个不错的做法是在您的bootstrap文件中进行配置(通过phpunit.xml配置),当然,您也可以在最适合您的地方进行配置。
// Create a pdo instance (this may already exist in your app) $db = new \PDO('sqlite::memory:'); // Use the pdo instance to create a new driver instance $driver = new Codesleeve\Fixture\Drivers\Standard($db); // Create a configuration array. $config = array( 'location' => 'path/to/your/fixtures.php' // The directory you wish to load fixture files from. ); // Fixture implements a singleton pattern. // Get an instance of Fixture and set the config and driver. $fixture = Fixture::getInstance(); $fixture->setConfig($config); $fixture->setDriver($driver); // Alternatively, you could do this in one swoop. $fixture = Fixture::getInstance($config, $driver);
示例
为了我们的示例,让我们假设我们有一个以下主题的 bleach 系统:
- 表格
- soul_reapers
- zanpakutos
- ranks
- ranks_soul_reapers(列:integer rank_id,integer soul_reaper_id,integer status)。
- 关系
- 一个鬼道神只拥有一柄斩魄刀,属于多个 ranks(多对多)。
- 一柄斩魄刀只属于一个鬼道神。
- 一个等级属于多个鬼道神。
标准驱动器
步骤 1 - 测试数据设置
在您的应用程序测试文件夹内,创建一个名为 fixtures 的文件夹。然后,在此文件夹内创建几个测试数据文件。测试数据文件使用原生的PHP数组语法编写。要创建一个,只需创建一个以对应的表命名的文件,并返回一个数据数组。以下是一个示例,我们将为我们的 'soul_reapers' 表创建一些测试数据:
在 tests/fixtures/soul_reapers.php
return array ( 'Ichigo' => array ( 'first_name' => 'Ichigo', 'last_name' => 'Kurosaki' ), 'Renji' => array ( 'first_name' => 'Renji', 'last_name' => 'Abarai' ), 'Genryusai' => array( 'first_name' => 'Genryusai', 'last_name' => 'Yammamoto' ) );
在此,我们简单地返回一个包含测试数据的嵌套数组。注意,有两个测试数据,每个都有唯一的名称(这一点非常重要,因为您将很快看到我们可以在测试中轻松地引用加载的测试数据)。现在,没有斩魄刀的鬼道神是不完整的,所以让我们用以下测试数据来填充我们的 zanpakutos 表:
在 tests/fixtures/zanpakutos.php
return array ( 'Zangetsu' => array ( 'soul_reaper_id' => 'Ichigo', 'name' => 'Zangetsu', ), 'Zabimaru' => array ( 'soul_reaper_id' => 'Renji', 'name' => 'Zabimaru', ), 'Ryujin Jakka' => array( 'soul_reaper_id' => 'Genryusai', 'name' => 'Ryujin Jakka', ) );
因为斩魄刀必须属于一个鬼道神(它毕竟是他们的灵魂的一部分),所以我们知道我们的 'zanpakutos' 表将包含一个名为 'soul_reaper_id' 的列。为了将斩魄刀与其所有者联系起来,我们可以简单地设置这个外键为它所属的对应鬼道神的名称。无需担心具体的id,插入顺序等问题。这很简单。继续前进,到目前为止,我们已经能够轻松地表达 'soul_reapers' 和 'zanpakutos' 之间的父/子(1对1)关系,但多对多(连接表)关系怎么办?作为一个示例,让我们看看另外两个表;'ranks' 和 'ranks_soul_reapers'。我们的 ranks 表测试数据如下:
在 tests/fixtures/ranks.php
return array ( 'Commander' => array ( 'title' => 'Commander' ), 'Captain' => array ( 'title' => 'Captain', ), 'Lieutenant' => array ( 'title' => 'Lieutenant', ), 'Substitute' => array ( 'title' => 'Substitute Shinigami', ), );
'ranks_soul_reapers'(多对多)连接表测试数据如下:
在 tests/fixtures/ranks_soul_reapers.php
return array ( 'CommanderYammamoto' => array ( 'soul_reaper_id' => 'Yammamoto', 'rank_id' => 'Commander' ), 'CaptainYammamoto' => array ( 'soul_reaper_id' => 'Yammamoto', 'rank_id' => 'Captain' ), 'LieutenantAbari' => array ( 'soul_reaper_id' => 'Renji', 'rank_id' => 'Lieutenant' ), 'SubstituteKurosaki' => array ( 'soul_reaper_id' => 'Ichigo', 'rank_id' => 'Substitute' ) );
注意,在我们的 ranks_soul_reapers 连接表中,我们有一个 'CommanderYammamoto' 和一个 'CaptainYammamoto' 的条目;这是因为源赖光既是总队长也是队长的角色,他是 Gotei 13 的总队长。
步骤 2 - 初始化测试数据类实例。
现在测试数据文件已经创建,下一步是在我们的测试中创建测试数据库实例。考虑以下测试(我们在这里使用PHPUnit,但测试框架无关紧要;SimpleTest也可以正常工作):
在 tests/exampleTest.php
use Codesleeve\Fixture\Fixture; use Codesleeve\Fixture\Drivers\Standard as StandardDriver; /** * The fixture instance. * * @var Fixture */ protected $fixture; /** * Initialize our test state. * * @return void */ public function setUp() { // set the fixture instance $this->fixture = Fixture::getInstance(); // populate our tables $this->fixture->up(); } /** * Reset our test state. * * @return void */ public function tearDown { $this->fixture->down(); }
这里发生了什么?一些事情
- 我们通过getInstance()方法获取fixture实例(这是一个单例模式)。
- 我们在fixture对象上调用up()方法。此方法初始化数据库并将插入的记录作为php标准对象缓存在fixture对象上。
- 不带参数调用up方法将初始化所有fixture。
- 使用fixture名称数组调用up方法将仅初始化那些fixture(例如,$this->fixture->up(array('soul_reapers'))将仅初始化soul_reapers表)。
- 在tearDown方法中,我们调用down()方法。此方法将截断所有已插入fixture数据的表。
作为额外的好处,可以(如果需要)直接从fixture对象本身访问种子数据库记录作为php标准对象。
// Returns 'Kurosaki' echo $this->fixture->soul_reapers('Ichigo')->last_name;
Eloquent Driver
步骤 1 - 模型设置
在您的模型文件夹(或您当前存储模型的地方),创建SoulReaper和Zanpakuto模型。
class SoulReaper extends Eloquent { protected $table = 'soul_reapers'; /** * A soul reaper has one zanpakuto. * * @return hasOne */ public function zanpakuto() { return $this->hasOne('Zanpakuto'); } /** * A soul reaper belongs to many ranks. * * @return belongsToMany */ public function ranks() { return $this->belongsToMany('ranks'); } } class Zanpakuto extends Eloquent { protected $table = 'zanpakutos'; /** * A zanpakuto belongs to a Soul Reaper. * * @return belongsTo */ public function soulReaper() { return $this->belongsTo('SoulReaper'); } }
步骤 2 - Fixture 设置
在您的应用程序测试文件夹中,创建一个名为fixtures的文件夹。接下来,在此文件夹内创建几个fixture文件。Fixture文件使用原生PHP数组语法编写。要创建一个,只需创建一个以fixture对应表命名的文件,并返回一个数据数组。就像我们之前的例子一样,让我们为我们的灵魂收割者系统创建一些fixture数据。
在 tests/fixtures/soul_reapers.php
return array ( 'Ichigo' => array ( 'first_name' => 'Ichigo', 'last_name' => 'Kurosaki', 'ranks' => array('Substitute|active:1') ), 'Renji' => array ( 'first_name' => 'Renji', 'last_name' => 'Abarai', 'ranks' => array('Lieutenant|active:1') ), 'Genryusai' => array( 'first_name' => 'Genryusai', 'last_name' => 'Yammamoto', 'ranks' => array('Captain|active:1', 'Commander|active:1') ) );
在 tests/fixtures/zanpakutos.php
return array ( 'Zangetsu' => array ( 'soulReaper' => 'Ichigo', 'name' => 'Zangetsu', ), 'Zabimaru' => array ( 'soulReaper' => 'Renji', 'name' => 'Zabimaru', ), 'Ryujin Jakka' => array( 'soulReaper' => 'Genryusai', 'name' => 'Ryujin Jakka', ) );
在 tests/fixtures/ranks.php
return array ( 'Commander' => array ( 'title' => 'Commander' ), 'Captain' => array ( 'title' => 'Captain', ), 'Lieutenant' => array ( 'title' => 'Lieutenant', ), 'Substitute' => array ( 'title' => 'Substitute Shinigami', ), );
在我们的每个文件中,我们只是返回一个包含fixture数据的嵌套数组。在这个数组中,我们使用数组语法创建记录以填充我们的数据库表。
因为我们知道Zanpakuto与SoulReaper有一个'belongsTo'关系,所以现在我们可以使用这个关系轻松地为我们的fixture数据创建外键。我们不需要担心特定的id、插入顺序等。我们只需要在fixture中为关系分配一个值(从belongsTo一方),它将自动填充。这非常简单。
// Creates the 'Zangetsu' record in the zanpakutos table and assigns ownership // to 'Ichigo' (It populates the 'soul_reaper_id' foreign key for us automatically). 'Zangetsu' => array ( 'soulReaper' => 'Ichigo', 'name' => 'Zangetsu', ),
也可以填充多对多(N到N)连接表关系。在我们的示例中,灵魂收割者与等级有多个对多个(belongsToMany)的关系。本质上,一个灵魂收割者可以有多个等级,等级也可以属于多个灵魂收割者(注意Genryusai同时拥有'Commander'和'Captain'等级;这是因为Genryusai Yammamoto是Gotei 13的Captain Commander)。为了表示这一点,我们只需将一个数组赋值给与fixture对应模型的多对多关系命名的值。在我们的示例中,从belongsToMany关系的灵魂收割者一方,我们简单地传递一个'rank'数组(因为我们定义了名为'ranks'的belongsToMany关系在我们的SoulReaper模型中),我们想要一个灵魂收割者拥有的。可以使用'|'分隔符以及使用':'分隔键/值来填充连接表上的额外列。
// This assigns Genryusai the ranks of Captain and Commander and // sets the 'active' pivot column to 1 for each rank. 'Genryusai' => array( 'first_name' => 'Genryusai', 'last_name' => 'Yammamoto', 'ranks' => array('Captain|active:1', 'Commander|active:1') )
步骤 3 - 初始化fixture类的一个实例。
现在测试数据文件已经创建,下一步是在我们的测试中创建测试数据库实例。考虑以下测试(我们在这里使用PHPUnit,但测试框架无关紧要;SimpleTest也可以正常工作):
在 tests/exampleTest.php
use Codesleeve\Fixture\Fixture; use Codesleeve\Fixture\Drivers\Eloquent as EloquentDriver; /** * The fixture instance. * * @var Fixture */ protected $fixture; /** * Initialize our test state. * * @return void */ public function setUp() { // set the fixture instance $db = new \PDO('sqlite::memory:'); $driver = new EloquentDriver($db); $this->fixture = Fixture::getInstance(array('location' => 'path/to/your/fixtures.php'), $driver); // populate our tables $this->fixture->up(); } /** * Reset our test state. * * @return void */ public function tearDown { $this->fixture->down(); }
伪造数据
Fixture与Faker有内置集成。创建伪造的fixture数据非常容易。
// We can call Faker through fixture via the 'Fake' method. // Here, we'll whip up some fake info for Ichigo: 'Ichigo' => array ( 'first_name' => 'Ichigo', 'last_name' => 'Kurosaki', 'ranks' => array('Substitute|active:1'), 'bio' => Fixture::fake('text'), 'age' => Fixture::fake('randomNumber', 15, 18), 'address' => Fixture::fake('address') ),
通过使用fixture为我们的测试数据库设置种子,我们获得了在集成测试过程中对数据库中任何给定时间的内容的非常精确的控制。这反过来使我们能够非常容易地测试包含数据库特定逻辑的应用程序的部分。
贡献
夹具始终欢迎来自社区的贡献,但我要求您只向开发分支提交所有拉取请求。让我再次强调这一点;我将不会接受对master分支的拉取请求。