環境
[OS] Debian 10
[PHP] 7.3.13
[Laravel] 7.6.2
[PHPUnit] 8.5.3
[Mockery] 1.3.1
はじめに
ソフトウェアやアプリケーション開発にとって品質管理は切っても切り離せない存在です。 日本の従来の品質管理は散布図やPB曲線などを用いて定量化を重視してきました。
しかし、それらは品質を数値化し分析することに寄りすぎていると感じるのは僕だけでしょうか? 品質管理の本来の目的は「品質を上げること」にあるはずです。
一言で品質と言っても様々ですが、「品質 = 要求仕様に則った実装」と定義すると、品質向上のための必要十分な施策はUnitTestによるホワイトボックステストが一番の近道だと考えています。 実際、ネットの情報を見ても、Googleなどのテック企業はUnitTestを中心に据えているように見えます。 その上で、報告などの目的でどうしても品質を定量化したいのであれば、UnitTestのカバレッジ計測ツールを使ったら良いのではないでしょうか。
1. 方針
下記の方針でユニットテストを書いていきますが、自分のプロジェクトと合わない部分は変えて下さい。
- 1-1. 後々CIで動かすことも考慮し、DB接続はモックで代替する。
- 1-2. フレームワークを含めず実装した箇所だけのテストコードを書く。
- 1-3. プロダクションコードの全ロジックを通すテストコードを書く。(「3. テスト記載方法」で後述)
- 1-4. 上記1-2の範囲でツールを用いてカバレッジを計測し、なるべく100%に近づける。(一般的にツールは評価が甘めのため)
- 1-5. テストコードの構成はフレームワークやデファクトスタンダードに合わせる。
2. 設定
2-1. PHPUnit
LaravelにはPHPUnitが最初から入ってるので、アプリのルートで下記コマンドを実行するとサンプルのテストコードが実行されます。
composer exec -v phpunit
アプリルートにある
tests
ディレクトリにテストコードを格納していきます。同ディレクトリ内はさらにFeature
とUnit
に分かれてます。tests |-- CreatesApplication.php |-- Feature | `-- ExampleTest.php |-- TestCase.php `-- Unit `-- ExampleTest.php
Laravelの方針に合わせて、下記の区分けでテストを書いていきます。
Feature
- 機能(エンドポイントURL)視点なので、実質コントローラのホワイトボックステストが該当すると思われます。
Unit
- 各クラスごとのテスト。
- DDDを採用している場合はService、Repositoryなど作成したクラスごとのテストケースを格納。
あと、Laravelのドキュメントにはありませんが、メンテナンス上プロダクションコードとテストコードのディレクトリ構成を合わせておきましょう。
プロダクションコード
app/Http/ |-- Controllers | |-- ContactController.php
テストコード
tests/Feature/Http/ |-- Controllers | |-- ContactControllerTest.php
次に方針1-2に沿ってテスト範囲を絞るためにアプリルートの
phpunit.xml
を編集します。細かい指定ができるのでこちらも参照してみて下さい。<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php"> ./app/Http/Controllers </directory> <exclude> <file>./app/Http/Controllers/Controller.php</file> </exclude> </whitelist> </filter>
2-2. Mockery
方針1-1に沿ってDBアクセスの代わりにMockeryからダミーの値を返すようにします。
Mockery(
[アプリルート]/vendor/mockery
)が入ってない場合はこちらのページなどを参考にインストールして下さい。
2-3. カバレッジ
品質評価の指標として、テストの網羅率を出します。
[アプリルート]/composer.json
に下記を追記。"scripts": { ... "test:coverage": [ "phpunit --coverage-html coverage" ] }
アプリルートで下記コマンドを実行すると結果が
[アプリルート]/coverage
にHTMLで出力されます。composer test:coverage
No code coverage driver is available
と出たり、結果が出力されない場合はXdebugのインストールが必要です。php -i
の出力結果をこちらの入力フォームにはりつけて「Analyze my phpinfo() output」をクリックすると自分の環境に合わせたインストール手順が提示されます。また、Mockeryとカバレッジを同時に使う場合は各テストメソッドのDocブロックにこれが必要なので記載しておいて下さい。
@runInSeparateProcess @preserveGlobalState disabled
3. テスト記載方法
3-1. 一般基準
一般的にユニットテストの基準は「命令網羅」「分岐網羅」「条件網羅」がありますが、ここでは「分岐網羅」(判定条件の真偽を少なくとも1回は実行)に合わせます。
if($x === 0){ //Do something. } if($y > 1){ //Do something. }
上記プロダクションコードを例に取ると、テストケースはこの2つになります。
(1) $x=0, $y=2 (両方true) (2) $x=1, $y=0 (両方false)
フローチャートにすると分かりやすいですが、(1)は赤線、(2)は青線で各ルートを1回は通すイメージです。
また、境界値もバグが起きやすい箇所なのでテストに含めましょう。
ただ、テストケースがむやみに増えてもメンテナンスが大変なので、上記の例だと(2)を
$y=1
に変えれば分岐網羅も境界値も同時にまかなえます。
3-2. PHPUnitとMockery
ID指定で特定のレコードを引っ張ってきて表示するというよくある編集画面を例にします。 レコードが取得できれば編集画面を表示し、何かの理由で取得できなかったら中断して一覧画面へ戻します。
分岐が1つなので、レコードが正常取得できた場合とできなかった場合のテストを用意します。 プロダクションコード内で実行が期待されるContactモデルのメソッドをモックに記憶させます。
・あるIDを引数にしてfindメソッドが呼ばれ1レコードを返却。
・21行目でedit()が呼ばれるURLにアクセスし、11-17行目で設定した値が画面に表示されることを確認。
もう1つはレコードが取得できない場合なので、モックのfindメソッドはnullを返すようにして、期待される動作として14行目で一覧画面への遷移を確認しています。
テストの書き方は以上ですが、もっとサンプルが見たい場合や自分でコードをさわってみたい場合は下記をご利用下さい。
4. サンプルコード
環境を作るのは面倒なのでDockerだけインストールしてこちらのDockerイメージを持ってきて下さい。ちなみにこのイメージは利用しやすそうだったこれをもとに拡張しました。サンプルを動かす手順はこちらです。
Dockerインストール
自分の環境にあったインストール方法を検索して下さい。Macの場合はこちらの記事が分かりやすかったです。Dockerインストールとイメージ取得でディスクは10Gほど使ったと思います。
Dockerイメージ取得
docker pull sankame/laravel-sample
Dockerイメージからコンテナを起動
docker run -ti -p 8080:80 --name test -d sankame/laravel-sample
Dockerを入れたマシン上で下記URLにアクセスするとサンプルページが表示されます。 http://localhost:8080/laravel-7-crud-app/public/contacts (もしインターネット上のサーバーで表示させる場合はドキュメントルートをpublic配下にするよう注意して下さい)
Dockerコンテナに入る
docker exec -it test /bin/bash
アプリルートは
/var/www/html/laravel-7-crud-app
です。今回の簡易CRUDアプリはコントローラーからモデルを呼び出すだけなので、[アプリルート]/tests/Feature
配下のみにテストコードを作成しました。テスト実行やカバレッジ出力のコマンドは前述の2を参照して下さい。