機能テストはいいぞ!

ふと、機能テストの良さについて語りたくなったので投稿。

単体テストより先に機能テストを書こう!

「テストを書く」というと単体テスト(ユニットテスト)を想像しがちですが、WEBシステムなら機能テスト(HTTPテスト)を先に書くことがおすすめです!

先に用語を整理しておきましょう。
まず、単体テストというのは(基本的に)メソッドや関数が想定通りに動いているかを確認するものです。
次に、機能テストというのは画面に表示されているものが想定通りに表示されているかを確認するものです。1

なぜ機能テストを先に書くといいの?

時間は有限なので、重要なテストから書きたいためです。 もちろん機能テストと単体テストを両方とも書けるといいですが、色々な都合で片方のテストしか書く時間がとれない場合、機能テストを書くことをおすすめします。 なぜなら、機能テストはユーザへの価値提供に直結するテストだからです。

また、機能テストのほうが直感的に書きやすいです。

  1. ストーリーの仕様をそのままテストにすれば良い

    単体テストはメソッド単位の粒度でテストを書くため、先にある程度のクラス設計が必要になります。一方で機能テストはページ単位の粒度でテストを書くため、クラス設計を待たずに書くことができます。

  2. テクニックが必要とされない

    単体テストではテストをしやすくするためのテクニック2が必要になりますが、機能テストではあまり必要になりません。言語の基本構文さえ身に付いていれば書き始めることができます。

機能テストを書くには?

機能テストを書くには、テストライブラリを使用すると良いです。
PHP界隈ではBehatをよく耳にします。が、私は使ったことがありません。
私はよくCodeceptionを使用しています。多くのフレームワークに対応しているのが特徴です。
また、Laravelではテストの機能を内包しており、追加ライブラリなしに機能テストを書くことができます。

機能テストのサンプル

実際にサンプルがないとわかりづらいですよね。
ここからは、LaravelでCodeceptionを利用した場合のサンプルを載せます。

$this->assertEquals(2, 1 + 1); のような何も参考にならない例ではなく、実際のシステムを想定した例を出していきます。

例えば、ブログシステムを考えてみましょう。
このブログシステムのトップページの要件は以下になります。

  • 記事がない場合は「まだ記事がありません」と表示される
  • 新着記事3つが掲載される
  • 非公開フラグの記事は表示されない

ひとつひとつをテストに落とし込んでみましょう。

最初のテスト

まず、トップページが表示されるテストを書きたいと思います。
これは暗黙的な要件ですが、ひとつのテストとして作成しておくと、バグが発生したときに対応しやすくなります。

<?php

public function トップページが表示される(FunctionalTester $I)
{
    $I->amOnPage('/');
    $I->seeResponseCodeIs(HttpCode::OK);
}

これは、「トップページにアクセスするとHTTPステータスコードが200番で返ってくる」というテストです。
ルーティングでミスっていれば404で返り、サーバ内でエラーがあれば500で返るため、バグの切り分けが容易になります。

ちょっとテクニック寄りなテストなのであまり機能テストっぽくありませんね。
次から機能テストらしさを出していきます!

記事がない場合は「まだ記事がありません」と表示される

先にコードを示します。

<?php

public function 記事がない場合は「まだ記事がありません」と表示される(FunctionalTester $I)
{
    $I->amOnPage('/');
    $I->see('まだ記事がありません');
}

どうでしょうか。わかりやすいと思いませんか?
$-> などの記号を抜くと、以下のようになります。

I am on page /  
I see まだ記事がありません

完璧な英語ではないですが、意味が完全にわかる形で読めますね!
これこそが機能テストの真価です!

新着記事3つが掲載される

このケースの場合、記事を事前に用意しておく必要があります。

<?php

public function 新着記事3つが掲載される(FunctionalTester $I)
{
    $newers = $I->haveMultiple(Article::class, 3, ['published_at' => '2001-01-01 00:00:00']);
    $older = $I->have(Article::class, ['published_at' => '2000-01-01 00:00:00']);

    $I->amOnPage('/');

    foreach($newers as $newer)  {
        $I->see($newer->title);
    }
    $I->dontSee($older->title);
}

havehaveMultiple メソッドで記事を生成しています。
また、このテストでは dontSee メソッドを用いて、4つ目の記事が表示されていないことをテストしました。

非公開フラグの記事は表示されない

このテストケースは今までの内容で記述できますね。

<?php

public function 非公開フラグの記事は表示されない(FunctionalTester $I)
{
    $article= $I->have(Article::class, ['publish_status' => PublishStatus::UNPUBLISH]);
    $I->amOnPage('/');
    $I->dontSee($article->title);
}

おわりに

  • 単体テストより先に機能テストを書こう!
  • 機能テストを書くにはテストライブラリを使おう!
  • 自然な表現でテストを書けるぞ!

あまり機能テストをガシガシ書いている人に出会ったことがないので、少しでも広まればいいと思います。


  1. 機能テストではJavaScriptの動作を確認することができません。JavaScriptの動作を確認したい場合は、ブラウザのエミュレータを使用した受入テストを書く必要があります。

  2. DIなど