Help us understand the problem. What is going on with this article?

PHPUnitで始めるユニットテスト

目次

ユニットテストとは
インストール方法
簡単なテストコードの書き方と実行方法
テスト結果の確認方法
複数データのテスト
プライベートメソッドのテスト

ユニットテストとは

プログラムの小さな機能単位に対して行う動作確認のこと
ある操作を行った時に結果がどうなるかを
順々に試していく作業を自動化するためにある

インストール方法

composerはインストールされている前提で進めていきます。
インストールしてない方はインストールしてから戻ってきてください
これは推奨されていないやり方ですが、

composer global require 'phpunit/phpunit:*'
echo "export PATH=\$PATH:\$HOME/.config/composer/vendor/bin" >> ~/.bash_profile
source ~/.bash_profile

phpunit -versionで確認してみてください
推奨されているやり方は以下の通りです

wget https://phar.phpunit.de/phpunit-|version|.phar
chmod +x phpunit-|version|.phar
./phpunit-|version|.phar --version

参考
https://phpunit.readthedocs.io/ja/latest/installation.html

簡単なテストコードの書き方と実行方法

User.php
<?php
class User
{
    public $name;

    public __construct($name)
    {
        $this->name = $name;
    }

    public function sayHi(): string
    {
        //return 'Hi ' . $this->name;
        return 'Hi ' . $name;
    }
}
UserTest.php
<?php
require __DIR__ . '/User.php';
class User extends PHPUnit\Framework\TestCasee
{
    public function testConstruct()
    {
        $user = new User('Perry');
        $this->assertEquals('Perry', $user->name);
    }

    public function testSayHi()
    {

        $user = new User('Satoru');
        $test = $user->sayHi();
        $expected = 'Hi Satoru';
        $this->assertEquals($expected, $test);
    }
}

これを実行するときは
phpunit UserTest.phpとすれば良い
ディレクトリ単位でも実行することができる

実際にこれを実行すると

 PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

.E                                                                  2 / 2 (100%)

Time: 24 ms, Memory: 4.00 MB

There was 1 error:

1) UserTest::testCreateMessage
Undefined variable: name

/PATH/User.php:11
/PATH/UserTest.php:13

ERRORS!
Tests: 2, Assertions: 1, Errors: 1.

こんな感じで帰ってくる
最初の.はテスト通ったよってこと
その次のEはエラーを表している
エラーが出た場合は上のように内容が詳細に記される
最初の行ではテストメソッドの名前とエラーの個数が表示される
次の行ではエラーの内容
次に該当箇所が表示され、その次の行で、UserTest.phpの13行目を実行した時に
エラーが発生したと丁寧に書いてくれています

それではコードを修正しましょう

User.php
<?php
class User
{
    public $name;

    public __construct($name)
    {
        $this->name = $name;
    }

    public function sayHi(): string
    {
        return 'Hi ' . $this->name;
    }
}

これで実行してみましょう

..                                                                  2 / 2 (100%)

Time: 22 ms, Memory: 4.00 MB

OK (2 tests, 2 assertions)

いい感じですね!
ちなみに文字リテラルが間違っていた場合はEではなく
Fが出力されます
スキップされた際はSらしいですみたことないけど!

このようにテスト実行->失敗した場合は修正->テストを繰り返して進めていきます
今回はassertEqualしか使わなかったですが、他にもたくさんあるので調べてみてください
分かりやすい記事があったので説明は省略します
https://qiita.com/rev84/items/12fbd16d210d6a86eff9
https://www.wakuwakubank.com/posts/425-php-php-unit/

複数データのテスト

データプロバイダを使って複数のデータをまとめてテストしましょう

Calc.php
<?php
class Calc
{
    private $number1;
    private $number2;

    public function __construct(int $number1, int $number2)
    {
        $this->number1 = $number1;
        $this->number2 = $number2;
    }

    public function calc(string $option = '+'): int
    {
        switch ($option) {
        case '+':
            $answer = $this->number1 + $this->number2;
            break;
        case '-':
            $answer = $this->number1 - $this->number2;
            break;
        case '*':
            $answer = $this->number1 * $this->number2;
            break;
        case '/':
            if ($this->number2 == 0) return false;
            $answer = $this->number1 / $this->number2;
            break;
        default:
            return false;
            }
            return $answer;
    }
}
CalcTest.php
<?php
require_once __DIR__ . '/Calc.php';
class CalcTest extends PHPUnit\Framework\TestCase
{
    /**
     * @dataProvider numberProvider
     */
    public function testCalc(int $number1, int $number2, string $option, int $expected)
    {
        $calc = new Calc($number1, $number2);
        $test = $calc->calc($option);
        $this->assertEquals($expected, $test);
    }

