Prev Next

第4章 PHPUnit 用のテストの書き方

例 4.1 で、 PHP の配列操作のテストを PHPUnit 用に書く方法を示します。 この例では、PHPUnit を使ったテストを書く際の基本的な決まり事や手順を紹介します。

  1. Class という名前のクラスのテストは、ClassTest という名前のクラスに記述します。

  2. ClassTest は、(ほとんどの場合) PHPUnit_Framework_TestCase を継承します。

  3. テストは、test* という名前のパブリックメソッドとなります。

    あるいは、@test アノテーションをメソッドのコメント部で使用することで、それがテストメソッドであることを示すこともできます。

  4. テストメソッドの中で assertEquals() のようなアサーションメソッド (「PHPUnit_Framework_Assert」 を参照ください) を使用して、期待される値と実際の値が等しいことを確かめます。

例 4.1: PHPUnit での配列操作のテスト

<?php
require_once 'PHPUnit/Framework.php';

class StackTest extends PHPUnit_Framework_TestCase
{
public function testPushAndPop()
{
$stack = array();
$this->assertEquals(0, count($stack));

array_push($stack, 'foo');
$this->assertEquals('foo', $stack[count($stack)-1]);
$this->assertEquals(1, count($stack));

$this->assertEquals('foo', array_pop($stack));
$this->assertEquals(0, count($stack));
}
}
?>

 

Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.

何かを print 文やデバッガの式に書きたくなったときは、 代わりにその内容をテストに書くようにするんだ。

 
  --Martin Fowler

テストの依存性

 

Unit Tests are primarily written as a good practice to help developers identify and fix bugs, to refactor code and to serve as documentation for a unit of software under test. To achieve these benefits, unit tests ideally should cover all the possible paths in a program. One unit test usually covers one specific path in one function or method. However a test method is not necessary an encapsulated, independent entity. Often there are implicit dependencies between test methods, hidden in the implementation scenario of a test.

ユニットテストを書くそもそもの目的は、バグを発見と修正や コードのリファクタリングを開発者がやりやすくすること。 そしてテスト対象のソフトウェアのドキュメントとしての役割を果たすことだ。 これらの目的を達成するためには、 ユニットテストがプログラム内のすべてのルートをカバーしていることが理想である。 ひとつのユニットテストがカバーするのは、 通常はひとつの関数やメソッド内の特定のルートだけとなる。 しかし、テストメソッドは必ずしもカプセル化して独立させる必要はない。 複数のテストメソッドの間に暗黙の依存性があって、 隠された実装シナリオがテストの中にあるのもよくあることだ。

 
  --Adrian Kuhn et. al.

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

  • プロデューサーとは、返り値としてテスト対象のユニットを生成するテストメソッドのこと。

  • コンシューマーとは、プロデューサーの返り値に依存するテストメソッドのこと。

例 4.2 は、@depends アノテーションを使ってテストメソッドの依存性をあらわす例です。

例 4.2: @depends アノテーションを使った依存性の表現

<?php
class StackTest extends PHPUnit_Framework_TestCase
{
public function testEmpty()
{
$stack = array();
$this->assertTrue(empty($stack));

return $stack;
}

/**
* @depends testEmpty
*/
public function testPush(array $stack)
{
array_push($stack, 'foo');
$this->assertEquals('foo', $stack[count($stack)-1]);
$this->assertFalse(empty($stack));

return $stack;
}

/**
* @depends testPush
*/
public function testPop(array $stack)
{
$this->assertEquals('foo', array_pop($stack));
$this->assertTrue(empty($stack));
}
}
?>

上の例では、まず最初のテスト testEmpty() で新しい配列を作り、それが空であることを確かめます。 このテストは、フィクスチャを返します。 二番目のテスト testPush()testEmpty() に依存しており、 依存するテストの結果を引数として受け取ります。 最後の testPop()testPush() に依存しています。

