技術メモなど

業務や日々のプログラミングのなかで気になったことをメモしています。PHP 成分多め。

PHPUnit で例外・トレイトのテスト

PHPUnit の例外・トレイトのテスト方法についてまとめたのでメモ。

環境情報

例外をテストする

例外をスローするメソッドをテストしたい。

<?php
class Sample
{
    const ERROR_CODE_1 = 10;
    public function foo($int)
    {
        if ($bool !== 1) {
            throw new ErrorException('例外のテスト', self::ERROR_CODE_1);
        }
        return $int;
    }
}

例外がスローされたかをテストする方法には以下の2つがある。

@expectedException アノテーションは後ろに検査したい例外クラスを指定する。 expectedException() メソッドは引数に検査したい例外クラスを指定する。

<?php
class SampleTest extends TestCase
{ 
    /**
     * @expectedException ErrorException
     */
    public function testSample1()
    {
        $sample = new Sample();

        $sample->foo(true);
    }

    public function testSample2()
    {
        $this->expectException(ErrorException::class);
        
        $sample = new Sample();
        
        $sample->foo(true);
    }
}

どちらも例外クラスを検査するものであるが、エラーメッセージやエラーコードまでは検証できない。

エラーメッセージを検証するには
@expectedExceptionMessage expectedExceptionMessage()
エラーコードを検証するには
@expectedExceptionCode expectedExceptionCode() を使う。

<?php
class SampleTest extends TestCase
{ 
    /**
     * @expectedException ErrorException
     * @expectedExceptionMessage 例外のテスト
     * @expectedExceptionCode Sample::ERROR_CODE_1
     */
    public function testSample1()
    {
        $sample = new Sample();
        
        $sample->foo(true);
    }

    public function testSample2()
    {
        $this->expectException(ErrorException::class);
        $this->expectExceptionMessage('例外のテスト');
        $this->expectExceptionCode(Sample::ERROR_CODE_1);
        
        $sample = new Sample();
        
        $sample->foo(true);
    }
}

trait をテストする

trait をテストしたい。

<?php
trait SampleTrait {
    public function foo ()
    {
        return 'foo and ' . $this->bar();
    } 

    abstract function bar();
}

trait は単独でインスタンス化することができないため、通常のクラスとは異なる手順が必要になる。 テストするには以下の方法がある。

  • テストクラスに直接 use する
  • 無名クラスを使う
  • getMockForTrait() を使う
<?php
class SampleTest extends TestCase
{
    // テストクラスに直接 use
    use SampleTrait;

    public function testSample1()
    {
        $actual = $this->foo();

        $this->assertEquals('foo and bar', $actual);
    }

    // テストクラスに直接 use する場合、抽象クラスも直接定義しなければならない。
    public function bar()
    {
        return 'bar';
    }

    // 無名クラスを使う
    public function testSample2()
    {
        $trait = new class{
            use SampleTrait;
            public function bar(){ return 'bar'; }
        };

        $actual = $trait->foo();

        $this->assertEquals('foo and bar', $actual);
    }
    // getMockForTrait()
    public function testSample3()
    {
        $mockTrait = $this->getMockForTrait(SampleTrait::class);
        $mockTrait->expects($this->any())
                ->method('bar')
                ->will($this->returnValue('bar'));

        $actual = $mockTrait->foo();

        $this->assertEquals('foo and bar', $actual);
    }
}

テストクラスに直接 use する方法はシンプルではあるが、trait がプロパティや抽象メソッドを定義している場合には余計なメソッドをテストクラスに生やさなければならなくなる。 具象メソッドについても テストクラス のメソッドやプロパティの衝突もあり得るのが怖い。

PHP7 であれば無名クラスが使える。テストクラス に依存しなくてよくなるため、PHP7であればこちらの方がよい。

getMockForTrait()メソッドは引数に渡した trait のモックオブジェクトを返却する。抽象メソッドをモックすることで具象メソッドをテストできるようになる。

参考文献

2. PHPUnit 用のテストの書き方 — PHPUnit latest Manual

8. テストダブル — PHPUnit latest Manual