付録A アノテーション

アノテーションとはメタデータを表す特別な構文のことで、 プログラミング言語のソースコードに追加することができます。 PHP そのものにはソースコードにアノテーションする専用の仕組みはありませんが、 ドキュメンテーションブロックに @アノテーション名 引数 のようなタグを書くことでアノテーションを表すという記法が PHP コミュニティ内で一般に使われています。 PHP では、リフレクション API の getDocComment() メソッドを使えば関数、クラス、メソッド、属性 それぞれのドキュメンテーションブロックにアクセスすることができます。 PHPUnit などのアプリケーションでは、 この情報をもとに実行時の振る舞いを設定するのです。

本章では、PHPUnit がサポートするすべてのアノテーションについて解説します。

@author

@author アノテーションは @group アノテーション (「@group」 を参照ください) のエイリアスで、 テストの作者にもとづいたフィルタリングができるようになります。

@after

@after アノテーションを使うと、 テストケースクラス内の各テストメソッドを実行した後に呼ぶメソッドを指定できます。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @after
     */
    public function tearDownSomeFixtures()
    {
        // ...
    }

    /**
     * @after
     */
    public function tearDownSomeOtherFixtures()
    {
        // ...
    }
}

@afterClass

@afterClass アノテーションを使うと、 テストケースクラス内の各テストメソッドを実行した後に呼ぶ静的メソッドを指定できます。 ここで共有フィクスチャの後始末をします。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @afterClass
     */
    public static function tearDownSomeSharedFixtures()
    {
        // ...
    }

    /**
     * @afterClass
     */
    public static function tearDownSomeOtherSharedFixtures()
    {
        // ...
    }
}

@backupGlobals

グローバル変数の保存や復元を、テストケースクラスのすべてのテストで完全に無効にすることができます。 このように使います。

/**
 * @backupGlobals disabled
 */
class MyTest extends PHPUnit_Framework_TestCase
{
    // ...
}

@backupGlobals アノテーションは、テストメソッドレベルで使うこともできます。 これによって、保存と復元の操作をより細やかに制御できるようになります。

/**
 * @backupGlobals disabled
 */
class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @backupGlobals enabled
     */
    public function testThatInteractsWithGlobalVariables()
    {
        // ...
    }
}

@backupStaticAttributes

クラスの静的属性の保存や復元を、テストケースクラスのすべてのテストで完全に無効にすることができます。 このように使います。

/**
 * @backupStaticAttributes disabled
 */
class MyTest extends PHPUnit_Framework_TestCase
{
    // ...
}

@backupStaticAttributes アノテーションは、テストメソッドレベルで使うこともできます。 これによって、保存と復元の操作をより細やかに制御できるようになります。

/**
 * @backupStaticAttributes disabled
 */
class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @backupStaticAttributes enabled
     */
    public function testThatInteractsWithStaticAttributes()
    {
        // ...
    }
}

@before

@before アノテーションを使うと、 テストケースクラス内の各テストメソッドを実行する前に呼ぶメソッドを指定できます。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @before
     */
    public function setupSomeFixtures()
    {
        // ...
    }

    /**
     * @before
     */
    public function setupSomeOtherFixtures()
    {
        // ...
    }
}

@beforeClass

@beforeClass アノテーションを使うと、 テストケースクラス内の各テストメソッドを実行する前に呼ぶ静的メソッドを指定できます。 ここで共有フィクスチャの準備をします。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @beforeClass
     */
    public static function setUpSomeSharedFixtures()
    {
        // ...
    }

    /**
     * @beforeClass
     */
    public static function setUpSomeOtherSharedFixtures()
    {
        // ...
    }
}

@codeCoverageIgnore*

@codeCoverageIgnore@codeCoverageIgnoreStart、そして @codeCoverageIgnoreEnd アノテーションを使うと、 コード内の特定の行をカバレッジ解析の対象外にできます。

利用法は 「コードブロックの無視」 を参照ください。

@covers

@covers アノテーションをテストコードで使うと、 そのテストメソッドがどのメソッドをテストするのかを指定することができます。

/**
 * @covers BankAccount::getBalance
 */
public function testBalanceIsInitiallyZero()
{
    $this->assertEquals(0, $this->ba->getBalance());
}

これを指定した場合は、指定したメソッドのみのコードカバレッジ情報を考慮することになります。

表 A.1@covers アノテーションの構文を示します。

表A.1 カバーするメソッドを指定するためのアノテーション