    public function numberProvider()
    {
        return [
            [1, 2, '+', 3],
            [1, 2, '-', 3],
            [1, 2, '*', 3],
            [1, 2, '/', 3],
            [3, 4, '+', 12],
            [3, 4, '-', 12],
            [3, 4, '*', 12],
            [3, 4, '/', 12],
            [7, 8, '+', 15],
            [7, 8, '-', 15],
            [7, 8, '*', 15],
            [7, 8, '/', 15],
        ];
    }
}

実行方法は先ほどのコマンドに--debugオプションをつけるだけー

実行結果は以下のようになりました

.FFFFF.F.FFF                                                      12 / 12 (100%)

Time: 24 ms, Memory: 4.00 MB

There were 9 failures:

1) CalcTest::testCalc with data set #1 (1, 2, '-', 3)
Failed asserting that -1 matches expected 3.

/PATH/CalcTest.php:12

2) CalcTest::testCalc with data set #2 (1, 2, '*', 3)
Failed asserting that 2 matches expected 3.

/PATH/CalcTest.php:12

3) CalcTest::testCalc with data set #3 (1, 2, '/', 3)
Failed asserting that 0 matches expected 3.

/PATH/CalcTest.php:12

4) CalcTest::testCalc with data set #4 (3, 4, '+', 12)
Failed asserting that 7 matches expected 12.

/PATH/CalcTest.php:12

5) CalcTest::testCalc with data set #5 (3, 4, '-', 12)
Failed asserting that -1 matches expected 12.

/PATH/CalcTest.php:12

6) CalcTest::testCalc with data set #7 (3, 4, '/', 12)
Failed asserting that 0 matches expected 12.

/PATH/CalcTest.php:12

7) CalcTest::testCalc with data set #9 (7, 8, '-', 15)
Failed asserting that -1 matches expected 15.

/PATH/CalcTest.php:12

8) CalcTest::testCalc with data set #10 (7, 8, '*', 15)
Failed asserting that 56 matches expected 15.

/PATH/CalcTest.php:12

9) CalcTest::testCalc with data set #11 (7, 8, '/', 15)
Failed asserting that 0 matches expected 15.

/PATH/CalcTest.php:12

FAILURES!
Tests: 12, Assertions: 12, Failures: 9.

データセットごとにtestCalc()メソッドが実行されていますね!
テストメソッドであるtestCalc()メソッドの直前に@dataProvider numberProviderと記述しましたが
これがデータプロバイダとしてメソッドを使うための指示になっているので書き忘れ厳禁

データプロバイダメソッドのnumberProvider()メソッドはデータ配列かIteratorインターフェイスを実装したオブジェクトを返すように記述してください

フィクスチャの話
テストを実行するための事前準備をフィクスチャと呼ぶ
PHPUnitには準備用のコードを共有するために
各テストメソッドが実行される前に自動的に実行されるsetUp()メソッドや
各テストメソッドが実行された後に自動的に実行されるtearDown()メソッドがある

プライベートメソッドのテスト

ReflectionMethodクラスを使う
テストケースを記述するクラスはテストするクラスとは別なので
プライベートメソッドをそのままテストすることができない
そこでReflectorMethodクラスを使うと外部からアクセス可能なように書き換えてテストしてくれるらしい
めっちゃ便利

$r = new ReflectionMethod('テスト対象のクラス', 'テスト対象のメソッド');
$r->setAccessible(true);
$test = $r->invoke('テスト対象のクラスのインスタンス');

こんな感じ
ReflectionMethodのインスタンスを作成してメソッドのアクセス権を書き換えて
テスト対象のメソッドを呼び出して結果を取得してるー

CalcTest.php
//@dataProvider numberProvider
public function testCalc()
{
    $r = new ReflectionMethod('Calc', 'calc');
    $r->setAccessible(true);
    $test = $r->invoke(new Calc(1, 2));
    $expected = 3;
    $this->assertEquals($expected, $test);
}

実行結果はこんな感じー

.                                                                  1 / 1 (100%)
Time: 22 ms, Memory: 4.00 MB
OK (1 test, 1 assertion)

データベースのテスト方法は長くなるのでまた次の機会にまとめられたらなと思いますー
テストのカバー率やSeleniumを使ってブラウザテストもできるみたいなので
それも合わせてまとめられたらなと思ってますー

satorunooshie
経済学を学んでいる大学三年生です PHP, Kotlinを中心に学習しています 役に立てそうな英語の記事や詰まったところを共有していきます
https://satorunooshie.net
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away