PHPUnitでユニットテスト① 導入編

PHPUnitでユニットテストを行う

PHPのテストフレームワーク、PHPUnitを使ったユニットテストの基本についてまとめます。

環境: PHPUnit 9.5.4、 PHP 7.4.8

ユニットテスト(単体テスト)とは

ユニットテストとはシステム、ソフトウェア開発における単体テストと呼ばれる工程であり、プログラム全体のうちの小さな部分単位(ユニット)ごとの検証を意味しています。

ひとつのユニットとは具体的に、プログラムにおける機能を実現するための関数やクラスのメソッドを指しており、ユニットテストではそれらが期待通りの実装となっているかを個別に検証し品質を担保します。

これとは対照に、プログラム全体、複数機能の組み合わせの動作検証が結合テストとなります。(総合テストとも呼ぶ)

これから紹介するPHPUnitのようなツール、テストフレームワークにはユニットテストのための便利な機能が標準的に組み込まれており、開発者自身でフレームワークの規約に沿ったテストコードを事前に作成しておく事でユニットテストの自動実行が可能となります。

Composerを使用してPHPUnitをインストールする

PHPUnitはPHPのパッケージ管理ツール、Composerを使用して導入することができます。

Composerについての基本は以下を参照ください。

(Mac)Composerを使用したLaravelのインストールとプロジェクト作成

公式ドキュメントにも記載されていますが、PHPUnitのライブラリファイルはひとつのプロジェクトごとにインストールすることが推奨されているようです。

composerの設定ファイル、composer.jsonの有無別にインストール方法を解説します。

1. composer.jsonが無い場合

プロジェクト構成例
(プラグラム全体のディレクトリをapp、クラスファイルなど、実装プログラムをsrc配下に設置)

.
└── app
    └── src

app配下に移動

srcディレクトリと同階層で下記コマンドを実行し、Composerの※初期化を行います。
(対話形式、基本的にEnterでOK.)

composer init

composer.jsonが作成されていることを確認する。

.
└── app
    ├── composer.json
    └── src

composer.jsonと同じパスのまま、下記のコマンドでPHPUnitのインストールを実行する。
(devオプションは開発モード)

インストール完了にしばらく時間がかかります。

composer require phpunit/phpunit --dev

インストールに成功すると、composer.jsonにphpunitの情報が記載されます。

{
    "require-dev": {
        "phpunit/phpunit": "^9.5" 
    }
}

vendorディレクトリが作成され、PHPUnit本体とその他の依存ライブラリがインストールされていることも確認できます。

.
└── app
    ├── composer.json
    ├── composer.lock
    ├── src
    └── vendor
vendor/
├── autoload.php
├── bin
├── composer
├── doctrine
├── myclabs
├── nikic
├── phar-io
├── phpdocumentor
├── phpspec
├── phpunit
├── sebastian
├── symfony
├── theseer
└── webmozart
2. composer.jsonが既に存在する場合

既にプロジェクト内で他のライブラリなどでcomposerを使っている場合はcomposer.jsonにPHPUnitの情報を追記してインストールの準備を行います。

.
└── app
    ├── composer.json
    └── src

composer.json
(数字の箇所にインストールするPHPUnitのバージョンを指定可)

{
    "require-dev": {
        "phpunit/phpunit": "^9.5"
    }
}

composer.jsonのパスで下記コマンドを実行し、PHPUnitをインストールする

composer install —-dev

インストールに成功するとcomposer.jsonから新規作成した時と同様、vendorディレクトリとPHPUnit本体、その他ライブラリが作成されます。

PHPUnitを実行する (SAMPLE)

PHPUnitの導入が完了したら簡単なテスト対象用プログラムと、ユニットテストの作成、実行を試してみます。

テスト対象プログラムの作成

前述のプロジェクト構成、src配下にクラス Sampleを作成

Sample.php

<?php

namespace app\src;

class Sample 
{
    public function hello() 
    {
        return "Hello";
    }
}

ユニットテストの対象として、Helloという文字列を返すだけのhelloメソッドを実装しています。