アノテーション説明
@covers ClassName::methodNameそのテストメソッドが指定したメソッドをカバーすることを表します。
@covers ClassNameそのテストメソッドが指定したクラスのすべてのメソッドをカバーすることを表します。
@covers ClassName<extended>そのテストメソッドが、指定したクラスとその親クラスおよびインターフェイスのすべてのメソッドをカバーすることを表します。
@covers ClassName::<public>そのテストメソッドが、指定したクラスのすべての public メソッドをカバーすることを表します。
@covers ClassName::<protected>そのテストメソッドが、指定したクラスのすべての protected メソッドをカバーすることを表します。
@covers ClassName::<private>そのテストメソッドが、指定したクラスのすべての private メソッドをカバーすることを表します。
@covers ClassName::<!public>そのテストメソッドが、指定したクラスのすべての非 public メソッドをカバーすることを表します。
@covers ClassName::<!protected>そのテストメソッドが、指定したクラスのすべての非 protected メソッドをカバーすることを表します。
@covers ClassName::<!private>そのテストメソッドが、指定したクラスのすべての非 private メソッドをカバーすることを表します。
@covers ::functionNameそのテストメソッドが、指定したグローバル関数をカバーすることを表します。


@coversDefaultClass

@coversDefaultClass アノテーションを使うと、 デフォルトの名前空間あるいはクラス名を指定できます。 こうすることで、 @covers アノテーションのたびに長い名前を繰り返す必要がなくなります。 例 A.1 を参照ください。

例 A.1: @coversDefaultClass を使ったアノテーションの短縮

<?php
/**
 * @coversDefaultClass \Foo\CoveredClass
 */
class CoversDefaultClassTest extends PHPUnit_Framework_TestCase
{
    /**
     * @covers ::publicMethod
     */
    public function testSomething()
    {
        $o = new Foo\CoveredClass;
        $o->publicMethod();
    }
}
?>


@coversNothing

@coversNothing アノテーションをテストコードで使うと、 そのテストケースについてはコードカバレッジ情報を記録しないように指定できます。

これはインテグレーションテストで使えます。例として 例 11.3 を参照ください。

このメソッドはクラスレベルおよびメソッドレベルで使え、 あらゆる @covers タグを上書きします。

@dataProvider

テストメソッドには任意の引数を渡すことができます。 引数は、データプロバイダメソッド (例 2.5provider()) から渡されます。 使用するデータプロバイダメソッドを指定するには @dataProvider アノテーションを使います。

詳細は 「データプロバイダ」 を参照ください。

@depends

PHPUnit は、テストメソッド間の依存性の明示的な宣言をサポートしています。 この依存性とは、テストメソッドが実行される順序を定義するものではありません。 プロデューサーがテストフィクスチャを作ってそのインスタンスを返し、 依存するコンシューマーがそれを受け取って利用するというものです。 例 2.2 は、@depends アノテーションを使ってテストメソッドの依存性をあらわす例です。

詳細は 「テストの依存性」 を参照ください。

@expectedException

例 2.9 は、テストするコード内で例外がスローされたかどうかを @expectedException アノテーションを使用して調べる方法を示すものです。

詳細は 「例外のテスト」 を参照ください。

@expectedExceptionCode

@expectedExceptionCode アノテーションを @expectedException と組み合わせて使うと、 スローされた例外のエラーコードについてのアサーションが可能となり、 例外をより狭い範囲に特定できるようになります。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException     MyException
     * @expectedExceptionCode 20
     */
    public function testExceptionHasErrorcode20()
    {
        throw new MyException('Some Message', 20);
    }
}

テストを実行しやすくし、重複を減らすために、 ショートカットを使ってクラス定数を指定することができます。 @expectedExceptionCode で "@expectedExceptionCode ClassName::CONST" のようにして使います。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
      * @expectedException     MyException
      * @expectedExceptionCode MyClass::ERRORCODE
      */
    public function testExceptionHasErrorcode20()
    {
      throw new MyException('Some Message', 20);
    }
}
class MyClass
{
    const ERRORCODE = 20;
}

@expectedExceptionMessage

@expectedExceptionMessage アノテーションは @expectedExceptionCode と似ており、 例外のエラーメッセージに関するアサーションを行います。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException        MyException
     * @expectedExceptionMessage Some Message
     */
    public function testExceptionHasRightMessage()
    {
        throw new MyException('Some Message', 20);
    }
}

期待するメッセージを、例外メッセージの一部にすることもできます。 これは、特定の名前や渡したパラメータが例外に表示されることを確かめたいけれども 例外メッセージ全体は固定していない場合に便利です。

class MyTest extends PHPUnit_Framework_TestCase
{
     /**
      * @expectedException        MyException
      * @expectedExceptionMessage broken
      */
     public function testExceptionHasRightMessage()
     {
         $param = "broken";
         throw new MyException('Invalid parameter "'.$param.'".', 20);
     }
}

テストを実行しやすくし、重複を減らすために、 ショートカットを使ってクラス定数を指定することができます。 @expectedExceptionMessage で "@expectedExceptionMessage ClassName::CONST" のようにして使います。 サンプルコードは 「@expectedExceptionCode」 を参照ください。

