TDD
アジャイル
laravel
docker
テスト駆動開発

本当に理解しているか自信ないから、改めてTDD(テスト駆動開発)をやってみた~アジャイルでDevOpsなシステム構築実践~

More than 1 year has passed since last update.

目的

初心に返ってテスト駆動開発をして、記事にしました。
記事にすると客観的になるし、ツッコミもらえるかもしれないし…

環境

Windows10
Docker
LaravelPHP
phpunit

作成する画面

資格種別一覧画面.png

データベースに格納されているデータを取得して表示するだけです。
データもフィルタする必要もないくらい超シンプルな機能です。

実装方法

RestAPIで実装するので、APIをつくります。
このAPIを作るときにTDD(テスト駆動開発)します。

前提

dockerを利用しているため、artisanはdocker-compose execを利用しています。
詳しくはこちらを参照してください。
補足として、dokcer-compose execを利用しないコマンドも載せます。

実装手順

  1. テストのテンプレートを作る
  2. 失敗するテストを書く
  3. テストに失敗する
  4. テストが通るようにプログラミングする
  5. テストに成功する
  6. 新しいテストケースを追加する
  7. 2から5の繰り返し

実装

1. テストのテンプレートを作る

Laravelのartisanを利用して、テストのテンプレートを作ります。
資格種別はExaminationsというテーブルに格納されています。
テストファイル名も"ExaminationTest"とします。

docker-composeを利用する場合
$ docker-compose exec workspace php artisan make:test ExaminationTest
普通に実行する場合
$ php artisan make:test ExaminationTest

"/tests/Feature/"フォルダに"ExaminationTest.php"ができます。

ここでいきなり"Feature"なの?と思った方!正しいです。
本来はAPIを実装する前にユニットテストがあるべきです。
しかし、このAPIはLaravelネイティブのメソッドを利用するため、ユニットテスト済みということにします。
APIが「Laravelネイティブのメソッド」のみであれば単体テストは不要と考えています。
※何でもかんでもテストを書くとボリュームが増えすぎて大変です。

2. 失敗するテストを書く

今回作成するAPIのURLは「/examination/getAll」とします。

まだ、コントローラもルーティングもしていないので、/examination/getAll にアクセスするとステータスコード200を返すはずです。

/tests/Feature/ExaminationTest.php
    public function testExaminationGetAll()
    {
        $response = $this->json('GET', '/api/getAllExamination');

        $response->assertStatus(200);
    }

3. テストに失敗する

テストを実行すると失敗します(あたりまえ~)。
--filterを利用して、メソッドを特定しています。

テスト実行(docker-compose利用)
$ docker-compose exec workspace ./vendor/bin/phpunit --filter=testExaminationGetAll
PHPUnit 6.5.4 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 703 ms, Memory: 10.00MB

There was 1 failure:

1) Tests\Feature\ExaminationTest::testExaminationGetAll
Expected status code 200 but received 404.
Failed asserting that false is true.

/var/www/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:78
/var/www/tests/Feature/ExaminationTest.php:30

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
通常時
$ docker-compose exec workspace ./vendor/bin/phpunit --filter=testExaminationGetAll

4. テストが通るようにプログラミングする

コントローラが存在していないのでテンプレートを作成します。

コントローラ生成(docker-composeを利用した場合)
$ docker-compose exec workspace php artisan make:controller ExaminationController
通常時
$ php artisan make:controller ExaminationController

続いて、テンプレートにgetAllメソッドを追加します。

/app/Http/Controllers/ExaminationController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ExaminationController extends Controller
{

    public function getAll()
    {

    }
}

ルーティングします。

/routes/api.php
//-- 試験区分をすべて取得
Route::get('/getAllExamination', 'ExaminationController@getAll');

5. テストに成功する

再度テストします。
テストがステータスコード200の確認だけなので、getAllメソッドが空でもテストは通ります。