テストケースの作成

作成したクラス(とメソッド)に対してテストケースを作成していきます。
PHPUnitでは基本的にひとつのクラスに対して、テストケース専用のクラスを対になるように作成していきます。

今回はプロジェクト内、src配下と同じ階層にtestディレクトリを作成し、その中にテストケースクラスを設置します。

.
└── app
    ├── composer.json
    ├── src
    │   └── Sample.php
    ├── test
    │   └── SampleTest.php
    └── vendor

テストケースクラスは慣習的に、

【テスト対象クラス名】Test.php

という形式で作成します。

SampleTest.php

<?php

use PHPUnit\Framework\TestCase;
use app\src\Sample;

class SampleTest extends TestCase
{
    public function testHello()
    {
        $sample = new Sample();
        
        $result = $sample->hello();
        
        $this->assertEquals("Hello", $result);
    }
}

PHPUnit本体のTestCaseを継承し、
Sampleクラスのhelloメソッドをテストする、testHelloメソッドを実装します。

(テスト対象クラスはComposerのオートロード機能によりテストケースクラスから参照できています)

メソッド内で、PHPUnitに用意されている検証用のアサーションメソッドを呼び出すことで、テスト対象メソッドに対する期待値と実際の値が等しいことを確かめられます。

PHPUnitのアサーションメソッドには用途に応じたものが複数種類存在しますが、ここでは中でもシンプルなassertEqualsを呼び出し、helloメソッドの実行結果の戻り値が文字列Helloであることを確かめています。

assertEquals(期待値, メソッドの返り値)

ユニットテストの実行

テストケースの準備が出来たらtestディレクトリと同階層で下記コマンドを実行し、ユニットテストを実行します。

$ vendor/bin/phpunit test/SampleTest.php

実行結果

PHPUnit 9.5.4 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:00.011, Memory: 4.00 MB

OK (1 test, 1 assertion)

OK (1 test, 1 assertion) と表示され、ひとつのテストケースが問題なく実行されたことが確認出来ました。

テストの失敗

次に、テスト対象プログラムの実装が誤っていた場合にテストがきちんと失敗することを確認します。

例としてhelloメソッドの元からの実装に対し、わざと誤った変更を行ない、先ほどと同じテストケースを実行します。

Sample.php

    public function hello() 
    {
        // 誤った実装
        return "Hello World";
    }

(返り値、文字列をHelloからHello Worldへ変更)

テストを実行

$ vendor/bin/phpunit test/SampleTest.php

実行結果

PHPUnit 9.5.4 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 00:00.015, Memory: 4.00 MB

There was 1 failure:

1) SampleTest::testHello
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'Hello'
+'Hello World'

/Users/ken/Sites/phpunit-lesson/app/test/SampleTest.php:16

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

最初のテスト実行時からhelloメソッドの実装を変えたので、結果にFAILURES!と表示され、期待通りテストの失敗を起こすことができました。

実行結果詳細より

Expected (期待する値)
Actual (実際の値)

それぞれ、

-‘Hello’
+’Hello World’

とHelloという期待値に対して、実際にはHello Worldという実行結果になっていたということが確認出来ます。

実行結果詳細の先頭のFの1文字はPHPのエラーではなく、Failを表しており、テストの失敗を意味しているようです。

テストケース作成の注意点

正しいのは Hello か? それとも Hello World か?

先ほどの失敗例の場合はわざと誤った実装に変更していたので、helloメソッドの実装を元に戻すことでテストケースを成功させることが可能になりますが、
通常は実際の値と期待値が異なっていた場合は、仕様をきちんと確認して、プログラムの実行値とテストケースの期待値のどちらが正しいか、どちらを修正すべきかを検討する必要があります。

当然、テスト対象プログラムの実装が誤っていた場合でもそれに対するテストの期待値もそれに合わせていたときは、テスト実行時に不正を見抜くことが出来ませんのでテストケース作成時には注意が必要です。


参考
PHPUnit 公式日本語ドキュメント

Follow me!