LoginSignup
2
6

More than 5 years have passed since last update.

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

Posted at

目的

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

環境

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 
2
6
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
6