問題の局所化を手早く行うには、失敗したテストに目を向けやすくしたいものです。 そのため PHPUnit では、 あるテストが失敗したときにはそのテストに依存する他のテストの実行をスキップします。 テスト間の依存性を活用して問題点を見つけやすくしている例を 例 4.3 に示します。

例 4.3: テストの依存性の活用

<?php
class DependencyFailureTest extends PHPUnit_Framework_TestCase
{
public function testOne()
{
$this->assertTrue(FALSE);
}

/**
* @depends testOne
*/
public function testTwo()
{
}
}
?>
phpunit --verbose DependencyFailureTest
PHPUnit 3.4.14 by Sebastian Bergmann.

DependencyFailureTest
FS

Time: 0 seconds

There was 1 failure:

1) testOne(DependencyFailureTest)
Failed asserting that <boolean:false> is true.
/home/sb/DependencyFailureTest.php:6

There was 1 skipped test:

1) testTwo(DependencyFailureTest)
This test depends on "DependencyFailureTest::testOne" to pass.

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

ひとつのテストに複数の @depends アノテーションをつけることもできます。 PHPUnit はテストが実行される順序を変更しないので、 テストが実行されるときに確実に依存性が満たされているようにしておく必要があります。

データプロバイダ

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

データプロバイダメソッドは、public でなければなりません。また、 メソッドの返り値の型は、配列の配列あるいはオブジェクト (Iterator インターフェイスを実装しており、 反復処理の際に配列を返すもの) である必要があります。 この返り値の各要素に対して、その配列の中身を引数としてテストメソッドがコールされます。

例 4.4: データプロバイダの使用

<?php
class DataTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider provider
*/
public function testAdd($a, $b, $c)
{
$this->assertEquals($c, $a + $b);
}

public function provider()
{
return array(
array(0, 0, 0),
array(0, 1, 1),
array(1, 0, 1),
array(1, 1, 3)
);
}
}
?>
phpunit DataTest
PHPUnit 3.4.14 by Sebastian Bergmann.

...F

Time: 0 seconds

There was 1 failure:

1) testAdd(DataTest) with data (1, 1, 3)
Failed asserting that <integer:2> matches expected value <integer:3>.
/home/sb/DataTest.php:21

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

注記

@dataProvider で指定したメソッドと @depends で指定したテストの両方からの入力を受け取るテストの場合、 データプロバイダからの引数のほうが依存するテストからの引数より先にきます。

注記

あるテストがデータプロバイダを使う別のテストに依存している場合、 別のテストで少なくともひとつのデータセットに対するテストが成功すれば そのテストも実行されます。 データプロバイダを使ったテストの結果をそのテストに注入することはできません。

例外のテスト

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

例 4.5: @expectedException アノテーションの使用法

<?php
require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase
{
/**
* @expectedException InvalidArgumentException
*/
public function testException()
{
}
}
?>
phpunit ExceptionTest
PHPUnit 3.4.14 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) testException(ExceptionTest)
Expected exception InvalidArgumentException

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

一方、setExpectedException() メソッドを使用して、発生するであろう例外を指定することもできます。この方法を 例 4.6 に示します。

例 4.6: テスト対象のコードで発生するであろう例外の指定

<?php
require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase
{
public function testException()
{
$this->setExpectedException('InvalidArgumentException');
}
}
?>
phpunit ExceptionTest
PHPUnit 3.4.14 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) testException(ExceptionTest)
Expected exception InvalidArgumentException

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

表 4.1 は、例外をテストするために用意されているメソッドをまとめたものです。

表4.1 例外のテスト用のメソッド

メソッド 意味
void setExpectedException(string $exceptionName) 発生することを期待する例外の名前を $exceptionName に設定します。
String getExpectedException() 発生することを期待する例外の名前を返します。

一方、 例 4.7 のような方法で例外をテストすることもできます。

例 4.7: 例外をテストするための、別の方法

<?php
require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase {
public function testException() {
try {
// ... 例外が発生するであろうコード ...
}

catch (InvalidArgumentException $expected) {
return;
}

$this->fail('期待通りの例外が発生しませんでした。');
}
}
?>

