Laravel中针对API的TDD步骤

测试驱动开发(英文:Test-driven development,缩写:TDD)是一种软件开发过程中的应用方法,由极限编程中倡导,以其倡导先写测试程序,然后编码实现其功能得名。

  • Laravel 5.6.28
  • PHPUnit 7.2.7

准备Laravel测试环境

修改测试配置

在项目根目录中,更新phpunit.xml文件的如下配置:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="API_DEBUG" value="false"/>
<ini name="memory_limit" value="512M" />

更新完后phpunit.xml文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <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_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="APP_DEBUG" value="false"/>
        <env name="MAIL_DRIVER" value="log"/>
        <ini name="memory_limit" value="512M" />
    </php>
</phpunit>

只需要在内存中进行测试,这样测试运行的速度会快些,所以在database配置项目中我们将使用sqlite:memory: (Sqlite的内存数据库)。 将APP_DEBUG设置为false,因为只需要对真实产生的错误进行断言。 另外,随着项目迭代测试用例会越来越多,在将来可能会需要增加memory_limit的值。

修改测试基类

在Laravel里测试用例的基类Tests\TestCase中作一些测试相关的准备

<?php

namespace Tests;

use Faker\Factory as Faker;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

/**
 * Class TestCase
 *
 * @package Tests
 */
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseMigrations, DatabaseTransactions;

    /**
     * @var \Faker\Factory
     */
    protected $faker;

    /**
     * Set up the test
     */
    public function setUp()
    {
        parent::setUp();
        $this->faker = Faker::create();
    }

    /**
     * Reset the migrations
     */
    public function tearDown()
    {
        $this->artisan('migrate:reset');
        parent::tearDown();
    }

}

编写测试用例

在终端使用命令php artisan make:test Api/ArticleTest --unit,生成的文件在tests/Unit/Api/ArticleTest,修改内容如下:

<?php

namespace Tests\Unit\Api;

use Tests\TestCase;

/**
 * Class ArticleTest
 *
 * @package Tests\Unit\Api
 */
class ArticleTest extends TestCase
{
    /**
     * 创建文章
     */
    public function testCanCreateAnArticle()
    {
        $data = [
            'title' => $this->faker->sentence,
            'content' => $this->faker->paragraph
        ];

        $this->post(route('api.articles.store'), $data)
             ->assertStatus(201)
             ->assertJson($data);
    }
}

为了让phpunit识别测试,需要在测试方法上添加/** @test */注释标识或者是测试方法命名以test前缀开头,这里选择后者。

上面编写的测试中,测试是否能创建一篇文章,并且断言了在创建文章成功后应用将返回201状态码还有预期的JSON数据。

在创建好我们的第一个测试后,执行phpunit或者vendor/bin/phpunit

在终端执行vendor/bin/phpunit后测试结果显示失败了,因为在测试驱动开发中是先写测试程序,然后在编码实现功能的。

所以在创建测试程序开始测试程序执行后的结果就是测试失败(测试驱动开发的第二条原则)。

在路由文件中创建请求的URL

在你项目中的routes/api.php文件中创建这个URL,在api中定义的路由其URL会自动加上/api前缀,如下定义一个资源路由。另外也可以通过在命令行运行php artisan route:list查看具体的路由详情。

Route::group(['prefix' => 'v1', 'namespace' => 'Api', 'as' => 'api.'], function () {
    Route::resource('articles', 'ArticleController');
});

在测试中断言响应会返回201状态码但是却返回了500,是因为请求的路由指向的处理控制器文件不存在。这个api/v1/articles POST 路由在应用中并不存在所以针对这个请求应用抛出了500错误。

可以进一步调试 POST 请求后的输出,如果没有明确地给出提示发生了什么才导致的这个错误,可以打开Laravel应用的日志文件/storage/logs/laravel.log里查找详细的错误信息。

从上图可以看出App\Http\Controllers\Api\ArticleController控制器不存在。

编写控制器逻辑

在终端使用命令php artisan make:controller Api/ArticleController,生成的文件在app/Http/Controllers/Api/ArticleController,修改内容如下

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ArticleController extends Controller
{
    public function store(Request $request)
    {
        return Article::create($request->all());
    }
}

再次执行vendor/bin/phpunit运行测试,发生如下错误:

打开storage/logs/laravel.log查看错误,发现是模型找不到:

创建模型

使用如下命令生成Article模型,模型文件默认在app\Article

php artisan make:model Article

创建好模型后,修改控制器store方法中对模型的引用:

<?php

namespace App\Http\Controllers\Api;

use App\Article;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ArticleController extends Controller
{
    public function store(Request $request)
    {
        return Article::create($request->all());
    }
}

修改完成后,再次执行测试:

上面的命令报错是因为没有设置模型的fillable属性,修改如下:

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $fillable = ['title', 'content'];
}

修改完毕后,再次执行测试:

如上报错,说明数据库表不存在,需要执行迁移文件生成对应的数据表

生成迁移文件

使用下面的命令生成迁移文件:

php artisan make:migration create_articles_table --create=articles

生成的文件目录在databases/migrations下,可以打开对应的文件修改内容如下:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->text('content');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
}

当定义好迁移文件后再次执行vendor/bin/phpunit,发现成功了。

通过文章总结起来测试驱动开发有三条原则:

  • 倡导先写测试程序,再编码实现功能

  • 测试程序创建起肯定会测试失败

  • 在让测试程序测试成功的过程中逐步编码实现功能

参考链接

results matching ""

    No results matching ""