LaravelでTDD (テスト駆動開発) を体験してみた


はじめに

先日行われた PHPカンファレンス2018 に参加してきました。
カンファレンスへの参加は初めてだったのですが、その場の空気を感じられたり、ライブコーディングを見ることができたり、ノベルティをもらえたり、と、とても有意義な体験ができました。

特に印象に残っているのは、テストについて触れている講演が多かったということです。
また私が聞いた講演の中では、TDD(テスト駆動開発)について言及されている方が多かった覚えがあります。
私も業務でテストを書いていて、その恩恵を受けているので、テストの重要性は理解しているつもりですが、実際にTDDを意識して取り入れたことはなかったので、これを機に身につけてみたいと思いました。

そこでこの記事では、LaravelでTDDを体験しよう#phpcon2018 で発表された内容を、実際にやってみたいと思います。
なお、 実際の講演のアーカイブ も残してくれていますので、こちらを見ればこの記事を読む必要はありません 笑

目標

  • TDDの流れを理解する
  • 開発のリズムを体感する

事前準備

講演では Homestead をプロジェクトごとにインストールしていますが、今回はすでにある Homestead にプロジェクトを追加する方法をとります。
Homestead の導入手順については ドキュメント をご参照ください。
なお、お手軽に試したいので今回はデータベースは使用しません。

バージョン

  • Homestead 7.20.0
  • Laravel 5.7.*

プロジェクト追加

ホストOS
$ mkdir ~/laravel_tdd
homestead.yaml
folders:
    - map: ~/laravel_tdd
      to: /home/vagrant/code/laravel_tdd

sites:
    - map: laravel_tdd.test
      to: /home/vagrant/code/laravel_tdd/public
ホストOS
$ homestead reload --provision
$ homestead ssh

homestead コマンドについてはこちら

vagrant@homestead
$ cd code
$ composer create-project laravel/laravel laravel_tdd --prefer-dist

laravel_tdd.test にアクセスして例の画面が表示されればOKですが、今回はテストだけなので、もしアクセスできなくても問題ありません。

実装

サイクル

  1. TODOリストから実装する機能を決める
  2. まずテストを書く
  3. テストが失敗することを確認(まだ実装していないため)
  4. 素早く機能を実装
  5. テストが成功することを確認
  6. テストが成功することを確認しながらリファクタリング
  7. 完了したら最初に戻る

TODOリスト

今回はユーザーを取得する、という想定で、

  • 'api/users' に GET メソッドでアクセスできる
  • ユーザー情報がjson形式で取得できる

という機能を実装してみます。

1サイクル目

1-1. TODOリストから実装する機能を決める

  • 'api/users' に GET メソッドでアクセスできる

1-2. まずテストを書く

vagrant@homestead
$ php artisan make:test UserControllerTest
tests/Feature/UserControllerTest.php
// ...
class UserControllerTest extends TestCase
{
    public function testIndex()
    {
        $response = $this->json('GET', '/api/users');

        $response->assertStatus(200);
    }
}

1-3. テストが失敗することを確認

vagrant@homestead
$ vendor/bin/phpunit tests/Feature/UserControllerTest.php
PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 392 ms, Memory: 12.00MB

There was 1 failure:

1) Tests\Unit\UserControllerTest::testIndex
Expected status code 200 but received 404.
Failed asserting that false is true.

1-4. 素早く機能を実装

routes/api.php
Route::get('users', function () {});

1-5. テストが成功することを確認

vagrant@homestead
$ vendor/bin/phpunit tests/Feature/UserControllerTest.php
PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 365 ms, Memory: 12.00MB

OK (1 test, 1 assertion)

1-6. テストが成功することを確認しながらリファクタリング

今回はこのまま

1-7. 完了したら最初に戻る

2サイクル目

2-1. TODOリストから実装する機能を決める

  • ユーザー情報がjson形式で取得できる

2-2. まずテストを書く

tests/Feature/UserControllerTest.php
// ...
    public function testGetUsers()
    {
        $response = $this->json('GET', '/api/users');

        $response->assertJson([]);
    }

2-3. テストが失敗することを確認

vagrant@homestead
$ vendor/bin/phpunit tests/Feature/UserControllerTest.php
PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

.F                                                                  2 / 2 (100%)

Time: 436 ms, Memory: 12.00MB

There was 1 failure:

1) Tests\Unit\UserControllerTest::testGetUsers
Invalid JSON was returned from the route.

2-4. 素早く機能を実装

routes/api.php
Route::get('users', function () {
    return [];
});

2-5. テストが成功することを確認

vagrant@homestead
$ vendor/bin/phpunit tests/Feature/UserControllerTest.php
PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 186 ms, Memory: 12.00MB

OK (2 tests, 2 assertions)

2-6. テストが成功することを確認しながらリファクタリング

機能をコントローラーに移します。

vagrant@homestead
php artisan make:controller UserController
app/Http/Controllers/UserController.php
class UserController extends Controller
{
    public function index()
    {
        // 本当はデータベースから取得するが、仮で想定
        $user = ['id' => 1, 'user_name' => '吉田'];
        return $user;
    }
}
routes/api.php
Route::get('users', 'UserController@index');
vagrant@homestead
$ vendor/bin/phpunit tests/Feature/UserControllerTest.php
PHPUnit 7.5.1 by Sebastian Bergmann and contributors.

..                                                                  2 / 2 (100%)

Time: 366 ms, Memory: 12.00MB

OK (2 tests, 2 assertions)

2-7. 完了したら最初に戻る

感想

実装手順がこれでよかったのかはさておき、率直には少し難しいと感じました。
まず設計や Laravel の仕様を理解していないと、どのような値がapiから返ってくるのかわからないため、テストで確認すべき事項がわかりません。
ただ、テストが失敗した際のエラーを見ればどのようなレスポンスが返ってきているのかがわかるので、書いているうちに設計がまとまってくる感はありました。
設計がまとまっていないところやよくわからないところは小さい項目からテストして、わかってきたら本質的なテストが書けるようになるのかな、と感じました。

もし慣れることができたら、実装の手順が決まっている分、テンポよく開発を進めることができそうです。

おわりに

TDD体験をしてみましたが、まだまだ理解が足りていないので、普段の業務でも取り入れられそうな所には取り入れてみて慣れていきたいと思います。
TDDを身につけて、楽しくリズムよく綺麗に開発できるようになりたいです。
年末にスライドや本を読んで理解を深めることを課題としたいと思います。
50 分でわかるテスト駆動開発
テスト開発駆動