@group

あるテストを、ひとつあるいは複数のグループに属するものとすることができます。 @group アノテーションをこのように使用します。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @group specification
     */
    public function testSomething()
    {
    }

    /**
     * @group regresssion
     * @group bug2204
     */
    public function testSomethingElse()
    {
    }
}

特定のグループに属するテストのみを選んで実行するには、 コマンドラインのテストランナーの場合は --group スイッチあるいは --exclude-group スイッチを指定します。XML 設定ファイルの場合は、 それぞれ対応するディレクティブを指定します。

@large

@large アノテーションは、 @group large のエイリアスです。

PHP_Invoker パッケージがインストールされていて strict モードが有効な場合に、 large テストは実行時間が 60 秒を超えたら失敗します。 このタイムアウト時間は、XML 設定ファイルの timeoutForLargeTests 属性で変更できます。

@medium

@medium アノテーションは @group medium のエイリアスです。 medium テストは、@large とマークしたテストに依存してはいけません。

PHP_Invoker パッケージがインストールされていて strict モードが有効な場合に、 medium テストは実行時間が 10 秒を超えたら失敗します。 このタイムアウト時間は、XML 設定ファイルの timeoutForMediumTests 属性で変更できます。

@preserveGlobalState

テストを別プロセスで実行するときに、 PHPUnit は親プロセスのグローバルな状態を保存しようと試みます。 親プロセスのすべてのグローバル状態をシリアライズし、 子プロセス内で最後にそれをアンシリアライズするのです。 しかし、親プロセスのグローバル状態の中にもしシリアライズできないものがあれば、 問題が発生します。この問題に対応するために、グローバル状態の保存を無効にすることができます。 そのために使うのが @preserveGlobalState アノテーションです。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @runInSeparateProcess
     * @preserveGlobalState disabled
     */
    public function testInSeparateProcess()
    {
        // ...
    }
}

@requires

@requires アノテーションを使うと、共通の事前条件 (たとえば PHP のバージョンや拡張モジュールのインストール状況) を満たさないときにテストをスキップできます。

条件に指定できる内容やその例については 表 7.3 を参照ください。

@runTestsInSeparateProcesses

テストクラス内のすべてのテストケースを、個別の PHP プロセスで実行するように指示します。

/**
 * @runTestsInSeparateProcesses
 */
class MyTest extends PHPUnit_Framework_TestCase
{
    // ...
}

注意: デフォルトで、PHPUnit は親プロセスのグローバルな状態を保存しようと試みます。 親プロセスのすべてのグローバル状態をシリアライズし、 子プロセス内で最後にそれをアンシリアライズするのです。 しかし、親プロセスのグローバル状態の中にもしシリアライズできないものがあれば、 問題が発生します。この問題に対応するために、グローバル状態の保存を無効にすることができます。 この問題の対処法については 「@preserveGlobalState」 を参照ください。

@runInSeparateProcess

そのテストを個別の PHP プロセスで実行するように指示します。

class MyTest extends PHPUnit_Framework_TestCase
{
    /**
     * @runInSeparateProcess
     */
    public function testInSeparateProcess()
    {
        // ...
    }
}

注意: デフォルトで、PHPUnit は親プロセスのグローバルな状態を保存しようと試みます。 親プロセスのすべてのグローバル状態をシリアライズし、 子プロセス内で最後にそれをアンシリアライズするのです。 しかし、親プロセスのグローバル状態の中にもしシリアライズできないものがあれば、 問題が発生します。この問題に対応するために、グローバル状態の保存を無効にすることができます。 この問題の対処法については 「@preserveGlobalState」 を参照ください。

@small

@small アノテーションは @group small のエイリアスです。 small テストは、@medium@large とマークしたテストに依存してはいけません。

PHP_Invoker パッケージがインストールされていて strict モードが有効な場合に、 small テストは実行時間が 1 秒を超えたら失敗します。 このタイムアウト時間は、XML 設定ファイルの timeoutForSmallTests 属性で変更できます。

注記

デフォルトでは、@medium あるいは @large のどちらかのアノテーションがないテストはすべて small と見なされます。 しかし、--group およびそれに関連するオプションは、 明示的なアノテーションで small とマークしているテストだけを small と見なすことに注意しましょう。

@test

テストメソッド名の先頭に test をつけるかわりに、メソッドのドキュメンテーションブロックで @test アノテーションを使ってそのメソッドがテストメソッドであることを指定することができます。

/**
 * @test
 */
public function initialBalanceShouldBe0()
{
    $this->assertEquals(0, $this->ba->getBalance());
}

@testdox

@ticket

このページの改善案をGitHubで提案してください!