规划好了APN provider的路由之后,这一节,我们就用TDD的方式逐一实现它们。
作为开始,我们用最简单的两个API来演示一个完整的TDD过程,通过这个过程来体会测试用例是如何“驱动”开发过程的。听着好像挺高级,其实核心的想法很简单:先假定功能已经开发好了,然后直接编写测试用例。显然,此时的测试是无法通过的。而这个驱动的过程,就是不断完善代码,最终让测试得以通过的过程。
定义API的细节
直接来看例子,我们将要实现的两个API是:
GET /api/v1/public-key/version
GET /api/v1/public-key
这两个API都不需要传递参数,服务器直接返回JSON包含对应请求的资源。其中,version
的返回值是这样的:
{
'version': 1
}
而public-key
的返回值是这样的(这里,用xxxxxxxxxxxxxxx
替代了返回的公钥):
{
'public': 'xxxxxxxxxxxxxxxx',
'version': 1
}
以上,就是这两个API的返回值细节。接下来,假设它们已经实现完了,我们要为它们编写测试用例。
设置phpunit
开始之前,要先设置一下执行单元测试的phpunit。Laravel在项目的根目录中添加了一个phpunit.xml
文件,在这个文件中找到php
节点,在其中添加下面两行内容:
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
它们表示在单元测试执行的时候使用内存中的sqlite数据库,取代开发环境中的MySQL。这样做是因为在测试的过程中可能会频繁的重置数据库中的内容,但我们并不希望这些操作影响到开发环境中使用的数据库。
从编写测试用例开始
设置好之后,作为TDD的开始,我们直接为这两个API编写测试用例。核心思路非常简单:请求API并检查返回值就好了。为此,在项目的根目录,先执行下面的命令创建测试文件:
php artisan make:test PublicKeyTest
在Laravel的tests
目录里,有两个子目录,其中:
Feature
用于存放某个功能的测试用例,例如将要实现的API。当我们执行了上面的命令之后,在Feature
目录中就会包含一个PublicKeyTest.php
文件,对/public-key
这个API的测试,就都写在这里;Unit
存放更小粒度的单元测试,例如某个函数。为了把生成的测试用例放到这个目录,在make:test
的时候,加上--unit
就好了,稍后,我们开发向APN发送数据的功能时,就会把处理数据的测试用例放到这里;
现在,打开刚创建的PublicKeyTest.php
,在它包含的PublicKeyTest
类中,自带了一个testExample
测试用例:
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PublicKeyTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
在phpunit里,所有用test
开头方法,都是测试用例。从这个默认的测试用例中,我们能发现不少有用的东西。例如,为了发起一个API请求,直接用$this->get()
就好了,其实,Laravel还支持post / put / patch / delete
方法。另外,为了检测HTTP API返回的状态码,可以通过assertStatus(200)
实现。说到这,似乎测试GET /api/v1/public-key/version
这个API就没任何问题了。
我们把之前的testExample
改造成这样:
public function testGetPublicKeyVersion()
{
$response = $this->get('/api/v1/public-key/version');
$data = $response->decodeResponseJson();
$this->assertEquals($data['version'], 1);
$response->assertStatus(200);
}
可以看到,除了检查返回的status code之外,我们还用assertEquals
检查了返回JSON中的version
字段的值。这样,第一个测试用例就完成了。现在,登录到php容器,在项目的根目录执行./vendor/bin/phpunit
。emm...
如果没有意外,显然测试会失败的,因为处理这个路由的KeyController@getPublicKeyVersion
现在还是一个空方法。于是,接下来的任务就变成了,让这个测试用例通过测试。
要注意的是,我们的目标仅仅是通过测试而已,不要给这个目标附加任何额外的要素,于是,第一版的getPublicKeyVersion
只要这样貌似就可以了:
public function getPublicKeyVersion()
{
return response()->json([
'version' => 1
]);
}
What's next?
虽然getPublicKeyVersion
看着有点儿糊弄事儿,但这个方法的确可以帮助我们通过测试。也就是说,我们编写的测试用例,只能“驱动”出来这么一个糊弄事儿的版本。因此,问题的源头,还要追溯到一开始我们编写的测试用例上。理解了这个围绕着测试用例去编写代码的想法之后,下段视频里,我们就来改进这个测试驱动开发的过程。