如何正确在Laravel编写PHPUnit单元测试(Unit Test)

本篇文章内容主要参考自Laracasts的Laravel 5.4从头开始:测试101,并且替换掉旧的有写法,改写成新版(Laravel 5.6)的版本。

我们会编写一个简单的单元测试,可以在独立的测试资料库中测试模型操作CRUD,同时不会因为测试创建操作而造成资料库无限肥大下去。

建立测试案例
先建立一个测试案例,本篇以单元测试为例建立一个PostTest.php。

# Create a test in the Unit directory...
php artisan make:test PostTest --unit
规划测试内容
例如我们想要测试以下Post model的方法是否正常:

# app/Post.php
public static function archives()
{
return static::selectRaw('year(created_at) year, monthname(created_at) month, count(*) published')
->groupBy('year', 'month')
->orderByRaw('min(created_at) desc')
->get()
->toArray();
}

我们可以先依据Given-When-Then格式来编写注解,规划好我们要写的测试内容。关于Given-When-Then可以参考本篇文末延伸阅读本文的整理。

# tests/Unit/PostTest.php
public function testArchives()
{
// Given I have two records in the database that art posts,
// and each one is posted a month apart.

// When I fetch the archives.

// Then the response should be in the proper format.
}

建立测试资料
在这里,我们需要两笔资料来测试。我们这时可以使用Model Factories来产生我们需要的测试资料到资料库中。关于Model Factories以及资料库测试的详细说明,参考可以官方文件或道场的翻译文件。

我们先产生一个Post的Factory:

php artisan make:factory PostFactory --model=Post
Factory配合可以Faker自动随机产生我们需要的内容到资料库中:

# database/factories/PostFactory.php
$factory->define(App\Post::class, function (Faker $faker) {
return [
'user_id' => function () {
return factory(App\User::class)->create()->id;
},
'title' => $faker->sentence,
'body' => $faker->paragraph
];
});

Faker可以产生非常轻微的假资料,可以到fzaninotto / Faker看看各种稀奇古怪的假资料。

完成之后Factory,我们就可以拿来用在测试案例的给定的前提下,建立两笔帖子,并且第二篇手动指定建立时间为一个月前。何时进行则是执行我们要测试的方法。结果与我们预期的相不相同。

关于然后最初可以使用的其他断言方法,可以查看本篇文末延伸阅读本文的整理。

于是,我们的测试就长得像下方这样:

# tests/Unit/PostTest.php
public function testArchives()
{
// Given I have two records in the database that art posts,
// and each one is posted a month apart.
$first = factory(Post::class)->create();
$second = factory(Post::class)->create([
'created_at' => \Carbon\Carbon::now()->subMonth()
]);

// When I fetch the archives.
$posts = Post::archives();

// Then the response should be in the proper format.
$this->assertCount(2, $posts);
}

我们先简单测试是否有抓到两篇文章就好,之后再来改良它。

请看执行测试,如果发布资料表真的只有两篇文,archives method也正确执行的话,我们就会看到绿灯:

phpunit tests/Unit/PostTest.php
但是有一个问题,我们每次执行测试会建立资料在我们的资料库中,这样会跟其他的资料混在一起吧?如果我们local的帖子表本身就超过2篇这个测试就不会通过了?

建立测试资料库
为了避免测试资料与一般资料混在一起,我们可以建立一个测试资料库。

mysql -uroot -p
然后新增一个资料库,例如这里我们命名为「blog_testing」:

create database blog_testing;
接着我们可以到专案根目录的phpunit.xml指定测试时要用的资料库。这个档案会在执行测试时暂时覆写Laravel的环境变数,将像是APP_ENV对划线「测试」。我们在PHP区块新一个增DB_DATABASE环境变数,并指定为「blog_testing」:

<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="DB_DATABASE" value="blog_testing"/>
</php>

这时如果我们执行测试,会发现喷一堆错误,因为还没有针对blog_testing做迁移啊!那怎么做才能对测试资料库做迁移呢?比较距离的做法是在config / database.php中新增一个全新的连接,但这样为了迁移大费周章有点麻烦。

比较另一个脏简便的方法,就是暂时在.ENV把中DB_DATABASE也。改成「blog_testing」,然后执行:

php artisan migrate
这样就很快速的搞定了。记得要把.env改回来啊!

然后我们再试试看执行测试,如果顺利我们就会看到绿灯:

phpunit tests/Unit/PostTest.php
但是又有一个问题,我们每次执行测试都会多两笔资料,旧的fakerr资料又用不到了很妨碍眼啊!总不能每次都要去资料库手动删除吧?

在每次测试后重置资料库
在Laravel 5.4以前,我们可以使用DatabaseTransation这个特质来协助我们rollback成执行前的样子。在Laravel 5.5以后则是改用更为强大的RefreshDatabase这个Trait。详细的比较说明可以参考Laracasts的Laravel 5.5的新功能: RefreshDatabase特性。

所以只要在你的测试类别里的第一行加上,Laravel就会帮你处理所有事情:

use RefreshDatabase;
可以详细参考官方文件或是道场的翻译文件

改进测试案例
现在,我们想要更精准地撰写测试案例,例如想要知道何时分段捞出来的$ posts资料真的是我们在Given建立的资料可以怎么做呢?

以这次的测试为例,我们可以在随后改成区段用assertEquals来判断:

$this->assertEquals([
[
"year" => $first->created_at->format('Y'),
"month" => $first->created_at->format('F'),
"published" => 1
],
[
"year" => $second->created_at->format('Y'),
"month" => $second->created_at->format('F'),
"published" => 1
],
], $posts);

这样就可以确认我们在当分段所捞取的就是Given分段建立的文章了。

参与评论