テスト実行(docker-composeを利用)
$ docker-compose exec workspace ./vendor/bin/phpunit --filter=testExaminationGetAll
PHPUnit 6.5.4 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 553 ms, Memory: 10.00MB

OK (1 test, 1 assertion)
通常時
$ ./vendor/bin/phpunit --filter=testExaminationGetAll

6. 新しいテストケースを追加する

このAPIは1件以上(現状4件)必ずデータが返ってきます。
データ件数が0件はエラーです。
したがって、「取得するJOSNを配列にすると1件以上存在すること」というテストが必要です。

/tests/Feature/ExaminationTest.php
public function testExaminationGetAll()
{
    /*
     * レスポンスを取得
     */
    $response = $this->json('GET', '/api/getAllExamination');

    /*
     * テストケース
     * 1. ステータスコード200が返ること
     */
    $response->assertStatus(200);

    /*
     * テストケース
     * 2. 取得するJOSNを配列にすると1件以上存在すること(0件はありえない)
     */
    $this->assertGreaterThanOrEqual(1, count(json_decode($response->getContent())));
}

実はこのテストケースにするために試行錯誤しています。
過度なテストをしても負荷がかかりメンテナンスが大変だし、テストしなさすぎも問題です。

この試行錯誤がTDD(テスト駆動開発)で重要です!!!
※この記事はこれがいいたい!

7. 2から5の繰り返し

再度テストを実行するとエラーになります(再度失敗するテストを書いています)。

テスト失敗
$ docker-compose exec workspace ./vendor/bin/phpunit --filter=testExaminationGetAll
PHPUnit 6.5.4 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 579 ms, Memory: 10.00MB

There was 1 failure:

1) Tests\Feature\ExaminationTest::testExaminationGetAll
Failed asserting that 0 is equal to 1 or is greater than 1.

/var/www/tests/Feature/ExaminationTest.php:44

FAILURES!
Tests: 1, Assertions: 3, Failures: 1.

試験区分をすべて取得してJSONで返すように実装します。
前述しましたが、このgetAll()メソッドは、Examination::all()を返すだけです。
Examination::all()が自作のメソッドであれば、ユニットテストをします。
しかし、これはLaravelのネイティブメソッドで、フレームワークが動作を保証していると考え、ユニットテストは書きません。

/app/Http/Controllers/ExaminationController.php
class ExaminationController extends Controller
{

    /**
     * すべての試験区分を取得
     *
     *
     * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\static[]
     */
    public function getAll()
    {
        /*
         * オブジェクトをreturnするとLaravelがJSONにしてくれる
         */
        return Examination::all();
    }
}

テストを実行すると、成功します

テストを再度実行
$ docker-compose exec workspace ./vendor/bin/phpunit --filter=testExaminationGetAll
PHPUnit 6.5.4 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 619 ms, Memory: 12.00MB

OK (1 test, 3 assertions)

このように
試行錯誤してテストケースを追加
→テスト失敗
→テストが通るようにプログラミング
→テスト成功
これを繰り返すことで、テストができる小さい、保守性・品質が高いAPIを作れます!

余談 テストの実行の仕方

tests配下のテストをすべて実行

Unitフォルダ配下ももFeatureフォルダ配下もテストが実行されます。

tests配下のテストをすべて実行
$ ./vendor/bin/phpunit

指定ディレクトリ配下全部実行

下記の場合Featureフォルダ配下だけ

Featureフォルダ配下だけテストを実行
$ ./vendor/bin/phpunit tests/Feature

クラス名を指定してテストを実行

クラスを指定できます。

クラス名を指定してテストを実行
$ ./vendor/bin/phpunit --filter=ExaminationTest

メソッドを指定してテストを実行

メソッドも指定できます。
テストを書いているときはこれを使うのが便利ですね。

メソッド名を指定してテストを実行
$ php vendor/bin/phpunit --filter=visit_sample_page