例外が発生するはずの 例 4.7 のコードで例外が発生しなかった場合、それに続く fail() (表 22.2 を参照ください) によってテストが終了し、問題を報告します。期待通りに例外が発生すると、 catch ブロックが実行されてテストは正常終了します。

PHP のエラーのテスト

デフォルトでは、PHPUnit はテストの実行中に発生した PHP のエラーや警告そして notice を例外に変換します。これらの例外を用いて、たとえば 例 4.8 のように PHP のエラーが発生することをテストできます。

例 4.8: @expectedException を用いた、PHP エラーが発生することのテスト

<?php
class ExpectedErrorTest extends PHPUnit_Framework_TestCase
{
/**
* @expectedException PHPUnit_Framework_Error
*/
public function testFailingInclude()
{
include 'not_existing_file.php';
}
}
?>
phpunit ExpectedErrorTest
PHPUnit 3.4.14 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

PHPUnit_Framework_Error_Notice および PHPUnit_Framework_Error_Warning は、 それぞれ PHP の notice と警告に対応します。

Prev Next
1. 自動テスト
2. PHPUnit の目標
3. PHPUnit のインストール
4. PHPUnit 用のテストの書き方
テストの依存性
データプロバイダ
例外のテスト
PHP のエラーのテスト
5. コマンドラインのテストランナー
6. Fixtures
tearDown() よりも setUp()
バリエーション
Fixture の共有
グローバルな状態
7. テストの構成
ファイルシステムを用いたテストスイートの構成
XML 設定ファイルを用いたテストスイートの構成
テストケースクラスの使用法
8. テストケースの拡張
出力内容のテスト
9. データベースのテスト
データセット
Flat XML データセット
XML データセット
CSV Data Set
データセットの交換
操作
データベースのテストのコツ
10. 不完全なテスト・テストの省略
不完全なテスト
テストの省略
11. テストダブル
スタブ
モックオブジェクト
ウェブサービスのスタブおよびモック
ファイルシステムのモック
12. テストの進め方
開発中のテスト
デバッグ中のテスト
13. テスト駆動開発
銀行口座の例
14. 振舞駆動開発
ボウリングゲームの例
15. コードカバレッジ解析
カバーするメソッドの指定
コードブロックの無視
ファイルのインクルードや除外
16. テストのその他の使用法
アジャイルな文書作成
複数チームでのテスト
17. 雛形ジェネレータ
テストケースクラスの雛形の作成
テストケースクラスからのクラスの雛形の作成
18. PHPUnit と Selenium
Selenium RC
PHPUnit_Extensions_SeleniumTestCase
19. ログ出力
テスト結果 (XML)
テスト結果 (TAP)
テスト結果 (JSON)
コードカバレッジ (XML)
20. ビルドの自動化
Apache Ant
Apache Maven
Phing
21. 継続的インテグレーション
Atlassian Bamboo
CruiseControl
phpUnderControl
22. PHPUnit API
概要
PHPUnit_Framework_Assert
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
PHPUnit_Framework_Test
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestSuite
PHPUnit_Framework_TestResult
パッケージの構成
23. PHPUnit の拡張
PHPUnit_Framework_TestCase のサブクラスの作成
アサートクラスの作成
PHPUnit_Extensions_TestDecorator のサブクラスの作成
PHPUnit_Framework_Test の実装
PHPUnit_Framework_TestResult のサブクラスの作成
PHPUnit_Framework_TestListener の実装
新しいテストランナーの作成
A. アサーション
B. アノテーション
@assert
@backupGlobals
@backupStaticAttributes
@covers
@dataProvider
@depends
@expectedException
@group
@outputBuffering
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. XML 設定ファイル
PHPUnit
テストスイート
グループ
コードカバレッジ対象のファイルの追加や除外
ログ出力
テストリスナー
PHP INI 項目や定数、グローバル変数の設定
Selenium RC の設定ブラウザ
D. 目次
E. 参考文献
F. 著作権