製作著作 © 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 Sebastian Bergmann
PHPUnit 6.5 対応版 Updated on 2017-12-07.
PHPUnit 6.5 は PHP 7 以降のバージョンで動作しますが、最新版の PHP を使うことを強く推奨します。
PHPUnit を使うには、拡張モジュール dom、json、 が必要です。これらは通常、デフォルトで有効になっています。
PHPUnit また、拡張モジュール pcre、 reflection、 そして spl も必要です。これらは標準の拡張モジュールとしてデフォルトで有効になっており、 PHP のビルドシステムやソースファイルに手を加えない限り、 無効にすることはできません。
コードカバレッジをサポートするには Xdebug 2.5.0 以降と tokenizer 拡張モジュールが必要です。 XML 形式で情報を出力するには、xmlwriter 拡張モジュールも必要です。
PHPUnit を入手する一番簡単な方法は、PHP Archive (PHAR) をダウンロードすることです。 必要な依存コンポーネントがすべて (オプションのコンポーネントの一部も含めて) ひとつのファイルにまとめられています。
PHP Archives (PHAR) を利用するには、 phar 拡張モジュールが必要です。
PHAR の --self-update
機能を使うには、
openssl
拡張モジュールが必要です。
Suhosin 拡張モジュールが有効になっている場合は、
php.ini
で PHAR の実行を許可する必要があります。
suhosin.executor.include.whitelist = phar
PHAR をグローバルにインストールするには、次のようにします。
$
wget http://phar.phpunit.cn/phpunit-6.5.phar
$
chmod +x phpunit-6.5.phar
$
sudo mv phpunit-6.5.phar /usr/local/bin/phpunit
$
phpunit --version
PHPUnit x.y.z by Sebastian Bergmann and contributors.
ダウンロードした PHAR ファイルを直接使ってもかまいません。
$
wget http://phar.phpunit.cn/phpunit-6.5.phar
$
php phpunit-6.5.phar --version
PHPUnit x.y.z by Sebastian Bergmann and contributors.
PHAR をグローバルにインストールする方法は、 Composer を Windows に手動でインストールする のと同じ手順です。
PHP バイナリ用のディレクトリを作ります(例:C:\bin
)
;C:\bin
を、環境変数 PATH
に追記します
(参考資料)。
http://phar.phpunit.cn/phpunit-6.5.phar をダウンロードして、
C:\bin\phpunit.phar
に保存します。
コマンドプロンプトを開きます (
Windows+R
» cmd
» ENTER)。
以下のようにして、バッチスクリプト
(C:\bin\phpunit.cmd
)
を作ります。
C:\Users\username>
cd C:\bin
C:\bin>
echo @php "%~dp0phpunit.phar" %* > phpunit.cmd
C:\bin>
exit
コマンドプロンプトをもう一枚開き、どこからでも PHPUnit を実行できることを確認します。
C:\Users\username>
phpunit --version
PHPUnit x.y.z by Sebastian Bergmann and contributors.
Cygwin や MingW32 (TortoiseGit など) のシェル環境で使う場合は、
五番目のステップは飛ばしてもかまいません。単にファイルを
phpunit
という名前 (拡張子 .phar
は不要) で保存して、あとは
chmod 775 phpunit
で実行可能にしておきましょう。
PHPUnit プロジェクトが配布する公式リリースにはすべて、 リリースマネージャーによる署名がついています。 検証用の PGP 署名と SHA1 ハッシュは、phar.phpunit.de から取得できます。
リリースの検証をどのように行うのかについて、説明しましょう。まず、
phpunit.phar
をダウンロードし、さらにその
PGP 署名 phpunit.phar.asc
もダウンロードします。
wget http://phar.phpunit.cn/phpunit.phar
wget http://phar.phpunit.cn/phpunit.phar.asc
ダウンロードした PHPUnit の PHP Archive (phpunit.phar
)
を、署名 (phpunit.phar.asc
) で検証します。
gpg phpunit.phar.asc
gpg: Signature made Sat 19 Jul 2014 01:28:02 PM CEST using RSA key ID 6372C20A
gpg: Can't check signature: public key not found
リリースマネージャーの公開鍵 (6372C20A
)
が、ローカルシステム上に存在しないようです。
検証を進めるには、リリースマネージャーの公開鍵を、鍵サーバーから取得する必要があります。
鍵サーバーには、たとえば pgp.uni-mainz.de
などがあります。
公開鍵サーバーはお互いリンクしあっているので、どの鍵サーバーを使ってもかまいません。
gpg --keyserver pgp.uni-mainz.de --recv-keys 0x4AA394086372C20A
gpg: requesting key 6372C20A from hkp server pgp.uni-mainz.de
gpg: key 6372C20A: public key "Sebastian Bergmann <sb@sebastian-bergmann.de>" imported
gpg: Total number processed: 1
gpg: imported: 1 (RSA: 1)
これで、"Sebastian Bergmann <sb@sebastian-bergmann.de>" さんの公開鍵を取得できました。 ただ、この鍵を作ったのが本当に Sebastian Bergmann という人なのかは、確かめようがありません。 ともあれ、もう一度リリースの署名を検証してみましょう。
gpg phpunit.phar.asc
gpg: Signature made Sat 19 Jul 2014 01:28:02 PM CEST using RSA key ID 6372C20A
gpg: Good signature from "Sebastian Bergmann <sb@sebastian-bergmann.de>"
gpg: aka "Sebastian Bergmann <sebastian@php.net>"
gpg: aka "Sebastian Bergmann <sebastian@thephp.cc>"
gpg: aka "Sebastian Bergmann <sebastian@phpunit.de>"
gpg: aka "Sebastian Bergmann <sebastian.bergmann@thephp.cc>"
gpg: aka "[jpeg image of size 40635]"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: D840 6D0D 8294 7747 2937 7831 4AA3 9408 6372 C20A
とりあえず、署名が正しいことはわかりました。
ただ、この署名が信頼できるものであるかどうかは、まだわかりません。
ここで言う「署名が正しい」とは、リリースのファイルが改ざんされていないということです。
しかし、公開鍵暗号方式の性質上、これだけでは不十分です。
6372C20A
を作ったのが
Sebastian Bergmann 本人であることを、確かめる必要があります。
公開鍵を作って公開鍵サーバーにアップロードするのは、誰にだってできることです。 当然、悪意のある攻撃者にも可能なことです。 攻撃者は、このニセの鍵を使って署名した、悪意のあるリリースを作ることもできます。 このリリース (そして署名) をダウンロードして検証すると、成功するでしょう。 なぜならその公開鍵は、悪意のある攻撃者が作ったニセの鍵だからです。 こういったことを防ぐために、鍵の作者も検証しなければいけないのです。 公開鍵の作者を検証する方法については、このマニュアルの範囲を超えるので、割愛します。
PHPUnit のインストールを管理するためのシェルスクリプトを用意しておくのもいいでしょう。 GnuPG の署名を検証してから、テストスイートを実行させるようなものです。たとえば次のようになります。
#!/usr/bin/env bash clean=1 # Delete phpunit.phar after the tests are complete? aftercmd="php phpunit.phar --bootstrap bootstrap.php src/tests" gpg --fingerprint D8406D0D82947747293778314AA394086372C20A if [ $? -ne 0 ]; then echo -e "\033[33mDownloading PGP Public Key...\033[0m" gpg --recv-keys D8406D0D82947747293778314AA394086372C20A # Sebastian Bergmann <sb@sebastian-bergmann.de> gpg --fingerprint D8406D0D82947747293778314AA394086372C20A if [ $? -ne 0 ]; then echo -e "\033[31mCould not download PGP public key for verification\033[0m" exit fi fi if [ "$clean" -eq 1 ]; then # Let's clean them up, if they exist if [ -f phpunit.phar ]; then rm -f phpunit.phar fi if [ -f phpunit.phar.asc ]; then rm -f phpunit.phar.asc fi fi # 最新のリリースとその署名の取得 if [ ! -f phpunit.phar ]; then wget http://phar.phpunit.cn/phpunit.phar fi if [ ! -f phpunit.phar.asc ]; then wget http://phar.phpunit.cn/phpunit.phar.asc fi # 実行前の検証 gpg --verify phpunit.phar.asc phpunit.phar if [ $? -eq 0 ]; then echo echo -e "\033[33mBegin Unit Testing\033[0m" # Run the testing suite `$after_cmd` # Cleanup if [ "$clean" -eq 1 ]; then echo -e "\033[32mCleaning Up!\033[0m" rm -f phpunit.phar rm -f phpunit.phar.asc fi else echo chmod -x phpunit.phar mv phpunit.phar /tmp/bad-phpunit.phar mv phpunit.phar.asc /tmp/bad-phpunit.phar.asc echo -e "\033[31mSignature did not match! PHPUnit has been moved to /tmp/bad-phpunit.phar\033[0m" exit 1 fi
Composer
を使ってプロジェクトの依存関係を管理するには、
phpunit/phpunit
への (開発時の) 依存情報をプロジェクトの
composer.json
ファイルに追加します。
composer require --dev phpunit/phpunit ^6.5
オプションのパッケージとして、これらが使えます。
PHP_Invoker
callable をタイムアウトつきで実行するユーティリティクラス。 テストのタイムアウトを厳格に指定するために必要なパッケージ。
このパッケージは、PHPUnit の PHAR 版の中に含まれています。 Composer でインストールするには、次のコマンドを実行します。
composer require --dev phpunit/php-invoker
DbUnit
DbUnit の PHP/PHPUnit 向けの移植。データベースとのやりとりをテスト可能にする。
このパッケージは、PHPUnit の PHAR 版の中に含まれていません。 Composer でインストールするには、次のコマンドを実行します。
composer require --dev phpunit/dbunit
例 2.1 で、 PHP の配列操作のテストを PHPUnit 用に書く方法を示します。 この例では、PHPUnit を使ったテストを書く際の基本的な決まり事や手順を紹介します。
Class
という名前のクラスのテストは、ClassTest
という名前のクラスに記述します。
ClassTest
は、(ほとんどの場合) PHPUnit\Framework\TestCase
を継承します。
テストは、test*
という名前のパブリックメソッドとなります。
あるいは、@test
アノテーションをメソッドのコメント部で使用することで、それがテストメソッドであることを示すこともできます。
テストメソッドの中で assertEquals()
のようなアサーションメソッド (付録 A を参照ください) を使用して、期待される値と実際の値が等しいことを確かめます。
例 2.1: PHPUnit での配列操作のテスト
<?php use PHPUnit\Framework\TestCase; class StackTest extends TestCase { public function testPushAndPop() { $stack = []; $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
何かを | ||
--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 necessarily 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 は、テストメソッド間の依存性の明示的な宣言をサポートしています。 この依存性とは、テストメソッドが実行される順序を定義するものではありません。 プロデューサーがテストフィクスチャを作ってそのインスタンスを返し、 依存するコンシューマーがそれを受け取って利用するというものです。
プロデューサーとは、返り値としてテスト対象のユニットを生成するテストメソッドのこと。
コンシューマーとは、プロデューサーの返り値に依存するテストメソッドのこと。
例 2.2
は、@depends
アノテーションを使ってテストメソッドの依存性をあらわす例です。
例 2.2: @depends
アノテーションを使った依存性の表現
<?php use PHPUnit\Framework\TestCase; class StackTest extends TestCase { public function testEmpty() { $stack = []; $this->assertEmpty($stack); return $stack; } /** * @depends testEmpty */ public function testPush(array $stack) { array_push($stack, 'foo'); $this->assertEquals('foo', $stack[count($stack)-1]); $this->assertNotEmpty($stack); return $stack; } /** * @depends testPush */ public function testPop(array $stack) { $this->assertEquals('foo', array_pop($stack)); $this->assertEmpty($stack); } } ?>
上の例では、まず最初のテスト testEmpty()
で新しい配列を作り、それが空であることを確かめます。
このテストは、フィクスチャを返します。
二番目のテスト testPush()
は
testEmpty()
に依存しており、
依存するテストの結果を引数として受け取ります。
最後の testPop()
は
testPush()
に依存しています。
プロデューサーの生成する戻り値は、デフォルトでは「そのままの形式」でコンシューマーに渡されます。
つまり、プロデューサーがオブジェクトを戻した場合は、そのオブジェクトへの参照がコンシューマーに渡されるということです。
参照ではなくオブジェクトのコピーを渡す必要がある場合は、@depends
ではなく @depends clone
を使う必要があります。
問題の局所化を手早く行うには、失敗したテストに目を向けやすくしたいものです。 そのため PHPUnit では、 あるテストが失敗したときにはそのテストに依存する他のテストの実行をスキップします。 テスト間の依存性を活用して問題点を見つけやすくしている例を 例 2.3 に示します。
例 2.3: テストの依存性の活用
<?php use PHPUnit\Framework\TestCase; class DependencyFailureTest extends TestCase { public function testOne() { $this->assertTrue(false); } /** * @depends testOne */ public function testTwo() { } } ?>
phpunit --verbose DependencyFailureTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
FS
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) DependencyFailureTest::testOne
Failed asserting that false is true.
/home/sb/DependencyFailureTest.php:6
There was 1 skipped test:
1) DependencyFailureTest::testTwo
This test depends on "DependencyFailureTest::testOne" to pass.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1, Skipped: 1.
ひとつのテストに複数の @depends
アノテーションをつけることもできます。
PHPUnit はテストが実行される順序を変更しないので、
テストが実行されるときに確実に依存性が満たされているようにしておく必要があります。
複数の @depends
アノテーションを持つテストは、
最初のプロデューサーからのフィクスチャを最初の引数、二番目のプロデューサーからのフィクスチャを二番目の引数、……
として受け取ります。
例 2.4
を参照ください。
例 2.4: 複数の依存性を持つテスト
<?php use PHPUnit\Framework\TestCase; class MultipleDependenciesTest extends TestCase { public function testProducerFirst() { $this->assertTrue(true); return 'first'; } public function testProducerSecond() { $this->assertTrue(true); return 'second'; } /** * @depends testProducerFirst * @depends testProducerSecond */ public function testConsumer() { $this->assertEquals( ['first', 'second'], func_get_args() ); } } ?>
phpunit --verbose MultipleDependenciesTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
...
Time: 0 seconds, Memory: 3.25Mb
OK (3 tests, 3 assertions)
テストメソッドには任意の引数を渡すことができます。
この引数は、データプロバイダメソッド
(例 2.5
の additionProvider()
)
で指定します。使用するデータプロバイダメソッドを指定するには
@dataProvider
アノテーションを使用します。
データプロバイダメソッドは、public
でなければなりません。また、
メソッドの返り値の型は、配列の配列あるいはオブジェクト
(Iterator
インターフェイスを実装しており、
反復処理の際に配列を返すもの) である必要があります。
この返り値の各要素に対して、その配列の中身を引数としてテストメソッドがコールされます。
例 2.5: 配列の配列を返すデータプロバイダの使用
<?php use PHPUnit\Framework\TestCase; class DataTest extends TestCase { /** * @dataProvider additionProvider */ public function testAdd($a, $b, $expected) { $this->assertEquals($expected, $a + $b); } public function additionProvider() { return [ [0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 3] ]; } } ?>
phpunit DataTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set #3 (1, 1, 3)
Failed asserting that 2 matches expected 3.
/home/sb/DataTest.php:9
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
大量のデータセットを使う場合は、デフォルトの数字を使うのではなく、各データセットに文字列の名前をつけておくと便利です。 出力もよりわかりやすくなり、テストを失敗させたデータセットの名前もわかるようになります。
例 2.6: データプロバイダでの名前つきデータセットの使用
<?php use PHPUnit\Framework\TestCase; class DataTest extends TestCase { /** * @dataProvider additionProvider */ public function testAdd($a, $b, $expected) { $this->assertEquals($expected, $a + $b); } public function additionProvider() { return [ 'adding zeros' => [0, 0, 0], 'zero plus one' => [0, 1, 1], 'one plus zero' => [1, 0, 1], 'one plus one' => [1, 1, 3] ]; } } ?>
phpunit DataTest
PHPUnit 4.6.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set "one plus one" (1, 1, 3)
Failed asserting that 2 matches expected 3.
/home/sb/DataTest.php:9
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
例 2.7: Iterator オブジェクトを返すデータプロバイダの使用
<?php use PHPUnit\Framework\TestCase; require 'CsvFileIterator.php'; class DataTest extends TestCase { /** * @dataProvider additionProvider */ public function testAdd($a, $b, $expected) { $this->assertEquals($expected, $a + $b); } public function additionProvider() { return new CsvFileIterator('data.csv'); } } ?>
phpunit DataTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) DataTest::testAdd with data set #3 ('1', '1', '3')
Failed asserting that 2 matches expected '3'.
/home/sb/DataTest.php:11
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
例 2.8: CsvFileIterator クラス
<?php use PHPUnit\Framework\TestCase; class CsvFileIterator implements Iterator { protected $file; protected $key = 0; protected $current; public function __construct($file) { $this->file = fopen($file, 'r'); } public function __destruct() { fclose($this->file); } public function rewind() { rewind($this->file); $this->current = fgetcsv($this->file); $this->key = 0; } public function valid() { return !feof($this->file); } public function key() { return $this->key; } public function current() { return $this->current; } public function next() { $this->current = fgetcsv($this->file); $this->key++; } } ?>
@dataProvider
で指定したメソッドと
@depends
で指定したテストの両方からの入力を受け取るテストの場合、
データプロバイダからの引数のほうが依存するテストからの引数より先にきます。
依存するテストからの引数は、どちらのデータセットに対しても同じになります。
例 2.9
を参照ください。
例 2.9: 同じテストでの @depends と @dataProvider の組み合わせ
<?php use PHPUnit\Framework\TestCase; class DependencyAndDataProviderComboTest extends TestCase { public function provider() { return [['provider1'], ['provider2']]; } public function testProducerFirst() { $this->assertTrue(true); return 'first'; } public function testProducerSecond() { $this->assertTrue(true); return 'second'; } /** * @depends testProducerFirst * @depends testProducerSecond * @dataProvider provider */ public function testConsumer() { $this->assertEquals( ['provider1', 'first', 'second'], func_get_args() ); } } ?>
phpunit --verbose DependencyAndDataProviderComboTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
...F
Time: 0 seconds, Memory: 3.50Mb
There was 1 failure:
1) DependencyAndDataProviderComboTest::testConsumer with data set #1 ('provider2')
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
- 0 => 'provider1'
+ 0 => 'provider2'
1 => 'first'
2 => 'second'
)
/home/sb/DependencyAndDataProviderComboTest.php:31
FAILURES!
Tests: 4, Assertions: 4, Failures: 1.
例 2.10
は、テストするコード内で例外がスローされたかどうかを
expectException()
メソッドを使用して調べる方法を示すものです。
例 2.10: expectException() メソッドの使用法
<?php use PHPUnit\Framework\TestCase; class ExceptionTest extends TestCase { public function testException() { $this->expectException(InvalidArgumentException::class); } } ?>
phpunit ExceptionTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) ExceptionTest::testException
Expected exception InvalidArgumentException
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
さらに、expectException()
、
expectExceptionCode()
、
expectExceptionMessage()
、
expectExceptionMessageRegExp()
といったメソッドで、
テスト対象のコードで発生するであろう例外をテストできます。
別の方法として、@expectedException
、
@expectedExceptionCode
、
@expectedExceptionMessage
、
@expectedExceptionMessageRegExp
といったアノテーションでも、
テスト対象のコードで発生するであろう例外をテストできます。
例 2.11
に例を示します。
例 2.11: @expectedException アノテーションの使用法
<?php use PHPUnit\Framework\TestCase; class ExceptionTest extends TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } } ?>
phpunit ExceptionTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) ExceptionTest::testException
Expected exception InvalidArgumentException
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
デフォルトでは、PHPUnit はテストの実行中に発生した PHP のエラーや警告そして notice を例外に変換します。これらの例外を用いて、たとえば 例 2.12 のように PHP のエラーが発生することをテストできます。
PHP の実行時設定 error_reporting
を使うと、
PHPUnit がどのエラーを例外に変換するのかを制限できます。
この機能に関して何か問題がでた場合は、PHP の設定を見直し、
調べたいと思っているエラーを抑制するようになっていないかどうか確認しましょう。
例 2.12: @expectedException を用いた、PHP エラーが発生することのテスト
<?php use PHPUnit\Framework\TestCase; class ExpectedErrorTest extends TestCase { /** * @expectedException PHPUnit\Framework\Error */ public function testFailingInclude() { include 'not_existing_file.php'; } } ?>
phpunit -d error_reporting=2 ExpectedErrorTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
.
Time: 0 seconds
OK (1 test, 1 assertion)
PHPUnit\Framework\Error\Notice
および
PHPUnit\Framework\Error\Warning
は、
それぞれ PHP の notice と警告に対応します。
例外をテストするときには可能な限り限定的にしなければいけません。
あまりに一般化されすぎたクラスをテストすると、予期せぬ副作用を引き起こしかねません。
というわけで、
@expectedException
や
setExpectedException()
を使った Exception
クラスのテストはできないようにしました。
エラーを引き起こすような PHP の関数、たとえば fopen
などに依存するテストを行うときには、テスト中にエラーを抑制できれば便利なことがあります。
そうすれば、notice のせいで
PHPUnit\Framework\Error\Notice
が出てしまうことなく、返り値だけをチェックできるようになります。
例 2.13: PHP のエラーが発生するコードの返り値のテスト
<?php use PHPUnit\Framework\TestCase; class ErrorSuppressionTest extends TestCase { public function testFileWriting() { $writer = new FileWriter; $this->assertFalse(@$writer->write('/is-not-writeable/file', 'stuff')); } } class FileWriter { public function write($file, $content) { $file = fopen($file, 'w'); if($file == false) { return false; } // ... } } ?>
phpunit ErrorSuppressionTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
.
Time: 1 seconds, Memory: 5.25Mb
OK (1 test, 1 assertion)
もしエラーを抑制しなければ、このテストは失敗して
fopen(/is-not-writeable/file): failed to open stream:
No such file or directory
となります。
メソッドの実行結果を確かめる方法として、(echo
や
print
などによる)
出力が期待通りのものかを調べたいこともあるでしょう。
PHPUnit\Framework\TestCase
クラスは、PHP の
出力バッファリング 機能を使用してこの仕組みを提供します。
例 2.14
では、期待する出力内容を expectOutputString()
メソッドで設定する方法を示します。
期待通りの出力が得られなかった場合は、そのテストは失敗という扱いになります。
例 2.14: 関数やメソッドの出力内容のテスト
<?php use PHPUnit\Framework\TestCase; class OutputTest extends TestCase { public function testExpectFooActualFoo() { $this->expectOutputString('foo'); print 'foo'; } public function testExpectBarActualBaz() { $this->expectOutputString('bar'); print 'baz'; } } ?>
phpunit OutputTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
.F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) OutputTest::testExpectBarActualBaz
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
表 2.1 は、 テストの出力用に提供するメソッドをまとめたものです。
表2.1 テストの出力用のメソッド
メソッド | 意味 |
---|---|
void expectOutputRegex(string $regularExpression) | 出力が正規表現 $regularExpression にマッチするであろうという予測を設定します。 |
void expectOutputString(string $expectedString) | 出力が文字列 $expectedString と等しくなるであろうという予測を設定します。 |
bool setOutputCallback(callable $callback) | たとえば出力時の正規化などに使用するコールバック関数を設定します。 |
string getActualOutput() | 実際の出力を取得します。 |
strict モードでは、出力を発生させるテストは失敗します。
テストが失敗した場合、PHPUnit は、状況を可能な限り詳細に報告します。 これが、何が問題だったのかを調べるのに役立つでしょう。
例 2.15: 配列の比較に失敗したときのエラー出力
<?php use PHPUnit\Framework\TestCase; class ArrayDiffTest extends TestCase { public function testEquality() { $this->assertEquals( [1, 2, 3, 4, 5, 6], [1, 2, 33, 4, 5, 6] ); } } ?>
phpunit ArrayDiffTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) ArrayDiffTest::testEquality
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
0 => 1
1 => 2
- 2 => 3
+ 2 => 33
3 => 4
4 => 5
5 => 6
)
/home/sb/ArrayDiffTest.php:7
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
この例では配列の要素のうちひとつだけが異なっています。 それ以外の値も表示することで、どこが悪かったのかをわかりやすくしています。
出力が長すぎる場合は PHPUnit が出力を分割し、違っている部分の前後数行だけを出力します。
例 2.16: 要素数の多い配列の比較に失敗したときのエラー出力
<?php use PHPUnit\Framework\TestCase; class LongArrayDiffTest extends TestCase { public function testEquality() { $this->assertEquals( [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 33, 4, 5, 6] ); } } ?>
phpunit LongArrayDiffTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) LongArrayDiffTest::testEquality
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
13 => 2
- 14 => 3
+ 14 => 33
15 => 4
16 => 5
17 => 6
)
/home/sb/LongArrayDiffTest.php:7
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
比較に失敗したときに、PHPUnit は入力値をテキスト形式にしてこれを比較します。 この実装が原因で、実際の違う箇所よりも多くの問題を報告してしまうことがあります。
この問題が発生するのは、 assertEquals などの「緩い」比較の関数を、配列やオブジェクトに対して使った場合だけです。
例 2.17: 緩い比較を使った場合の diff の生成のエッジケース
<?php use PHPUnit\Framework\TestCase; class ArrayWeakComparisonTest extends TestCase { public function testEquality() { $this->assertEquals( [1, 2, 3, 4, 5, 6], ['1', 2, 33, 4, 5, 6] ); } } ?>
phpunit ArrayWeakComparisonTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) ArrayWeakComparisonTest::testEquality
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
- 0 => 1
+ 0 => '1'
1 => 2
- 2 => 3
+ 2 => 33
3 => 4
4 => 5
5 => 6
)
/home/sb/ArrayWeakComparisonTest.php:7
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
この例では、最初のインデックスの
1
と '1'
がエラー報告されていますが、assertEquals ではこれらを等しいとみなしているはずです。
phpunit
コマンドを実行すると、PHPUnit
のコマンドライン版テストランナーが起動します。
コマンドラインのテストランナーを使用したテストの様子を以下に示します。
phpunit ArrayTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
..
Time: 0 seconds
OK (2 tests, 2 assertions)
このように実行すると、PHPUnit のコマンドラインテストランナーは、
まず現在の作業ディレクトリにあるソースファイル
ArrayTest.php
を探してそれを読み込み、テストケースクラス
ArrayTest
を探します。
そして、そのクラス内のテストを実行します。
テストがひとつ実行されるたびに、PHPUnit コマンドラインツールはその経過を示す文字を出力します。
PHPUnit は、失敗 (failures) と
エラー (errors) を区別します。
「失敗」は PHPUnit のアサーションに違反した場合、つまり例えば
assertEquals()
のコールに失敗した場合などで、
「エラー」は予期せぬ例外や PHP のエラーが発生した場合となります。
この区別は、時に有用です。というのは「エラー」は一般的に「失敗」
より修正しやすい傾向があるからです。
もし大量の問題が発生した場合は、まず「エラー」を最初に片付け、
その後で「失敗」を修正していくのが最良の方法です。
以下のコードで、コマンドライン版テストランナーのオプションの一覧を見てみましょう。
phpunit --help
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
Usage: phpunit [options] UnitTest [UnitTest.php]
phpunit [options] <directory>
Code Coverage Options:
--coverage-clover <file> Generate code coverage report in Clover XML format.
--coverage-crap4j <file> Generate code coverage report in Crap4J XML format.
--coverage-html <dir> Generate code coverage report in HTML format.
--coverage-php <file> Export PHP_CodeCoverage object to file.
--coverage-text=<file> Generate code coverage report in text format.
Default: Standard output.
--coverage-xml <dir> Generate code coverage report in PHPUnit XML format.
Logging Options:
--log-junit <file> Log test execution in JUnit XML format to file.
--log-tap <file> Log test execution in TAP format to file.
--log-json <file> Log test execution in JSON format.
--testdox-html <file> Write agile documentation in HTML format to file.
--testdox-text <file> Write agile documentation in Text format to file.
Test Selection Options:
--filter <pattern> Filter which tests to run.
--testsuite <name,...> Filter which testsuite to run.
--group ... Only runs tests from the specified group(s).
--exclude-group ... Exclude tests from the specified group(s).
--list-groups List available test groups.
--test-suffix ... Only search for test in files with specified
suffix(es). Default: Test.php,.phpt
Test Execution Options:
--report-useless-tests Be strict about tests that do not test anything.
--strict-coverage Be strict about unintentionally covered code.
--strict-global-state Be strict about changes to global state
--disallow-test-output Be strict about output during tests.
--enforce-time-limit Enforce time limit based on test size.
--disallow-todo-tests Disallow @todo-annotated tests.
--process-isolation Run each test in a separate PHP process.
--no-globals-backup Do not backup and restore $GLOBALS for each test.
--static-backup Backup and restore static attributes for each test.
--colors=<flag> Use colors in output ("never", "auto" or "always").
--columns <n> Number of columns to use for progress output.
--columns max Use maximum number of columns for progress output.
--stderr Write to STDERR instead of STDOUT.
--stop-on-error Stop execution upon first error.
--stop-on-failure Stop execution upon first error or failure.
--stop-on-risky Stop execution upon first risky test.
--stop-on-skipped Stop execution upon first skipped test.
--stop-on-incomplete Stop execution upon first incomplete test.
-v|--verbose Output more verbose information.
--debug Display debugging information during test execution.
--loader <loader> TestSuiteLoader implementation to use.
--repeat <times> Runs the test(s) repeatedly.
--tap Report test execution progress in TAP format.
--testdox Report test execution progress in TestDox format.
--printer <printer> TestListener implementation to use.
Configuration Options:
--bootstrap <file> A "bootstrap" PHP file that is run before the tests.
-c|--configuration <file> Read configuration from XML file.
--no-configuration Ignore default configuration file (phpunit.xml).
--include-path <path(s)> Prepend PHP's include_path with given path(s).
-d key[=value] Sets a php.ini value.
Miscellaneous Options:
-h|--help Prints this usage information.
--version Prints the version and exits.
phpunit UnitTest
UnitTest
という名前のクラスで定義されている
テストを実行します。このクラスは、UnitTest.php
という名前のファイルの中に定義されているものとします。
UnitTest
は、PHPUnit\Framework\TestCase
を継承したクラスであるか、あるいは PHPUnit_Framework_Test
オブジェクト、例えば PHPUnit_Framework_TestSuite
のインスタンスを返す public static suite()
というメソッドを保持するクラスでなければなりません。
phpunit UnitTest UnitTest.php
UnitTest
という名前のクラスで定義されているテストを実行します。
このクラスは、指定したファイルの中で定義されているものとします。
--coverage-clover
テスト結果から XML 形式のログファイルを作成し、 コードカバレッジ情報もそこに含めます。 詳細は 第 13 章 を参照ください。
この機能は、tokenizer 拡張モジュールおよび Xdebug 拡張モジュールがインストールされている場合にのみ使用可能となることに注意しましょう。
--coverage-crap4j
コードカバレッジレポートを Crap4j 形式で作成します。詳細は 第 11 章 を参照ください。
この機能は、tokenizer 拡張モジュールおよび Xdebug 拡張モジュールがインストールされている場合にのみ使用可能となることに注意しましょう。
--coverage-html
コードカバレッジレポートを HTML 形式で作成します。詳細は 第 11 章 を参照ください。
この機能は、tokenizer 拡張モジュールおよび Xdebug 拡張モジュールがインストールされている場合にのみ使用可能となることに注意しましょう。
--coverage-php
シリアライズした PHP_CodeCoverage オブジェクトを生成し、 コードカバレッジ情報もそこに含めます。
この機能は、tokenizer 拡張モジュールおよび Xdebug 拡張モジュールがインストールされている場合にのみ使用可能となることに注意しましょう。
--coverage-text
テストを実行したときに、ログファイルあるいはコマンドライン出力で 可読形式のコードカバレッジ情報を生成します。 詳細は 第 13 章 を参照ください。
この機能は、tokenizer 拡張モジュールおよび Xdebug 拡張モジュールがインストールされている場合にのみ使用可能となることに注意しましょう。
--log-junit
JUnit XML フォーマットを使用して、テストの実行結果のログを作成します。 詳細は 第 13 章 を参照ください。
--testdox-html
および --testdox-text
実行したテストについて、HTML あるいはプレーンテキスト形式のドキュメントを生成します 詳細は 第 12 章 を参照ください。
--filter
指定した正規表現パターンにマッチする名前のテストのみを実行します。
パターンがデリミタで囲まれていない場合は、PHPUnit はパターンをデリミタ /
で囲みます。
マッチするテスト名は、次のいずれかのフォーマットになります。
TestNamespace\TestCaseClass::testMethod
デフォルトのテスト名のフォーマットは、テストメソッドの中でマジック定数
__METHOD__
を使うのと同等です。
TestNamespace\TestCaseClass::testMethod with data set #0
テストがデータプロバイダーを持つ場合、データを処理するたびに、 現在のインデックスをデフォルトのテスト名の後に続けたものを取得します。
TestNamespace\TestCaseClass::testMethod with data set "my named data"
テストが持つデータプロバイダーが名前つきセットを使う場合、データを処理するたびに、 現在の名前をデフォルトのテスト名の後に続けたものを取得します。 名前つきデータセットの例は 例 3.1 を参照ください。
例 3.1: 名前つきデータセット
<?php use PHPUnit\Framework\TestCase; namespace TestNamespace; class TestCaseClass extends TestCase { /** * @dataProvider provider */ public function testMethod($data) { $this->assertTrue($data); } public function provider() { return [ 'my named data' => [true], 'my data' => [true] ]; } } ?>
/path/to/my/test.phpt
PHPT のテストのテスト名は、ファイルシステムのパスになります。
有効なフィルターパターンの例は、例 3.2 を参照ください。
例 3.2: フィルターパターンの例
--filter 'TestNamespace\\TestCaseClass::testMethod'
--filter 'TestNamespace\\TestCaseClass'
--filter TestNamespace
--filter TestCaseClass
--filter testMethod
--filter '/::testMethod .*"my named data"/'
--filter '/::testMethod .*#5$/'
--filter '/::testMethod .*#(5|6|7)$/'
データプロバイダーのマッチングに使えるショートカットは、 例 3.3 を参照ください。
例 3.3: フィルターのショートカット
--filter 'testMethod#2'
--filter 'testMethod#2-4'
--filter '#2'
--filter '#2-4'
--filter 'testMethod@my named data'
--filter 'testMethod@my.*data'
--filter '@my named data'
--filter '@my.*data'
--testsuite
指定したパターンにマッチする名前のテストスイートのみを実行します。
--group
指定したグループのテストのみを実行します。
あるテストを特定のグループに所属させるには、
@group
アノテーションを使用します。
@author
アノテーションは
@group
のエイリアスで、
テストの作者に基づいてテストをフィルタリングします。
--exclude-group
指定したグループをテストの対象外とします。
あるテストを特定のグループに所属させるには、
@group
アノテーションを使用します。
--list-groups
使用可能なテストグループの一覧を表示します。
--test-suffix
指定したサフィックスのテストファイルだけを探します。
--report-useless-tests
何もテストをしないテストについて厳格にチェックします。 詳細は ??? を参照ください。
--strict-coverage
意図せずカバーしているコードについて厳格にチェックします。 詳細は ??? を参照ください。
--strict-global-state
グローバルな状態の変更について厳格にチェックします。 詳細は ??? を参照ください。
--disallow-test-output
実行中に何かを出力するテストについて厳格にチェックします。 詳細は ??? を参照ください。
--disallow-todo-tests
docblock に @todo
アノテーションが指定されているテストを実行しません。
--enforce-time-limit
テストのサイズに応じて、制限時間を設定します。 詳細は ??? を参照ください。
--process-isolation
各テストを個別の PHP プロセスで実行します。
--no-globals-backup
$GLOBALS のバックアップ・リストアを行いません。 詳細は 「グローバルな状態」 を参照ください。
--static-backup
ユーザ定義クラスの静的属性のバックアップ・リストアを行います。 詳細は 「グローバルな状態」 を参照ください。
--colors
出力に色を使用します。 Windows では、ANSICON あるいは ConEmu を利用します。
指定できる値は、以下の三つです。
never
: 出力を色分けしません。これは、--colors
オプションを省略したときのデフォルトです。
auto
: ターミナルが色をサポートしていない場合や、
出力をコマンドにパイプしたりファイルにリダイレクトしたりする場合を除き、出力に色を使います。
always
: ターミナルが色をサポートしていない場合や、
出力をコマンドにパイプしたりファイルにリダイレクトしたりする場合も含めて、常に出力に色を使います。
--colors
だけを指定して何も値を指定しなかった場合は、auto
を選んだものとみなされます。
--stderr
オプションで、出力先を
STDOUT
ではなく STDERR
にします。
--stop-on-error
最初にエラーが発生した時点で実行を停止します。
--stop-on-failure
最初にエラーあるいは失敗が発生した時点で実行を停止します。
--stop-on-risky
最初に危険なテストがあらわれた時点で実行を停止します。
--stop-on-skipped
最初にテストのスキップが発生した時点で実行を停止します。
--stop-on-incomplete
最初に不完全なテストがあらわれた時点で実行を停止します。
--verbose
より詳細な情報を出力します。例えば、 未完成のテストや省略したテストの名前が表示されます。
--debug
テスト名などのデバッグ情報を、テストの実行開始時に出力します。
--loader
PHPUnit_Runner_TestSuiteLoader
を実装したクラスのうち、
実際に使用するものを指定します。
標準のテストスイートローダーは、現在の作業ディレクトリおよび PHP
の設定項目 include_path
で指定されているディレクトリからソースファイルを探します。
Project_Package_Class
クラスがソースファイル Project/Package/Class.php
に対応します。
--repeat
指定された回数だけ、繰り返しテストを実行します。
--testdox
テストの進行状況を、アジャイルな文書として報告します。 詳細は 第 12 章 を参照ください。
--printer
結果を表示するために使うプリンタクラスを指定します。このプリンタクラスは
PHPUnit_Util_Printer
を継承し、かつ
PHPUnit\Framework\TestListener
インターフェイスを実装したものでなければなりません。
--bootstrap
テストの前に実行される "ブートストラップ" PHP ファイルを指定します。
--configuration
, -c
設定を XML ファイルから読み込みます。 詳細は 付録 C を参照ください。
phpunit.xml
あるいは
phpunit.xml.dist
(この順番で使用します)
が現在の作業ディレクトリに存在しており、かつ --configuration
が使われていない場合、設定が自動的にそのファイルから読み込まれます。
--no-configuration
現在の作業ディレクトリにある phpunit.xml
および
phpunit.xml.dist
を無視します。
--include-path
PHP の include_path
の先頭に、指定したパスを追加します。
-d
指定した PHP 設定オプションの値を設定します。
PHPUnit 4.8 以降では、これらのオプションを引数の後にも指定できるようになりました。
テストを記述する際にいちばん時間を食うのは、テストを開始するための事前設定と テスト終了後の後始末の処理を書くことです。この事前設定は、テストの フィクスチャ と呼ばれます。
例 2.1
では、フィクスチャは
$stack
という変数に格納された配列だけでした。
しかし、たいていの場合はフィクスチャはこれより複雑なものとなり、
それを準備するにはかなりの量のコードが必要です。本来のテストの内容が、
フィクスチャを設定するためのコードの中に埋もれてしまうことになります。
この問題は、複数のテストで同じようなフィクスチャを設定する場合により顕著になります。
テストフレームワークの助けがなければ、
個々のテストのなかで同じような準備コードを繰り返し書くはめになってしまいます。
PHPUnit は、準備用のコードの共有をサポートしています。
各テストメソッドが実行される前に、setUp()
という名前のテンプレートメソッドが実行されます。setUp()
は、テスト対象のオブジェクトを生成するような処理に使用します。
テストメソッドの実行が終了すると、それが成功したか否かにかかわらず、
tearDown()
という名前の別のテンプレートメソッドが実行されます。
tearDown()
では、テスト対象のオブジェクトの後始末などを行います。
例 2.2
producer-consumer の関係を使って複数のテストでフィクスチャを共有しました。
これは常に要求されるというものではありませんし、常に可能だとも限りません。
例 4.1 では、
フィクスチャを再利用するのではなくコードで作成する方式で
StackTest
にテストを書く方法をごらんいただきましょう。
まずインスタンス変数 $stack
を宣言し、
メソッドローカル変数のかわりにこれを使うことにします。
そして、array
の作成を
setUp()
メソッドで行います。
最後に、冗長なコードをテストメソッドから削除し、
アサーションメソッド assertEquals()
ではメソッド変数 $stack
のかわりに
新たに導入したインスタンス変数 $this->stack
を使うようにします。
例 4.1: setUp() を使用して stack フィクスチャを作成する
<?php use PHPUnit\Framework\TestCase; class StackTest extends TestCase { protected $stack; protected function setUp() { $this->stack = []; } public function testEmpty() { $this->assertTrue(empty($this->stack)); } public function testPush() { array_push($this->stack, 'foo'); $this->assertEquals('foo', $this->stack[count($this->stack)-1]); $this->assertFalse(empty($this->stack)); } public function testPop() { array_push($this->stack, 'foo'); $this->assertEquals('foo', array_pop($this->stack)); $this->assertTrue(empty($this->stack)); } } ?>
テンプレートメソッド setUp()
および tearDown()
は、テストケースクラスのテストメソッドごとに (そして最初にインスタンスを作成したときに)
一度ずつ実行されます。
さらに、テンプレートメソッド setUpBeforeClass()
および
tearDownAfterClass()
が存在します。
これらはそれぞれ、テストケースクラスの最初のテストメソッドの実行前と
テストケースクラスの最後のテストの実行後にコールされます。
以下の例は、テストケースクラスで使用できるすべてのテンプレートメソッドを示すものです。
例 4.2: 利用可能なすべてのテンプレートメソッド
<?php use PHPUnit\Framework\TestCase; class TemplateMethodsTest extends TestCase { public static function setUpBeforeClass() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function setUp() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function assertPreConditions() { fwrite(STDOUT, __METHOD__ . "\n"); } public function testOne() { fwrite(STDOUT, __METHOD__ . "\n"); $this->assertTrue(true); } public function testTwo() { fwrite(STDOUT, __METHOD__ . "\n"); $this->assertTrue(false); } protected function assertPostConditions() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function tearDown() { fwrite(STDOUT, __METHOD__ . "\n"); } public static function tearDownAfterClass() { fwrite(STDOUT, __METHOD__ . "\n"); } protected function onNotSuccessfulTest(Exception $e) { fwrite(STDOUT, __METHOD__ . "\n"); throw $e; } } ?>
phpunit TemplateMethodsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
TemplateMethodsTest::setUpBeforeClass
TemplateMethodsTest::setUp
TemplateMethodsTest::assertPreConditions
TemplateMethodsTest::testOne
TemplateMethodsTest::assertPostConditions
TemplateMethodsTest::tearDown
.TemplateMethodsTest::setUp
TemplateMethodsTest::assertPreConditions
TemplateMethodsTest::testTwo
TemplateMethodsTest::tearDown
TemplateMethodsTest::onNotSuccessfulTest
FTemplateMethodsTest::tearDownAfterClass
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) TemplateMethodsTest::testTwo
Failed asserting that <boolean:false> is true.
/home/sb/TemplateMethodsTest.php:30
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
setUp()
と tearDown()
は理屈上では対称的になるはずですが、実際にはそうではありません。実際には、
tearDown()
を実装する必要があるのは setUp()
で外部リソース (ファイルやソケットなど) を割り当てた場合のみです。もし
setUp()
で単に PHP オブジェクトを作成しただけの場合は、
一般には tearDown()
は必要ありません。しかし、もし
setUp()
で大量のオブジェクトを作成した場合には、
それらの後始末をするために tearDown()
で変数を
unset()
したくなることもあるでしょう。
テストケースオブジェクト自体のガベージコレクションにはあまり意味がありません。
ふたつのテストがあって、それぞれの setup がほんの少しだけ違う場合にはどうなるでしょう? このような場合は、二種類の可能性が考えられます。
もし setUp()
の違いがごくわずかなものなら、
その違う部分を setUp()
からテストメソッドのほうに移動させます。
setUp()
の違いが大きければ、
テストケースクラスを別に分ける必要があります。それぞれのクラスには、
setup の違いを表す名前をつけます。
複数のテストの間でフィクスチャを共有する利点は、ほとんどありません。 しかし、設計上の問題などでどうしても フィクスチャを共有しなければならないこともあるでしょう。
複数のテスト間で共有する意味のあるフィクスチャの例として意味のあるものといえば、 データベースとの接続でしょう。テストのたびに新しいデータベース接続を毎回作成するのではなく、 最初にログインした状態を再利用するということです。こうすることで、 テストの実行時間を短縮できます。
例 4.3
では、テンプレートメソッド setUpBeforeClass()
および
tearDownAfterClass()
を用いて、
テストケースクラス内の最初のテストを実行する前にデータベースに接続し、
最後のテストが終わってから接続を切断するようにしています。
例 4.3: テストスイートの複数テスト間でのフィクスチャの共有
<?php use PHPUnit\Framework\TestCase; class DatabaseTest extends TestCase { protected static $dbh; public static function setUpBeforeClass() { self::$dbh = new PDO('sqlite::memory:'); } public static function tearDownAfterClass() { self::$dbh = null; } } ?>
このようにフィクスチャを共有することがテストの価値を下げてしまうということを、 まだうまく伝え切れていないかもしれません。問題なのは、 各オブジェクトが疎結合になっていないという設計なのです。 複数が連携しているようなテストを作って設計上の問題から目をそらしてしまうのではなく、 きちんと設計しなおした上で、スタブ (第 9 章 を参照ください) を使用するテストを書くことをお勧めします。
singleton を使用するコードをテストするのはたいへんです。 同様に、グローバル変数を使うコードのテストもまたたいへんです。 一般に、テスト対象のコードはグローバル変数と密接に関連しており、 グローバル変数の内容を制御することはできません。 さらに別の問題もあって、あるテストの中でグローバル変数を変更してしまうと 別のテストがうまく動かなくなる可能性があります。
PHP では、グローバル変数は次のような動きをします。
グローバル変数 $foo = 'bar';
は、$GLOBALS['foo'] = 'bar';
として格納される。
$GLOBALS
はスーパーグローバル変数と呼ばれる。
スーパーグローバル変数は組み込みの変数で、すべてのスコープで常に利用できる。
関数やメソッドのスコープでグローバル変数 $foo
にアクセスするには、直接 $GLOBALS['foo']
にアクセスするか、あるいは global $foo;
を用いて (グローバル変数を参照する) ローカル変数を作成する。
グローバル変数のほかに、クラスの静的属性もグローバル状態となります。
PHPUnit 6より前のバージョンのデフォルトでは、PHPUnit がテストを実行する際には、
グローバル変数やスーパーグローバル変数 ($GLOBALS
,
$_ENV
, $_POST
,
$_GET
, $_COOKIE
,
$_SERVER
, $_FILES
,
$_REQUEST
) への変更が他のテストへの影響を及ぼさないようにしました。
PHPUnit 6以降は、グローバル変数やスーパーグローバル変数のバックアップとリストアをデフォルトでは行わなくなりました。
この機能を使いたい場合は、--globals-backup
オプションを指定するか、
XML設定ファイルで backupGlobals="true"
を指定します。
グローバル変数やクラスの静的属性のバックアップ・リストアには
serialize()
および
unserialize()
を使用しています。
PHP 組み込みの一部のクラス、たとえば PDO
のオブジェクトはシリアライズできないため、そのようなオブジェクトが
$GLOBALS
配列に格納されている場合はバックアップ操作が失敗します。
「@backupGlobals」 で説明している
@backupGlobals
アノテーションを使用すると、
グローバル変数のバックアップ・リストア操作を制御することができます。
あるいは、グローバル変数のブラックリストを指定して、
その変数だけはバックアップ・リストアの対象から除外することもできます。
class MyTest extends TestCase { protected $backupGlobalsBlacklist = ['globalVariable']; // ... }
$backupGlobalsBlacklist
プロパティをたとえば setUp()
メソッド内で設定しても効果が及びません。
「@backupStaticAttributes」
で説明する @backupStaticAttributes
アノテーションを使うと、
宣言されたクラス内のすべての static プロパティの値をバックアップしてからテストを始め、
テストが終わった後でそれらの値を復元することができます。
テストが始まる際に、宣言されたすべてのクラスについて処理を行います。テストクラス自身だけではありません。 処理するのは、クラスの static プロパティだけです。関数の内部の static 変数は対象外です。
@backupStaticAttributes
の操作は、テストメソッドの前に実行されます。
ただし、有効になっている場合だけです。
先に実行されたテストメソッドの中で static プロパティの値が変更されており、かつそのメソッドでは
@backupStaticAttributes
が有効になっていなかった場合、
バックアップ (そしてリストア) されるのは、先に実行されたメソッドで変更後の値になります。
もともと宣言されていたデフォルト値ではありません。
PHP は、static 変数が宣言された当時のデフォルト値を、どこにも記録していないのです。
これは、テストの内部で新しく読み込んだ (宣言した) クラスの static プロパティについても同様です。 この場合も、もともと宣言されていたデフォルト値を、テストの後に復元することはできません。デフォルト値が残っていないからです。 テスト内で設定された値が、それ以降のテストに持ち越されます。
ユニットテストでは、テスト対象の static プロパティの値は、setUp()
で明示的にリセットしておくことを推奨します
(そして、tearDown()
でもリセットしておけば、それ以降のテストに影響を及ぼすこともなくなります)。
static 属性のブラックリストを渡せば、保存と復元の対象からそれらを除外することもできます。 ブラックリストは、このように指定します。
class MyTest extends TestCase { protected $backupStaticAttributesBlacklist = [ 'className' => ['attributeName'] ]; // ... }
$backupStaticAttributesBlacklist
プロパティをたとえば setUp()
メソッド内で設定しても効果が及びません。
PHPUnit の目指すところのひとつに 「自由に組み合わせられる」ということがあります。つまり、 例えば「そのプロジェクトのすべてのテストを実行する」「プロジェクトの中の ある部品を構成するすべてのクラスについて、すべてのテストを実行する」 「特定のひとつのクラスのテストのみを実行する」など、 数や組み合わせにとらわれずに好きなテストを一緒に実行できるということです。
PHPUnit では、さまざまな方法でテストを組み合わせてテストスイートにまとめることができます。 本章では、その中でもよく使われる手法を説明します。
おそらく、テストスイートをとりまとめるもっとも簡単な方法は すべてのテストケースのソースファイルを一つのテストディレクトリにまとめることでしょう。 PHPUnit はテストディレクトリを再帰的に探索し、 テストを自動的に見つけて実行します。
sebastianbergmann/money
ライブラリのテストスイートを見てみましょう。このプロジェクトのディレクトリ構成を見ると、
テストケースクラスが tests
ディレクトリにまとめられていることがわかります。
その中のディレクトリの構造は、テスト対象のシステム (SUT) がある
src
ディレクトリ以下の構造と同じになっています。
src tests `-- Currency.php `-- CurrencyTest.php `-- IntlFormatter.php `-- IntlFormatterTest.php `-- Money.php `-- MoneyTest.php `-- autoload.php
PHPUnit のコマンドラインテストランナーに テストディレクトリの場所を指示してやるだけで、 このライブラリのすべてのテストを実行できます。
phpunit --bootstrap src/autoload.php tests
PHPUnit 6.5.0 by Sebastian Bergmann.
.................................
Time: 636 ms, Memory: 3.50Mb
OK (33 tests, 52 assertions)
PHPUnit のコマンドラインテストランナーでディレクトリを指定すると、
その中の *Test.php
ファイルを見つけて実行します。
tests/CurrencyTest.php
にあるテストケースクラス
CurrencyTest
で宣言されているテストだけを実行するには、
次のコマンドを実行します。
phpunit --bootstrap src/autoload.php tests/CurrencyTest
PHPUnit 6.5.0 by Sebastian Bergmann.
........
Time: 280 ms, Memory: 2.75Mb
OK (8 tests, 8 assertions)
実行したいテストをより細かく指示するには
--filter
オプションを使います。
phpunit --bootstrap src/autoload.php --filter testObjectCanBeConstructedForValidConstructorArgument tests
PHPUnit 6.5.0 by Sebastian Bergmann.
..
Time: 167 ms, Memory: 3.00Mb
OK (2 test, 2 assertions)
この方式の欠点は、テストの実行順を制御できないことです。 そのため、テストの依存性に関する問題を引き起こすことがあります。 「テストの依存性」 を参照ください。 次の節では、テストの実行順序を XML 設定ファイルで明示的に指定する方法を説明します。
PHPUnit の XML 設定ファイル (付録 C)
を使ってテストスイートを構成することもできます。
例 5.1
に、最小限の phpunit.xml
ファイルを示します。これは、
tests
ディレクトリを再帰的に探索して
*Test.php
というファイルにある
*Test
クラスをすべて追加する設定です。
例 5.1: XML 設定ファイルを用いたテストスイートの構成
<phpunit bootstrap="src/autoload.php"> <testsuites> <testsuite name="money"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
--configuration
が設定
されていない 場合は、現在の作業ディレクトリから
phpunit.xml
あるいは
phpunit.xml.dist
を (この順に) 探し、
見つかった場合はそれを自動的に読み込みます。
どのテストを実行するのかを明示的に指定することができます。
例 5.2: XML 設定ファイルを用いたテストスイートの構成
<phpunit bootstrap="src/autoload.php"> <testsuites> <testsuite name="money"> <file>tests/IntlFormatterTest.php</file> <file>tests/MoneyTest.php</file> <file>tests/CurrencyTest.php</file> </testsuite> </testsuites> </phpunit>
PHPUnit は、テストを実行する際に、以下のような追加のチェックをすることができます。
PHPUnit は、何も確かめていないテストを検出することができます。
このチェックを有効にするには、コマンドラインオプション
--report-useless-tests
を使うか、あるいは PHPUnit の XML 設定ファイルで
beStrictAboutTestsThatDoNotTestAnything="true"
を設定します。
何もアサーションを実行していないテストは、このチェックを有効にしておくと、
危険であるとマークされます。モックオブジェクトでの例外や、
@expectedException
などのアノテーションは、アサーションとみなします。
PHPUnit は、意図せずカバーされているコードを検出することができます。
このチェックを有効にするには、コマンドラインオプション
--strict-coverage
を使うか、あるいは PHPUnit の XML 設定ファイルで
checkForUnintentionallyCoveredCode="true"
を設定します。
@covers
アノテーションつきのテストが、
@covers
や @uses
に記されていないコードを実行している場合に、
このチェックを有効にしておくと、危険であるとマークされます。
PHPUnit は、テストの最中の出力を検出することができます。
このチェックを有効にするには、コマンドラインオプション
--disallow-test-output
を使うか、あるいは PHPUnit の XML 設定ファイルで
beStrictAboutOutputDuringTests="true"
を設定します。
テストコードあるいは被テストコードの中で、print
などで何かを出力している場合に、
このチェックを有効にしておくと、危険であるとマークされます。
PHP_Invoker
パッケージがインストールされており、
かつ pcntl
拡張モジュールが利用可能な場合は、
テストの実行時間に制限を設けることができます。
この時間制限を有効にするには、コマンドラインオプション
--enforce-time-limit
を使うか、あるいは PHPUnit の XML 設定ファイルで
beStrictAboutTestSize="true"
を設定します。
@large
とマークされたテストは、
実行時間が 60 秒を超えたら失敗します。
このタイムアウト時間は、XML 設定ファイルの
timeoutForLargeTests
属性で変更できます。
@medium
とマークされたテストは、
実行時間が 10 秒を超えたら失敗します。
このタイムアウト時間は、XML 設定ファイルの
timeoutForMediumTests
属性で変更できます。
@medium
とも @large
ともマークされていないテストは、
@small
とマークされたものとみなします。
このテストは、実行時間が 1 秒を超えたら失敗します。
このタイムアウト時間は、XML 設定ファイルの
timeoutForSmallTests
属性で変更できます。
新しいテストケースクラスを作成する際には、 これから書くべきテストの内容をはっきりさせるために、 まず最初は以下のような空のテストメソッドを書きたくなることでしょう。
public function testSomething() { }
しかし、PHPUnit フレームワークでは空のメソッドを「成功した」
と判断してしまうという問題があります。このような解釈ミスがあると、
テスト結果のレポートが無意味になってしまいます。
そのテストがほんとうに成功したのか、
それともまだテストが実装されていないのかが判断できないからです。
実装していないテストメソッドの中で $this->fail()
をコールするようにしたところで事態は何も変わりません。
こうすると、テストが「失敗した」と判断されてしまいます。
これは未実装のテストが「成功」と判断されてしまうのと同じくらいまずいことです
(訳注: レポートを見ても、そのテストがほんとうに失敗したのか、
まだ実装されていないだけなのかがわかりません)。
テストの成功を青信号、失敗を赤信号と考えるなら、
テストが未完成あるいは未実装であることを表すための黄信号が必要です。
そのような場合に使用するインターフェイスが
PHPUnit_Framework_IncompleteTest
で、
これは未完成あるいは未実装のテストメソッドで発生する例外を表すものです。
このインターフェイスの標準的な実装が
PHPUnit_Framework_IncompleteTestError
です。
例 7.1
では SampleTest
というテストケースクラスを定義しています。
便利なメソッド markTestIncomplete()
(これは、自動的に PHPUnit_Framework_IncompleteTestError
を発生させます) をテストメソッド内でコールすることで、
このメソッドがまだ完成していないことをはっきりさせます。
例 7.1: テストに未完成の印をつける
<?php use PHPUnit\Framework\TestCase; class SampleTest extends TestCase { public function testSomething() { // オプション: お望みなら、ここで何かのテストをしてください。 $this->assertTrue(true, 'これは動いているはずです。'); // ここで処理を止め、テストが未完成であるという印をつけます。 $this->markTestIncomplete( 'このテストは、まだ実装されていません。' ); } } ?>
未完成のテストは、PHPUnit のコマンドライン版テストランナーでは以下のように
I
で表されます。
phpunit --verbose SampleTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
I
Time: 0 seconds, Memory: 3.95Mb
There was 1 incomplete test:
1) SampleTest::testSomething
このテストは、まだ実装されていません。
/home/sb/SampleTest.php:12
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 1, Incomplete: 1.
表 7.1 に、テストを未完成扱いにするための API を示します。
表7.1 未完成のテスト用の API
メソッド | 意味 |
---|---|
void markTestIncomplete() | 現在のテストを未完成扱いにします。 |
void markTestIncomplete(string $message) | 現在のテストを未完成扱いにします。それを説明する文字列として $message を使用します。 |
すべてのテストがあらゆる環境で実行できるわけではありません。 考えてみましょう。たとえば、データベースの抽象化レイヤーを使用しており、 それがさまざまなドライバを使用してさまざまなデータベースシステムを サポートしているとします。MySQL ドライバのテストができるのは、 当然 MySQL サーバが使用できる環境だけです。
例 7.2
に示すテストケースクラス DatabaseTest
には、
テストメソッド testConnection()
が含まれています。
このクラスのテンプレートメソッド setUp()
では、
MySQLi 拡張モジュールが使用可能かを調べたうえで、もし使用できない場合は
markTestSkipped()
メソッドでテストを省略するようにしています。
例 7.2: テストを省略する
<?php use PHPUnit\Framework\TestCase; class DatabaseTest extends TestCase { protected function setUp() { if (!extension_loaded('mysqli')) { $this->markTestSkipped( 'MySQLi 拡張モジュールが使用できません。' ); } } public function testConnection() { // ... } } ?>
飛ばされたテストは、PHPUnit のコマンドライン版テストランナーでは以下のように
S
で表されます。
phpunit --verbose DatabaseTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
S
Time: 0 seconds, Memory: 3.95Mb
There was 1 skipped test:
1) DatabaseTest::testConnection
MySQLi 拡張モジュールが使用できません。
/home/sb/DatabaseTest.php:9
OK, but incomplete or skipped tests!
Tests: 1, Assertions: 0, Skipped: 1.
表 7.2 に、テストを省略するための API を示します。
表7.2 テストを省略するための API
メソッド | 意味 |
---|---|
void markTestSkipped() | 現在のテストを省略扱いにします。 |
void markTestSkipped(string $message) | 現在のテストを省略扱いにします。それを説明する文字列として $message を使用します。 |
ここまでに示したメソッドに加えて、
@requires
アノテーションを使って共通の事前条件を記述することもできます。
表7.3 @requires の例用例
型 | 取り得る値 | 例 | 別の例 |
---|---|---|---|
PHP | PHP のバージョン | @requires PHP 5.3.3 | @requires PHP 7.1-dev |
PHPUnit | PHPUnit のバージョン | @requires PHPUnit 3.6.3 | @requires PHPUnit 4.6 |
OS | PHP_OS にマッチする正規表現 | @requires OS Linux | @requires OS WIN32|WINNT |
function | function_exists に渡せるパラメータ | @requires function imap_open | @requires function ReflectionMethod::setAccessible |
extension | 拡張モジュール名 (オプションでバージョンも指定できる) | @requires extension mysqli | @requires extension redis 2.2.0 |
例 7.3: @requires を使ったテストケースのスキップ
<?php use PHPUnit\Framework\TestCase; /** * @requires extension mysqli */ class DatabaseTest extends TestCase { /** * @requires PHP 5.3 */ public function testConnection() { // このテストには mysqli 拡張モジュールと PHP 5.3 以降が必須です } // ... その他のすべてのテストには mysqli 拡張モジュールが必須です } ?>
特定のバージョンの PHP でしか使えない構文を利用する場合は、 「テストスイート」 にあるように XML 設定ファイルでのバージョン依存のインクルードを検討しましょう。
初級者・中級者向けのユニットテストのサンプルは、 どんな言語を対象としたものであっても、 テストしやすいようなロジックに対してシンプルなテストをしているものばかりです。 データベースを扱う一般的なアプリケーションを考えると、これはまったく現実離れしています。 たとえば WordPress や TYPO3、あるいは Symfony で Doctrine や Propel などを使い始めるとすぐに、 PHPUnit でのテストがやりづらいことを実感するはずです。 データベースとこれらのライブラリが密結合になっているからです。
PHP の pdo
拡張モジュール、そしてデータベースごとの拡張モジュール
(pdo_mysql
など) がインストールされていることを確認しておきましょう。
これらがインストールされていないと、以下のサンプルは動きません。
きっと日々の業務やプロジェクトでも身に覚えがあることでしょう。 自分の持つ PHPUnit に関する知識を駆使して作業を進めようとしたのに、 こんな問題のせいで行き詰ってしまうことが。
テストしたいメソッドがかなり大きめの JOIN 操作を実行し、 データを使って重要な結果を算出している。
ひとつのビジネスロジックの中で SELECT、INSERT、UPDATE そして DELETE を組み合わせて実行している。
ふたつ以上の (おそらくもっと多い) テーブルから初期データを準備しないと そのメソッドのテストができない。
DBUnit 拡張を使うと、テスト用のデータベースのセットアップを単純化でき、 データベース操作後の内容の検証もすることができます。
DBUnit が現在サポートしているのは、MySQL および PostgreSQL、Oracle、SQLite です。 Zend Framework や Doctrine 2 を使うと、IBM DB2 や Microsoft SQL Server のような他のデータベースにもアクセスできます。
ウェブ上にあるユニットテストのサンプルの中にデータベースを扱うものが全く見当たらない理由はなぜか。 それは、データベースを扱うテストは準備するのも保守するのもたいへんだからです。 データベースを使うテストをするには、このようなことに気をつける必要があります。
データベースのスキーマやテーブル
テーブルへの、テストで必要となるレコードの追加
テスト実行後のデータベースの状態の検証
テスト実行ごとのデータベースの後始末
PDO や MySQLi あるいは OCI8 といったデータベース API はどれも使いにくい上に、 こういった処理を自分で書こうとすると長ったらしくなってしまって面倒です。
テストコードはできる限り簡潔に、そして明確に書かねばなりません。その理由は次のとおりです。
製品コードにちょっと手を加えるたびに大量のテストコードを変更する羽目になるのは困る。
数ヵ月後に改めて読み直したときにも 読みやすく理解しやすいテストコードであってほしい。
さらに知っておく必要があることは、 データベースは基本的に、自分のコードへのグローバルな入力変数であるということです。 テストスイート内にあるふたつのテストを同じデータベースに対して実行すると、 おそらくデータを複数回再利用することになります。あるテストが失敗すると それ以降のテストの結果にも影響を及ぼしやすく、テストを進めるのが非常に難しくなります。 先ほど箇条書きでまとめた中の「後始末」こそが、この 「データベースがグローバルな入力になる」 問題を解決するために重要です。
DbUnit を使うと、 データベースのテストにおけるこれらの問題をシンプルにする助けになります。
PHPUnit では助けようにもどうにもならないことが、 データベースのテストはデータベースを使わないものに比べてとても遅くなるという事実です。 テストの実行時間がどれくらいになるかはデータベースとのやりとりの量に依存しますが、 各テストで使うデータの量を少なめにしておいて 可能な限りはデータベースを使わないテストで済ませるようにすれば、 巨大なテストスイートであっても 1 分未満で実行させるのは容易です。
Doctrine 2 プロジェクト がよい例です。 このプロジェクトのテストスイートには現時点で約 1000 件のテストが含まれています。 そのほぼ半数がデータベースを扱うテストですが、 標準的なデスクトップコンピューター上の MySQL を使ってテストスイートを実行しても 15 秒程度でテストが完了します。
Gerard Meszaros は、著書 xUnit Test Patterns でユニットテストを次の四段階に分類しています。
フィクスチャのセットアップ (Setup)
テストしたいシステムの実行 (Exercise)
結果の検証 (Verify)
後始末 (Teardown)
フィクスチャとは?
フィクスチャとは、アプリケーションやデータベースの初期状態のことです。 テストを実行する前に用意します。
データベースをテストするには、少なくとも setup と teardown のときにはテーブルに接続してフィクスチャのクリーンアップや書き込みをしなければなりません。 しかし、データベース拡張には、 データベーステストの四段階を次のようなワークフローに振り向ける十分な理由があります。 このフローは、個々のテストに対して実行します。
データベースを扱う最初のテストというのはいつでも存在します。 実際のところ、そのときテーブルにデータが存在するのかどうかはわかりません。 PHPUnit は指定した全テーブルに対して TRUNCATE を実行し、 テーブルの中身を空にします。
通常、PHPUnit を使うテストケースでは
PHPUnit\Framework\TestCase
クラスを継承してこのようにします。
<?php use PHPUnit\Framework\TestCase; class MyTest extends TestCase { public function testCalculate() { $this->assertEquals(2, 1 + 1); } } ?>
テストコードで Database Extension を使う場合は少しだけ複雑になり、
別の抽象テストケースを継承しなければなりません。そして、二つの抽象メソッド
getConnection()
と
getDataSet()
を実装します。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class MyGuestbookTest extends TestCase { use TestCaseTrait; /** * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection */ public function getConnection() { $pdo = new PDO('sqlite::memory:'); return $this->createDefaultDBConnection($pdo, ':memory:'); } /** * @return PHPUnit_Extensions_Database_DataSet_IDataSet */ public function getDataSet() { return $this->createFlatXMLDataSet(dirname(__FILE__).'/_files/guestbook-seed.xml'); } } ?>
クリーンアップとフィクスチャの読み込みの機能を動かすには、 PHPUnit Database Extension からデータベース接続にアクセスできなければなりません。 データベース接続の抽象化には PDO ライブラリを使います。 重要なのは、PHPUnit のデータベース拡張を使うためだけに わざわざアプリケーションを PDO ベースにする必要はないということです。 この接続を使うのは、単にクリーンアップとフィクスチャの準備のためだけです。
先ほどの例では、インメモリの SQLite 接続を作って
createDefaultDBConnection
メソッドに渡しました。
このメソッドは PDO のインスタンスをラップしたもので、二番目のパラメータ
(データベース名) に非常にシンプルなデータベース接続の抽象化レイヤーを渡します。このパラメータの型は
PHPUnit_Extensions_Database_DB_IDatabaseConnection
です。
「データベース接続の使い方」で、このインターフェイスの API と、その活用法について説明します。
getDataSet()
メソッドで定義するのは、
個々のテストを実行する前のデータベースの初期状態がどうあるべきかということです。
データベースの状態の抽象化は DataSet と DataTable
という概念を使って行い、これらをそれぞれ
PHPUnit_Extensions_Database_DataSet_IDataSet
および
PHPUnit_Extensions_Database_DataSet_IDataTable
というインターフェイスで表します。次の節でこれらの概念を詳しく説明し、
これをデータベースのテストに使うと何がうれしいのかについても示します。
実装するために最低限知っておくべきことは、
getDataSet()
メソッドがコールされるのが
setUp()
の中で一度だけであり、
ここでフィクスチャのデータセットを取得してデータベースに挿入するということです。
先ほどの例では、ファクトリメソッド
createFlatXMLDataSet($filename)
を使って XML 形式のデータセットを表しました。
PHPUnit は、テストの実行前にデータベーススキーマ (すべてのテーブル、トリガー、シーケンス、ビューを含むもの) ができあがっていることを想定しています。つまり開発者としては、 テストスイートを実行する前にデータベースを正しく準備しておかねばならないということです。
データベースのテストにおけるこの事前条件を満たす方法には、次のようなものがあります。
インメモリの SQLite ではなく永続化したデータベースを使うのなら、 最初に一度 phpMyAdmin (MySQL の場合) などのツールでデータベースを用意しておけば、 あとはテストを実行するたびにそれを再利用できます。
Doctrine 2 や Propel といったライブラリを使っている場合は、その API を使えばテストの実行前に必要なデータベーススキーマを作ることができます。 PHPUnit のブートストラップ 機能を使うと、そのコードをテスト実行時に毎回実行させることもできます。
先の実装例を見ればすぐにわかるでしょうが、
getConnection()
メソッドはきわめて静的なものであり、
さまざまなデータベーステストケースで再利用することができます。
さらに、テストのパフォーマンスを良好に保ちつつデータベースのオーバーヘッドを下げるために、
ちょっとしたリファクタリングを施して汎用的な抽象テストケースを用意しましょう。
このようにしても、テストケースごとに異なるデータフィクスチャを指定することができます。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; abstract class MyApp_Tests_DatabaseTestCase extends TestCase { use TestCaseTrait; // PDO のインスタンス生成は、クリーンアップおよびフィクスチャ読み込みのときに一度だけ static private $pdo = null; // PHPUnit_Extensions_Database_DB_IDatabaseConnection のインスタンス生成は、テストごとに一度だけ private $conn = null; final public function getConnection() { if ($this->conn === null) { if (self::$pdo == null) { self::$pdo = new PDO('sqlite::memory:'); } $this->conn = $this->createDefaultDBConnection(self::$pdo, ':memory:'); } return $this->conn; } } ?>
しかし、これはまだデータベースへの接続情報を PDO 接続の設定にハードコードしてしまっています。 PHPUnit にはさらにすばらしい機能があるので、それを使ってテストケースをより汎用的にしましょう。 XML 設定ファイル を使えば、テストの実行のたびにデータベース接続を設定できます。 まずは 「phpunit.xml」 というファイルをアプリケーションの tests/ ディレクトリに作り、 中身をこのようにします。
<?xml version="1.0" encoding="UTF-8" ?> <phpunit> <php> <var name="DB_DSN" value="mysql:dbname=myguestbook;host=localhost" /> <var name="DB_USER" value="user" /> <var name="DB_PASSWD" value="passwd" /> <var name="DB_DBNAME" value="myguestbook" /> </php> </phpunit>
テストケースはこのように書き直せます。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; abstract class Generic_Tests_DatabaseTestCase extends TestCase { use TestCaseTrait; // PDO のインスタンス生成は、クリーンアップおよびフィクスチャ読み込みのときに一度だけ static private $pdo = null; // PHPUnit_Extensions_Database_DB_IDatabaseConnection のインスタンス生成は、テストごとに一度だけ private $conn = null; final public function getConnection() { if ($this->conn === null) { if (self::$pdo == null) { self::$pdo = new PDO( $GLOBALS['DB_DSN'], $GLOBALS['DB_USER'], $GLOBALS['DB_PASSWD'] ); } $this->conn = $this->createDefaultDBConnection(self::$pdo, $GLOBALS['DB_DBNAME']); } return $this->conn; } } ?>
データベースの設定情報を切り替えてテストスイートを実行するには、 コマンドラインから次のようにします。
user@desktop> phpunit --configuration developer-a.xml MyTests/
user@desktop> phpunit --configuration developer-b.xml MyTests/
データベースのテストを実行するときにターゲットデータベースを切り替えられるようにしておくことは、 開発機で作業をしている場合などは特に重要です。 複数の開発者が同じデータベース接続を使ってデータベースのテストを実行したりすると、 レースコンディション (競合条件) によるテストの失敗が頻発するでしょう。
PHPUnit Database Extension の中心となる概念が データセットとデータテーブルです。まずはこの考え方を理解することが、 PHPUnit でのデータベースのテストをマスターする近道です。 データセットとデータテーブルは、データベースのテーブルや行、 そしてカラムの抽象化レイヤーです。シンプルな API によってデータベースの内容をオブジェクト構造に隠蔽できるだけでなく、 データベース以外のソースによる実装もできるようになっています。
この抽象化を使って、データベースの実際の中身と我々が期待する内容を比較します。 期待する内容は XML や YAML そして CSV などのファイルでも表せますし、 PHP の配列として表すこともできます。 DataSet インターフェイスと DataTable インターフェイスのおかげで、 これらの全く異なる概念のソースをリレーショナルデータベースに見立てて 同様に扱えるようになります。
データベースのアサーションをテストの中で行う流れは、 次のようにシンプルな三段階となります。
ひとつあるいは複数のテーブルをデータベース内から指定する (実際のデータセット)。
期待するデータセットをお好みのフォーマット (YAML, XML など) で用意する。
両者がお互いに等しいことを確認する。
データセットやデータテーブルの PHPUnit Database Extension における使い道は、 何もアサーションだけだというわけではありません。先ほどの節で見たように、 これらを使ってデータベースの初期状態の内容を記述することもできます。 フィクスチャとなるデータセットを Database TestCase で定義すると、それをこのように使うことができます。
データセットで指定したテーブルのすべての行を削除する。
データテーブルのすべての行をデータベースに書き込む。
これら三種類のデータセット/データテーブルが用意されています。
ファイルベースのデータセットやデータテーブル
クエリベースのデータセットやデータテーブル
フィルタ用や合成用のデータセットやデータテーブル
ファイルベースのデータセットやデータテーブルは、 初期状態のフィクスチャを定義したり期待する状態を定義したりするときによく使います。
最も一般的なデータセットは、フラット XML と呼ばれるものです。
これは非常にシンプルな xml 形式で、ルートノード
<dataset>
の中のタグがデータベースのひとつの行を表します。
テーブルと同じ名前のタグが追加する行を表し、
その属性がカラムを表します。
単純な掲示板アプリケーションの例は、このようになります。
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" /> <guestbook id="2" content="I like it!" user="nancy" created="2010-04-26 12:14:20" /> </dataset>
見るからに書きやすそうですね。この場合は
<guestbook>
がテーブル名で、
2 行が追加されます。そして、四つのカラム 「id」、
「content」、「user」 そして
「created」 に、それぞれ対応する値が設定されています。
しかし、この単純性による問題もあります。
たとえば、先ほどの例で空のテーブルはどうやって指定すればいいのかがよくわかりません。 実は、何も属性を指定せずにテーブルと同じ名前のタグを追加すれば、空のテーブルを表すことができます。 空の guestbook テーブルを表すフラット xml ファイルは、このようになります。
<?xml version="1.0" ?> <dataset> <guestbook /> </dataset>
フラット xml データセットでの NULL 値の処理は、あまりおもしろいものではありません。 ほとんどのデータベースでは、NULL 値と空文字列は別のものとして扱います (例外のひとつは Oracle です) が、これをフラット xml 形式で表すのは困難です。NULL 値を表すには、 行の指定のときに属性を省略します。 この例の掲示板で、匿名の投稿を許可し、そのときには user カラムに NULL を指定することにしましょう。 guestbook テーブルの状態は、このようになります。
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" /> <guestbook id="2" content="I like it!" created="2010-04-26 12:14:20" /> </dataset>
この例では、二番目のエントリが匿名の投稿を表します。 しかし、これはカラムの認識において深刻な問題につながります。 データセットが等しいことを確認するアサーションでは、各データセットでテーブルの持つカラムを指定しなければなりません。 ある属性がデータテーブルのすべての行で NULL だったなら、 Database Extension はそのカラムがテーブルに存在することをどうやって知るというのでしょう?
フラット XML データセットはここで、重大な前提を使っています。 テーブルの最初の行で定義されている属性が、そのテーブルのカラムを定義しているものと見なすのです。 先ほどの例では、guestbook テーブルのカラムが 「id」 と 「content」、「user」 そして 「created」 であると見なすということです。二番目の行には 「user」 が定義されていないので、データベースには NULL を挿入します。
guestbook の最初のエントリをデータセットから削除すると、guestbook テーブルのカラムは 「id」、「content」 そして 「created」 だけになってしまいます。 「user」 が指定されていないからです。
フラット XML データセットを効率的に使うには、NULL 値がからむ場合は 各テーブルの最初の行には NULL を含まないようにします。 それ以降の行では、属性を省略して NULL を表すことができます。 これはあまりスマートなやり方ではありません。 というのも、データベースのアサーションで行の順番が影響してしまうからです。
一方、テーブルのカラムの一部だけをフラット XML データセットで指定すると、 それ以外のカラムにはデフォルト値が設定されます。 そのため、もし省略したカラムの定義が 「NOT NULL DEFAULT NULL」 などの場合はエラーになります。
結論として言えるのは、フラット XML データセットを使うなら NULL 値が不要な場合だけにしておいたほうがよい、ということだけです。
フラット XML データセットのインスタンスを
Database TestCase から作るには、
createFlatXmlDataSet($filename)
メソッドを使います。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class MyTestCase extends TestCase { use TestCaseTrait; public function getDataSet() { return $this->createFlatXmlDataSet('myFlatXmlFixture.xml'); } } ?>
もうひとつ別の構造の XML データセットもあります。これは多少冗長な書き方ですが、
フラット XML データセットにおける NULL の問題は発生しません。
ルートノード <dataset>
の配下に指定できるタグは、
<table>
や
<column>
、<row>
、
<value>
そして
<null />
です。
先に定義した Guestbook のフラット XML と同様のデータセットは、このようになります。
<?xml version="1.0" ?> <dataset> <table name="guestbook"> <column>id</column> <column>content</column> <column>user</column> <column>created</column> <row> <value>1</value> <value>Hello buddy!</value> <value>joe</value> <value>2010-04-24 17:15:23</value> </row> <row> <value>2</value> <value>I like it!</value> <null /> <value>2010-04-26 12:14:20</value> </row> </table> </dataset>
<table>
には name が必須で、
さらにすべてのカラムの名前を定義しなければなりません。
また、ゼロ個以上の <row>
要素を含めることができます。<row>
要素を定義しなければ、そのテーブルが空であることになります。
<value>
タグや
<null />
タグは、先に指定した
column>
要素の順番で指定しなければなりません。
<null />
タグは、
見た目の通り、値が NULL であることを表します。
XML データセットのインスタンスを
Database TestCase から作るには、
createXmlDataSet($filename)
メソッドを使います。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class MyTestCase extends TestCase { use TestCaseTrait; public function getDataSet() { return $this->createXMLDataSet('myXmlFixture.xml'); } } ?>
この新しい XML フォーマットは、
MySQL データベース 専用です。
PHPUnit 3.5 以降で対応します。この形式のファイルを生成するには、
mysqldump
を使います。mysqldump
では CSV データセットも対応していますが、
それとは違ってこの XML 形式の場合はひとつのファイルに複数のテーブルを含めることができます。
この形式のファイルを作るには、
mysqldump
を次のように実行します。
mysqldump --xml -t -u [username] --password=[password] [database] > /path/to/file.xml
このファイルを Database TestCase で使うには、
createMySQLXMLDataSet($filename)
メソッドをコールします。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class MyTestCase extends TestCase { use TestCaseTrait; public function getDataSet() { return $this->createMySQLXMLDataSet('/path/to/file.xml'); } } ?>
あるいは、YAML データセットを使って、guestbook の例をこのように表すこともできます。
guestbook: - id: 1 content: "Hello buddy!" user: "joe" created: 2010-04-24 17:15:23 - id: 2 content: "I like it!" user: created: 2010-04-26 12:14:20
これは、シンプルで便利なうえに、さらにフラット XML
データセットが持つ NULL の問題も解決しています。
NULL を YAML で表すには、単にカラム名の後に何も値を指定しなければよいのです。
空文字列を指定する場合は
column1: ""
のようにします。
YAML Dataset 用のファクトリーメソッドは今のところ Database TestCase に存在しないので、手動でインスタンスを生成しなければなりません。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; use PHPUnit\DbUnit\DataSet\YamlDataSet; class YamlGuestbookTest extends TestCase { use TestCaseTrait; protected function getDataSet() { return new YamlDataSet(dirname(__FILE__)."/_files/guestbook.yml"); } } ?>
さらにもうひとつのファイルベースのデータセットとして、CSV ファイルを使ったものもあります。データセット内の各テーブルを、 それぞれ単一の CSV ファイルとして扱います。 guestbook の例では、このようなファイル guestbook-table.csv を定義します。
id,content,user,created 1,"Hello buddy!","joe","2010-04-24 17:15:23" 2,"I like it!","nancy","2010-04-26 12:14:20"
この形式は Excel や OpenOffice で編集できるという点で非常に便利ですが、 CSV データセットでは NULL 値を指定することができません。 空のカラムは、データベースのデフォルトに基づいた空の値として扱われます。
CSV データセットを作るには、このようにします。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; use PHPUnit\DbUnit\DataSet\CsvDataSet; class CsvGuestbookTest extends TestCase { use TestCaseTrait; protected function getDataSet() { $dataSet = new CsvDataSet(); $dataSet->addTable('guestbook', dirname(__FILE__)."/_files/guestbook.csv"); return $dataSet; } } ?>
PHPUnit の Database Extension のバージョン 1.3.2 以降では、 配列ベースのデータセットが使えます。 guestbook の例だと、このようになります。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ArrayGuestbookTest extends TestCase { use TestCaseTrait; protected function getDataSet() { return new MyApp_DbUnit_ArrayDataSet( [ 'guestbook' => [ [ 'id' => 1, 'content' => 'Hello buddy!', 'user' => 'joe', 'created' => '2010-04-24 17:15:23' ], [ 'id' => 2, 'content' => 'I like it!', 'user' => null, 'created' => '2010-04-26 12:14:20' ], ], ] ); } } ?>
PHP の DataSet には、これまでのファイルベースのデータセットに比べて明らかな利点があります。
PHP の配列は NULL
値を扱える。
アサーション用に新たなファイルを用意する必要がなく、 直接テストケース内で指定できる。
このデータセットでは、フラット XML や CSV そして YAML データセットと同様に、最初に指定した行のキーがテーブルのカラム名を表します。 つまり、先ほどの例だと 「id」、 「content」、「user」 そして 「created」 です。
このデータセットの実装は、シンプルでわかりやすいものです。
<?php class MyApp_DbUnit_ArrayDataSet extends PHPUnit_Extensions_Database_DataSet_AbstractDataSet { /** * @var array */ protected $tables = []; /** * @param array $data */ public function __construct(array $data) { foreach ($data AS $tableName => $rows) { $columns = []; if (isset($rows[0])) { $columns = array_keys($rows[0]); } $metaData = new PHPUnit_Extensions_Database_DataSet_DefaultTableMetaData($tableName, $columns); $table = new PHPUnit_Extensions_Database_DataSet_DefaultTable($metaData); foreach ($rows AS $row) { $table->addRow($row); } $this->tables[$tableName] = $table; } } protected function createIterator($reverse = false) { return new PHPUnit_Extensions_Database_DataSet_DefaultTableIterator($this->tables, $reverse); } public function getTable($tableName) { if (!isset($this->tables[$tableName])) { throw new InvalidArgumentException("$tableName is not a table in the current database."); } return $this->tables[$tableName]; } } ?>
データベースのアサーションでは、ファイルベースのデータセットだけでなく Query/SQL ベースのデータセットでデータベースの実際の中身を含むものが必要になることもあります。 そんなときに使えるのが Query データセットです。
<?php $ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); $ds->addTable('guestbook'); ?>
単にテーブル名だけを指定してテーブルを追加すると、 次のクエリを実行してデータテーブルを定義したのと同じ意味になります。
<?php $ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); $ds->addTable('guestbook', 'SELECT * FROM guestbook'); ?>
ここでテーブルに対して任意のクエリを実行して、
取得する行や列を絞り込んだり
ORDER BY
句を追加したりすることができます。
<?php $ds = new PHPUnit_Extensions_Database_DataSet_QueryDataSet($this->getConnection()); $ds->addTable('guestbook', 'SELECT id, content FROM guestbook ORDER BY created DESC'); ?>
データベースアサーションの節で、このデータセットを使う方法をより詳しく説明しています。
テスト用のデータベース接続にアクセスすると、 自動的にすべてのテーブルとその中身を含むデータセットを生成します。 接続先のデータベースは、接続用のファクトリーメソッドの二番目のパラメータで指定します。
データベース全体の完全なデータセットを作るには
testGuestbook()
のようにします。
ホワイトリスト形式で指定したテーブルだけに絞り込むには
testFilteredGuestbook()
メソッドのようにします。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class MySqlGuestbookTest extends TestCase { use TestCaseTrait; /** * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection */ public function getConnection() { $database = 'my_database'; $user = 'my_user'; $password = 'my_password'; $pdo = new PDO('mysql:...', $user, $password); return $this->createDefaultDBConnection($pdo, $database); } public function testGuestbook() { $dataSet = $this->getConnection()->createDataSet(); // ... } public function testFilteredGuestbook() { $tableNames = ['guestbook']; $dataSet = $this->getConnection()->createDataSet($tableNames); // ... } } ?>
これまで、フラット XML や CSV のデータセットには NULL の問題があると説明してきました。 しかし、ちょっとわかりにくい回避策を使えばこれらのデータセットで NULL を扱うこともできます。
Replacement データセットは既存のデータセットに対するデコレータで、 データセットの任意のカラムの値を別の値で置換することができます。 guestbook の例で NULL 値を扱うには、このようなファイルを作ります。
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" /> <guestbook id="2" content="I like it!" user="##NULL##" created="2010-04-26 12:14:20" /> </dataset>
そして、フラット XML データセットを Replacement データセットでラップします。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ReplacementTest extends TestCase { use TestCaseTrait; public function getDataSet() { $ds = $this->createFlatXmlDataSet('myFlatXmlFixture.xml'); $rds = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($ds); $rds->addFullReplacement('##NULL##', null); return $rds; } } ?>
巨大なフィクスチャファイルを扱うときには、 データセットフィルタをホワイトリストあるいはブラックリストとして使って テーブルやカラムを絞り込んだサブデータセットを作ることができます。 これは、DB データセットと組み合わせて データセットのカラムを絞り込むときに使うと非常に便利です。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class DataSetFilterTest extends TestCase { use TestCaseTrait; public function testIncludeFilteredGuestbook() { $tableNames = ['guestbook']; $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addIncludeTables(['guestbook']); $filterDataSet->setIncludeColumnsForTable('guestbook', ['id', 'content']); // .. } public function testExcludeFilteredGuestbook() { $tableNames = ['guestbook']; $dataSet = $this->getConnection()->createDataSet(); $filterDataSet = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($dataSet); $filterDataSet->addExcludeTables(['foo', 'bar', 'baz']); // only keep the guestbook table! $filterDataSet->setExcludeColumnsForTable('guestbook', ['user', 'created']); // .. } } ?>
注意 ひとつのテーブルに対してカラムの exclude フィルタと include フィルタを同時に使うことはできません。 さらに、テーブルのホワイトリストとブラックリストはどちらか一方しか指定できません。
Composite データセットは、既存の複数のデータセットをひとつにまとめるときに有用です。 複数のデータセットに同名のテーブルが含まれる場合は、 指定した順で行を連結します。 たとえば、このようなふたつのデータセットがあるものとしましょう。 まずは fixture1.xml。
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" /> </dataset>
そして fixture2.xml。
<?xml version="1.0" ?> <dataset> <guestbook id="2" content="I like it!" user="##NULL##" created="2010-04-26 12:14:20" /> </dataset>
Composite データセットを使えば、両方のフィクスチャファイルをまとめることができます。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class CompositeTest extends TestCase { use TestCaseTrait; public function getDataSet() { $ds1 = $this->createFlatXmlDataSet('fixture1.xml'); $ds2 = $this->createFlatXmlDataSet('fixture2.xml'); $compositeDs = new PHPUnit_Extensions_Database_DataSet_CompositeDataSet(); $compositeDs->addDataSet($ds1); $compositeDs->addDataSet($ds2); return $compositeDs; } } ?>
フィクスチャを準備するとき、PHPUnit の Database Extension はフィクスチャ内で定義された順に行を追加していきます。 データベースのスキーマ定義で外部キーを使っている場合は、 外部キー制約に違反しないような順番でテーブルを指定しなければなりません。
データセットやデータテーブルの内部構造を理解するために、 まずはデータセットのインターフェイスから見ていきましょう。 自分でデータセットやデータテーブルを作るつもりのない人は、 読み飛ばしてもかまいません。
<?php interface PHPUnit_Extensions_Database_DataSet_IDataSet extends IteratorAggregate { public function getTableNames(); public function getTableMetaData($tableName); public function getTable($tableName); public function assertEquals(PHPUnit_Extensions_Database_DataSet_IDataSet $other); public function getReverseIterator(); } ?>
公開インターフェイスは、データベーステストケースの
assertDataSetsEqual()
アサーションで内部的に使われており、これでデータセットの内容を検証します。
IDataSet は IteratorAggregate
インターフェイスから getIterator()
メソッドを継承しており、これを使ってデータセット内の全テーブルの反復処理を行います。
リバースイテレータを使うと、PHPUnit で作ったテーブルのデータの切り詰めを、
テーブルを作ったときと逆の順番で行えます。これで、外部キー制約に違反せずに済むようになります。
テーブルのインスタンスをデータセットに追加するには、
実装によってさまざまな手法があります。たとえば
YamlDataSet
や
XmlDataSet
そして FlatXmlDataSet
のようなファイルベースのデータセットでは、
データセットの作成時にソースファイルを使って内部的に追加します。
テーブルは、このようなインターフェイスを使って表します。
<?php interface PHPUnit_Extensions_Database_DataSet_ITable { public function getTableMetaData(); public function getRowCount(); public function getValue($row, $column); public function getRow($row); public function assertEquals(PHPUnit_Extensions_Database_DataSet_ITable $other); } ?>
getTableMetaData()
メソッドは別として、
それ以外のメソッドはまさに文字通りの働きをするものです。
これらのメソッドはすべて、Database Extension のさまざまなアサーションで必須となります。
その詳細は次の章で説明します。
getTableMetaData()
メソッドの返す値は、
PHPUnit_Extensions_Database_DataSet_ITableMetaData
インターフェイスを実装したものでなければなりません。
このインターフェイスはテーブルの構造を表し、このような情報を保持します。
テーブル名。
テーブルのカラム名の配列。並び順は、結果セットに登場する順と同じ。
主キーカラムの配列。
このインターフェイスには、ふたつの TableMetaData のインスタンスがお互いに等しいかを調べるアサーションも定義されています。 これは、データセットの同一性を調べるアサーションで利用するものです。
Connection インターフェイスには、三種類のおもしろいメソッドが用意されています。
このインターフェイスは、データベーステストケースの
getConnection()
メソッドが返すものです。
<?php interface PHPUnit_Extensions_Database_DB_IDatabaseConnection { public function createDataSet(Array $tableNames = NULL); public function createQueryTable($resultName, $sql); public function getRowCount($tableName, $whereClause = NULL); // ... } ?>
createDataSet()
メソッドは、Database
(DB) データセットを作ります。これは、データセットの実装の節で説明したものです。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ConnectionTest extends TestCase { use TestCaseTrait; public function testCreateDataSet() { $tableNames = ['guestbook']; $dataSet = $this->getConnection()->createDataSet(); } } ?>
createQueryTable()
メソッドを使うと、
QuryTable のインスタンスを作れます。引数には、結果の名前と SQL クエリを渡します。
これは、次の節 (データベースアサーション API)
で説明する結果やテーブルのアサーションで有用なメソッドです。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ConnectionTest extends TestCase { use TestCaseTrait; public function testCreateQueryTable() { $tableNames = ['guestbook']; $queryTable = $this->getConnection()->createQueryTable('guestbook', 'SELECT * FROM guestbook'); } } ?>
getRowCount()
は、
テーブル内の行数を手軽に取得するためのメソッドです。
オプションで、where 句によるフィルタリングもできます。
これを使えば、シンプルな同一性のアサーションが可能です。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ConnectionTest extends TestCase { use TestCaseTrait; public function testGetRowCount() { $this->assertEquals(2, $this->getConnection()->getRowCount('guestbook')); } } ?>
テストツール用として、Database Extension ではいくつかのアサーションを提供しています。 これらを使えば、データベースやテーブルの現在の状態 そしてテーブルの行数を検証できます。この節では、 これらの機能の詳細を説明します。
テーブルの行数が特定の値であるかどうかを調べられれば便利なことがよくあります。 これは、接続 API を使ってちょっとしたコードを書かなくとも簡単に実現できます。 guestbook に行を追加した後で、初期登録した 2 エントリ以外にもう一行増えて 3 行になっていることを調べるには、このようにします。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class GuestbookTest extends TestCase { use TestCaseTrait; public function testAddEntry() { $this->assertEquals(2, $this->getConnection()->getRowCount('guestbook'), "Pre-Condition"); $guestbook = new Guestbook(); $guestbook->addEntry("suzy", "Hello world!"); $this->assertEquals(3, $this->getConnection()->getRowCount('guestbook'), "Inserting failed"); } } ?>
先ほどのアサーションも有用ですが、本当にチェックしたいのは、 すべての値が正しいカラムにきちんと登録されたかどうかです。 これは、テーブルのアサーションで実現します。
そのために、QueryTable のインスタンスを定義しました。 テーブル名と SQL クエリからその内容を取得し、 それをファイルベースあるいは配列ベースのデータセットと比較します。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class GuestbookTest extends TestCase { use TestCaseTrait; public function testAddEntry() { $guestbook = new Guestbook(); $guestbook->addEntry("suzy", "Hello world!"); $queryTable = $this->getConnection()->createQueryTable( 'guestbook', 'SELECT * FROM guestbook' ); $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml") ->getTable("guestbook"); $this->assertTablesEqual($expectedTable, $queryTable); } } ?>
さて次に、このアサーションに使うフラット XML ファイル expectedBook.xml を用意しましょう。
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Hello buddy!" user="joe" created="2010-04-24 17:15:23" /> <guestbook id="2" content="I like it!" user="nancy" created="2010-04-26 12:14:20" /> <guestbook id="3" content="Hello world!" user="suzy" created="2010-05-01 21:47:08" /> </dataset>
残念ながら、このアサーションが成功するのは、ちょうど 2010–05–01 21:47:08 に実行したときだけになります。 日付はデータベースのテストでいつも問題になるものなので、それを回避する手段として 「created」 カラムをアサーションで無視させることができます。
調整後のフラット XML ファイル expectedBook.xml はこのようになり、これでアサーションを通過させることができます。
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Hello buddy!" user="joe" /> <guestbook id="2" content="I like it!" user="nancy" /> <guestbook id="3" content="Hello world!" user="suzy" /> </dataset>
QueryTable の呼び出しも修正しなければなりません。
<?php $queryTable = $this->getConnection()->createQueryTable( 'guestbook', 'SELECT id, content, user FROM guestbook' ); ?>
複雑なクエリの結果に対するアサーションも、 QueryTable 方式で可能です。単に結果の名前とクエリを指定して、 それをデータセットと比較すればよいのです。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ComplexQueryTest extends TestCase { use TestCaseTrait; public function testComplexQuery() { $queryTable = $this->getConnection()->createQueryTable( 'myComplexQuery', 'SELECT complexQuery...' ); $expectedTable = $this->createFlatXmlDataSet("complexQueryAssertion.xml") ->getTable("myComplexQuery"); $this->assertTablesEqual($expectedTable, $queryTable); } } ?>
もちろん、複数のテーブルの状態を一度に確かめたり クエリデータセットをファイルベースのデータセットと比較したりすることも可能です。 データセットのアサーションには二通りの方法があります。
接続の Database (DB) データセットを使い、 それをファイルベースのデータセットと比較する。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class DataSetAssertionsTest extends TestCase { use TestCaseTrait; public function testCreateDataSetAssertion() { $dataSet = $this->getConnection()->createDataSet(['guestbook']); $expectedDataSet = $this->createFlatXmlDataSet('guestbook.xml'); $this->assertDataSetsEqual($expectedDataSet, $dataSet); } } ?>
データセットを自分で作ることもできます。
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class DataSetAssertionsTest extends TestCase { use TestCaseTrait; public function testManualDataSetAssertion() { $dataSet = new PHPUnit_Extensions_Database_DataSet_QueryDataSet(); $dataSet->addTable('guestbook', 'SELECT id, content, user FROM guestbook'); // additional tables $expectedDataSet = $this->createFlatXmlDataSet('guestbook.xml'); $this->assertDataSetsEqual($expectedDataSet, $dataSet); } } ?>
いいえ。PHPUnit は、テストスイートの開始時にすべてのデータベースオブジェクトが存在することを前提とします。 データベースやテーブル、シーケンス、トリガー、そしてビューなどは、 テストスイートを実行する前に作っておく必要があります。
Doctrine 2 や eZ Components の強力なツールを使えば、定義済みのデータ構造からデータベーススキーマを作成できます。 しかし、これらを使うには PHPUnit extension にフックで組み込まねばなりません。 そうしないと、テストスイートを実行する前にデータベースの自動再作成ができなくなります。
各テストの実行後はデータベースをクリアするので、 テストを実行するたびにデータベースを再作成する必要はありません。 事前に作ったデータベースをずっと使いまわすことができます。
いいえ。PDO が必要なのは、フィクスチャの準備や後始末とアサーションのときだけです。 テスト対象のコード内では、なんでもお好みの方法でデータベースにアクセスできます。
テストケースの getConnection()
メソッドで作った
PDO インスタンスをキャッシュしていなければ、
データベースを使うテストを実行するたびにデータベースへの接続の数は増加し続けます。
デフォルトの設定では MySQL が受け付ける同時接続は 100 までであり、
他のデータベースにも同様の接続数制限があります。
「自前でのデータベーステストケースの抽象化」 に、このエラーを回避する方法を示しています。 ひとつの PDO インスタンスをキャッシュして、すべてのテストで使いまわす方法です。
Gerard Meszaros は、テストダブルの概念を [Meszaros2007] でこのように述べています。
PHPUnit の createMock($type)
メソッドや getMockBuilder($type)
メソッドを使うと、
指定した元インターフェイス (あるいは元クラス) のテストダブルとして振る舞うオブジェクトを自動的に生成することができます。
このテストダブルオブジェクトは、元のオブジェクトを要するすべての場面で使うことができます。
createMock($type)
メソッドは、指定した型 (インターフェイスやクラス)
のテストダブルオブジェクトをその場で返します。
テストダブルの作成は、デフォルトではベストプラクティスに沿って行われます
(元クラスの __construct()
や __clone()
は実行しません)。また、テストダブルのメソッドに渡された引数はクローンされません。
デフォルトと異なる挙動を求める場合は、
getMockBuilder($type)
メソッドを用いてテストダブルの生成処理をカスタマイズする必要があります。
デフォルトでは、元クラスのすべてのメソッドが置き換えられて、
(元のメソッドは呼び出さずに) 単に null
を返すだけのダミー実装になります。たとえば
will($this->returnValue())
メソッドを使うと、
ダミー実装がコールされたときに値を返すよう設定することができます。
final
, private
および
static
メソッドのスタブやモックは作れないことに注意しましょう。
PHPUnit のテストダブル機能ではこれらを無視し、元のメソッドの振る舞いをそのまま維持します。
実際のオブジェクトを置き換えて、 設定した何らかの値を (オプションで) 返すようなテストダブルのことを スタブ といいます。 スタブ を使うと、 「SUT が依存している実際のコンポーネントを置き換え、 SUT の入力を間接的にコントロールできるようにすることができます。 これにより、SUT が他の何者も実行しないことを強制させることができます。」
例 9.2
に、スタブメソッドの作成と返り値の設定の方法を示します。まず、
PHPUnit\Framework\TestCase
クラスの
createMock()
メソッドを用いて
SomeClass
オブジェクトのスタブを作成します
(例 9.1)。
次に、PHPUnit が提供する、いわゆる
Fluent Interface
(流れるようなインターフェイス)
を用いてスタブの振る舞いを指定します。簡単に言うと、
いくつもの一時オブジェクトを作成して、
それらを連結するといった操作は必要ないということです。
そのかわりに、例にあるようにメソッドの呼び出しを連結します。
このほうが、より読みやすく "流れるような" コードとなります。
例 9.1: スタブを作りたいクラス
<?php use PHPUnit\Framework\TestCase; class SomeClass { public function doSomething() { // なにかをします } } ?>
例 9.2: メソッドに固定値を返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->willReturn('foo'); // $stub->doSomething() をコールすると // 'foo' を返すようになります $this->assertEquals('foo', $stub->doSomething()); } } ?>
この例がきちんと動作するのは、元のクラスで "method" という名前のメソッドが宣言されていない場合だけです。
元のクラスで "method" という名前のメソッドが宣言されている場合は、
$stub->expects($this->any())->method('doSomething')->willReturn('foo');
としなければいけません。
舞台裏では、createMock()
メソッドが使われたときに
PHPUnit が自動的に、求める振る舞いを実装した新たな PHP のクラスを生成しています。
例 9.3 に例を示します。
これは、モックビルダーの流れるようなインターフェイスを使って、テストダブルの作成方法を設定するものです。
このテストダブルで使っている設定は、createMock()
がデフォルトで使用するベストプラクティスと同じです。
例 9.3: モックビルダー API を使った、生成されるテストダブルクラスの変更
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testStub() { // SomeClass クラスのスタブを作成します $stub = $this->getMockBuilder($originalClassName) ->disableOriginalConstructor() ->disableOriginalClone() ->disableArgumentCloning() ->disallowMockingUnknownTypes() ->getMock(); // スタブの設定を行います $stub->method('doSomething') ->willReturn('foo'); // $stub->doSomething() をコールすると // 'foo' を返すようになります $this->assertEquals('foo', $stub->doSomething()); } } ?>
ここまでの例では、
willReturn($value)
を使ってシンプルな値を返していました。
この構文は、
will($this->returnValue($value))
と同じ意味です。
この長い構文での検証を使うと、より複雑な動きをするスタブも作れます。
時には、メソッドをコールした際の引数のひとつを
(そのまま) スタブメソッドコールの返り値としたいこともあるでしょう。
例 9.4 は、
returnValue()
のかわりに
returnArgument()
を用いてこれを実現する例です。
例 9.4: メソッドに引数のひとつを返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnArgumentStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->returnArgument(0)); // $stub->doSomething('foo') は 'foo' を返します $this->assertEquals('foo', $stub->doSomething('foo')); // $stub->doSomething('bar') は 'bar' を返します $this->assertEquals('bar', $stub->doSomething('bar')); } } ?>
流れるようなインターフェイスをテストするときには、
スタブメソッドがオブジェクト自身への参照を返すようにできると便利です。
例 9.5 は、
returnSelf()
を使ってこれを実現する例です。
例 9.5: スタブオブジェクトへの参照を返すメソッドのスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnSelf() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->returnSelf()); // $stub->doSomething() は $stub を返します $this->assertSame($stub, $stub->doSomething()); } } ?>
スタブメソッドをコールした結果として、
定義済みの引数リストにあわせて異なる値を返さなければならないこともあるでしょう。
returnValueMap()
を使えば、
マップを作って引数と関連付け、それを返り値に対応させることができます。
例 9.6 を参照ください。
例 9.6: メソッドにマップからの値を返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnValueMapStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // 値を返すための、引数のマップを作製します $map = [ ['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'] ]; // スタブの設定を行います $stub->method('doSomething') ->will($this->returnValueMap($map)); // $stub->doSomething() は、渡した引数に応じて異なる値を返します $this->assertEquals('d', $stub->doSomething('a', 'b', 'c')); $this->assertEquals('h', $stub->doSomething('e', 'f', 'g')); } } ?>
スタブメソッドをコールした結果として固定値
(returnValue()
を参照ください) や (不変の) 引数
(returnArgument()
を参照ください)
ではなく計算した値を返したい場合は、
returnCallback()
を使用します。
これは、スタブメソッドからコールバック関数やメソッドの結果を返させます。
例 9.7
を参照ください。
例 9.7: メソッドにコールバックからの値を返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testReturnCallbackStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->returnCallback('str_rot13')); // $stub->doSomething($argument) は str_rot13($argument) を返します $this->assertEquals('fbzrguvat', $stub->doSomething('something')); } } ?>
コールバックメソッドを設定するよりももう少しシンプルな方法として、
希望する返り値のリストを指定することもできます。この場合に使うのは
onConsecutiveCalls()
メソッドです。
例 9.8
の例を参照ください。
例 9.8: メソッドに、リストで指定した値をその順で返させるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testOnConsecutiveCallsStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->onConsecutiveCalls(2, 3, 5, 7)); // $stub->doSomething() は毎回異なる値を返します $this->assertEquals(2, $stub->doSomething()); $this->assertEquals(3, $stub->doSomething()); $this->assertEquals(5, $stub->doSomething()); } } ?>
値を返すのではなく、スタブメソッドで例外を発生させることもできます。
例 9.9
に、throwException()
でこれを行う方法を示します。
例 9.9: メソッドに例外をスローさせるスタブ
<?php use PHPUnit\Framework\TestCase; class StubTest extends TestCase { public function testThrowExceptionStub() { // SomeClass クラスのスタブを作成します $stub = $this->createMock(SomeClass::class); // スタブの設定を行います $stub->method('doSomething') ->will($this->throwException(new Exception)); // $stub->doSomething() は例外をスローします $stub->doSomething(); } } ?>
また、スタブを使用することで、よりよい設計を行うことができるようにもなります。
あちこちで使用されているリソースを単一の窓口 (façade : ファサード)
経由でアクセスするようにすることで、
それを簡単にスタブに置き換えられるようになります。例えば、
データベースへのアクセスのコードをそこらじゅうにちりばめるのではなく、
その代わりに IDatabase
インターフェイスを実装した単一の
Database
オブジェクトを使用するようにします。すると、
IDatabase
を実装したスタブを作成することで、
それをテストに使用できるようになるのです。同時に、
テストを行う際にスタブデータベースを使用するか
本物のデータベースを使用するかを選択できるようになります。
つまり開発時にはローカル環境でテストし、
統合テスト時には実際のデータベースでテストするといったことができるようになるのです。
スタブ化しなければならない機能は、たいてい同一オブジェクト内で密結合しています。 この機能ををひとつの結合したインターフェイスにまとめることで、 システムのそれ以外の部分との結合を緩やかにすることができます。
実際のオブジェクトを置き換えて、 (メソッドがコールされたことなどの) 期待する内容を検証するテストダブルのことを モック といいます。
モックオブジェクト は "SUT の間接的な出力の内容を検証するために使用する観測地点です。 一般的に、モックオブジェクトにはテスト用スタブの機能も含まれます。 まだテストに失敗していない場合に、間接的な出力の検証用の値を SUT に返す機能です。 したがって、モックオブジェクトとは テスト用スタブにアサーション機能を足しただけのものとは異なります。 それ以外の用途にも使うことができます" (Gerard Meszaros)。
そのテストのスコープ内で生成されたモックオブジェクトだけが、PHPUnit による自動検証の対象となります。
たとえば、データプロバイダなどで生成されたモックオブジェクトや
@depends
アノテーションで注入されたオブジェクトについては、PHPUnit では検証しません。
ひとつ例を示します。ここでは、別のオブジェクトを観察している
あるオブジェクトの特定のメソッド (この例では update()
)
が正しくコールされたかどうかを調べるものとします。
例 9.10
は、テスト対象のシステム (SUT) の一部である
Subject
クラスと Observer
クラスのコードです。
例 9.10: テスト対象のシステム (SUT) の一部である Subject クラスと Observer クラス
<?php use PHPUnit\Framework\TestCase; class Subject { protected $observers = []; protected $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } public function attach(Observer $observer) { $this->observers[] = $observer; } public function doSomething() { // なにかをします // ... // なにかしたということをオブザーバに通知します $this->notify('something'); } public function doSomethingBad() { foreach ($this->observers as $observer) { $observer->reportError(42, 'Something bad happened', $this); } } protected function notify($argument) { foreach ($this->observers as $observer) { $observer->update($argument); } } // その他のメソッド } class Observer { public function update($argument) { // なにかをします } public function reportError($errorCode, $errorMessage, Subject $subject) { // なにかをします } // その他のメソッド } ?>
例 9.11
では、モックオブジェクトを作成して
Subject
オブジェクトと Observer
オブジェクトの対話をテストする方法を説明します。
まず
PHPUnit\Framework\TestCase
クラスの
getMockBuilder()
メソッドを使用して Observer
のモックオブジェクトを作成します。
getMock()
メソッドの二番目の (オプションの)
パラメータに配列を指定しているので、Observer
クラスの中の update()
メソッドについてのみモック実装が作成されます。
あるメソッドがコールされたのかどうか、そしてどんな引数を渡してコールされたのかを検証したいので、
expects()
メソッドと with()
メソッドを用意しました。
これらを使って、このやりとりがどのように行われるのかを指定します。
例 9.11: あるメソッドが、指定した引数で一度だけコールされることを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testObserversAreUpdated() { // Observer クラスのモックを作成します。 // update() メソッドのみのモックです。 $observer = $this->getMockBuilder(Observer::class) ->setMethods(['update']) ->getMock(); // update() メソッドが一度だけコールされ、その際の // パラメータは文字列 'something' となる、 // ということを期待しています。 $observer->expects($this->once()) ->method('update') ->with($this->equalTo('something')); // Subject オブジェクトを作成し、Observer オブジェクトの // モックをアタッチします。 $subject = new Subject('My subject'); $subject->attach($observer); // $subject オブジェクトの doSomething() メソッドをコールします。 // これは、Observer オブジェクトのモックの update() メソッドを、 // 文字列 'something' を引数としてコールすることを期待されています。 $subject->doSomething(); } } ?>
with()
メソッドには任意の数の引数を渡すことができます。
これは、モック対象のメソッドの引数の数に対応します。
メソッドの引数に対して、単なるマッチだけでなくより高度な制約を指定することもできます。
例 9.12: メソッドが引数つきでコールされることを、さまざまな制約の下でテストする例
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testErrorReported() { // Observer クラスのモックを作成します。 // reportError() メソッドをモックします。 $observer = $this->getMockBuilder(Observer::class) ->setMethods(['reportError']) ->getMock(); $observer->expects($this->once()) ->method('reportError') ->with( $this->greaterThan(0), $this->stringContains('Something'), $this->anything() ); $subject = new Subject('My subject'); $subject->attach($observer); // doSomethingBad() メソッドは、 // reportError() メソッドを通じてオブザーバにエラーを報告しなければなりません。 $subject->doSomethingBad(); } } ?>
withConsecutive()
メソッドには、
テスト対象の呼び出しにあわせて、引数の配列を好きなだけ渡せます。
個々の配列は制約のリストです。
with()
と同様に、これがモック対象メソッドのそれぞれの引数に対応します。
例 9.13: あるメソッドが、指定した引数つきで 2 回呼び出されることを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class FooTest extends TestCase { public function testFunctionCalledTwoTimesWithSpecificArguments() { $mock = $this->getMockBuilder(stdClass::class) ->setMethods(['set']) ->getMock(); $mock->expects($this->exactly(2)) ->method('set') ->withConsecutive( [$this->equalTo('foo'), $this->greaterThan(0)], [$this->equalTo('bar'), $this->greaterThan(0)] ); $mock->set('foo', 21); $mock->set('bar', 48); } } ?>
callback()
制約を使えば、より複雑な引数の検証ができます。
この制約は、PHP のコールバックを引数として受け取ります。
このコールバックは、検証したい引数を受け取って、検証を通過した場合に true
、
それ以外の場合に false
を返します。
例 9.14: より複雑な引数の検証
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testErrorReported() { // Observer クラスのモックを作成します。 // reportError() メソッドをモックします。 $observer = $this->getMockBuilder(Observer::class) ->setMethods(['reportError']) ->getMock(); $observer->expects($this->once()) ->method('reportError') ->with($this->greaterThan(0), $this->stringContains('Something'), $this->callback(function($subject){ return is_callable([$subject, 'getName']) && $subject->getName() == 'My subject'; })); $subject = new Subject('My subject'); $subject->attach($observer); // doSomethingBad() メソッドは、 // reportError() メソッドを通じてオブザーバにエラーを報告しなければなりません。 $subject->doSomethingBad(); } } ?>
例 9.15: メソッドが一度だけ呼ばれ、同じオブジェクトが渡されたことを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class FooTest extends TestCase { public function testIdenticalObjectPassed() { $expectedObject = new stdClass; $mock = $this->getMockBuilder(stdClass::class) ->setMethods(['foo']) ->getMock(); $mock->expects($this->once()) ->method('foo') ->with($this->identicalTo($expectedObject)); $mock->foo($expectedObject); } } ?>
例 9.16: パラメータのクローンの有効にしたモックオブジェクトの作成
<?php use PHPUnit\Framework\TestCase; class FooTest extends TestCase { public function testIdenticalObjectPassed() { $cloneArguments = true; $mock = $this->getMockBuilder(stdClass::class) ->enableArgumentCloning() ->getMock(); // これでモックがパラメータをクローンするようになり、 // identicalTo 制約は失敗します } } ?>
表 A.1 はメソッドの引数に適用できる制約、そして 表 9.1 は起動回数を指定するために使える matcher です。
表9.1 Matchers
Matcher | 意味 |
---|---|
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any() | 評価対象のメソッドがゼロ回以上実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount never() | 評価対象のメソッドが実行されなかった際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce() | 評価対象のメソッドが最低一回以上実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount once() | 評価対象のメソッドが一度だけ実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count) | 評価対象のメソッドが指定した回数だけ実行された際にマッチするオブジェクトを返します。 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index) | 評価対象のメソッドが $index 回目に実行された際にマッチするオブジェクトを返します。 |
at()
マッチャーのパラメータ $index
は、
指定したモックオブジェクトでの すべてのメソッドの実行
の、ゼロからはじまるインデックスを参照します。
このマッチャーを使うときには注意しましょう。テストが実装の詳細とあまりにも密結合になり、
脆いテストになってしまう可能性があるからです。
最初に説明したとおり、createMock()
メソッドが用いるデフォルトのテストダブル生成方法がニーズを満たさない場合は、
getMockBuilder($type)
メソッドを使えば生成方法をカスタマイズできます。
モックビルダーが提供するメソッドの一覧は、次のとおりです。
setMethods(array $methods)
をモックビルダーオブジェクト上でコールすると、テストダブルで置き換えるメソッドを指定することができます。その他のメソッドの挙動は変更しません。setMethods(NULL)
とすると、どのメソッドも置き換えません。
setConstructorArgs(array $args)
をコールしてパラメータの配列を渡すと、それを元クラスのコンストラクタに渡すことができます (デフォルトのダミー実装では、コンストラクタは置き換えません)。
setMockClassName($name)
を使うと、生成されるテストダブルクラスのクラス名を指定することができます。
disableOriginalConstructor()
を使うと、元クラスのコンストラクタを無効にすることができます。
disableOriginalClone()
を使うと、元クラスのクローンコンストラクタを無効にすることができます。
disableAutoload()
を使うと、テストダブルクラスを生成するときに __autoload()
を無効にすることができます。
Prophecy は 「クセは強いけれども、強力で柔軟な、PHP のオブジェクトモッキングフレームワークです。 最初は phpspec2 のニーズを満たすために作られましたが、今やそれ以外のテスティングフレームワークでも、 最小限の努力で使えるようになりました」 とのことです。
PHPUnit は、Prophecy を使ったテストダブルの作成に標準で対応しています。 例 9.17 は、例 9.11 と同じテストを、Prophecy の理念に沿って表すとどうなるかを示す例です。
例 9.17: あるメソッドが、指定した引数で一度だけコールされることを確かめるテスト
<?php use PHPUnit\Framework\TestCase; class SubjectTest extends TestCase { public function testObserversAreUpdated() { $subject = new Subject('My subject'); // Observer クラスの prophecy を作成します。 $observer = $this->prophesize(Observer::class); // update() メソッドが一度だけコールされ、その際の // パラメータは文字列 'something' となる、 // ということを期待しています。 $observer->update('something')->shouldBeCalled(); // prophecy を公開し、モックオブジェクトを // Subject にアタッチします。 $subject->attach($observer->reveal()); // $subject オブジェクトの doSomething() メソッドをコールします。 // これは、Observer オブジェクトのモックの update() メソッドを、 // 文字列 'something' を引数としてコールすることを期待されています。 $subject->doSomething(); } } ?>
Prophecy を使ってスタブやスパイそしてモックを作ったり設定したり使ったりする方法の詳細については、 その ドキュメント を参照ください。
getMockForTrait()
メソッドは、指定したトレイトを使ったモックオブジェクトを返します。
そのトレイトのすべての抽象メソッドがモックの対象となります。
これを使えば、トレイトの具象メソッドをテストすることができます。
例 9.18: トレイトの具象メソッドのテスト
<?php use PHPUnit\Framework\TestCase; trait AbstractTrait { public function concreteMethod() { return $this->abstractMethod(); } public abstract function abstractMethod(); } class TraitClassTest extends TestCase { public function testConcreteMethod() { $mock = $this->getMockForTrait(AbstractTrait::class); $mock->expects($this->any()) ->method('abstractMethod') ->will($this->returnValue(true)); $this->assertTrue($mock->concreteMethod()); } } ?>
getMockForAbstractClass()
メソッドは、
抽象クラスのモックオブジェクトを返します。
そのクラスのすべての抽象メソッドがモックの対象となります。
これを使えば、抽象クラスにある具象メソッドをテストすることができます。
例 9.19: 抽象クラスの具象メソッドのテスト
<?php use PHPUnit\Framework\TestCase; abstract class AbstractClass { public function concreteMethod() { return $this->abstractMethod(); } public abstract function abstractMethod(); } class AbstractClassTest extends TestCase { public function testConcreteMethod() { $stub = $this->getMockForAbstractClass(AbstractClass::class); $stub->expects($this->any()) ->method('abstractMethod') ->will($this->returnValue(true)); $this->assertTrue($stub->concreteMethod()); } } ?>
ウェブサービスとのやりとりを行うアプリケーションを、
実際にウェブサービスとやりとりすることなくテストしたくなることもあるでしょう。
ウェブサービスのスタブやモックを作りやすくするために getMockFromWsdl()
メソッドが用意されており、これは getMock()
(上を参照ください)
とほぼ同様に使うことができます。唯一の違いは、
getMockFromWsdl()
が返すスタブやモックが WSDL
のウェブサービス記述にもとづくものであるのに対して getMock()
が返すスタブやモックが PHP のクラスやインターフェイスにもとづくものであるという点です。
例 9.20
は、getMockFromWsdl()
を使って
GoogleSearch.wsdl
に記述されたウェブサービスのスタブを作る例です。
例 9.20: ウェブサービスのスタブ
<?php use PHPUnit\Framework\TestCase; class GoogleTest extends TestCase { public function testSearch() { $googleSearch = $this->getMockFromWsdl( 'GoogleSearch.wsdl', 'GoogleSearch' ); $directoryCategory = new stdClass; $directoryCategory->fullViewableName = ''; $directoryCategory->specialEncoding = ''; $element = new stdClass; $element->summary = ''; $element->URL = 'https://phpunit.de/'; $element->snippet = '...'; $element->title = '<b>PHPUnit</b>'; $element->cachedSize = '11k'; $element->relatedInformationPresent = true; $element->hostName = 'phpunit.de'; $element->directoryCategory = $directoryCategory; $element->directoryTitle = ''; $result = new stdClass; $result->documentFiltering = false; $result->searchComments = ''; $result->estimatedTotalResultsCount = 3.9000; $result->estimateIsExact = false; $result->resultElements = [$element]; $result->searchQuery = 'PHPUnit'; $result->startIndex = 1; $result->endIndex = 1; $result->searchTips = ''; $result->directoryCategories = []; $result->searchTime = 0.248822; $googleSearch->expects($this->any()) ->method('doGoogleSearch') ->will($this->returnValue($result)); /** * $googleSearch->doGoogleSearch() はスタブが用意した結果を返し、 * ウェブサービスの doGoogleSearch() が呼び出されることはありません */ $this->assertEquals( $result, $googleSearch->doGoogleSearch( '00000000000000000000000000000000', 'PHPUnit', 0, 1, false, '', false, '', '', '' ) ); } } ?>
vfsStream は 仮想ファイルシステム 用の ストリームラッパー で、 ユニットテストにおいて実際のファイルシステムのモックを作るときに有用です。
Composer
を使ってプロジェクトの依存関係を管理するには、
mikey179/vfsStream
への依存情報をプロジェクトの
composer.json
ファイルに追加します。
次に示すのは最小限の
composer.json
ファイルの例で、
開発時の PHPUnit 4.6 と vfsStream への依存を定義しています。
{ "require-dev": { "phpunit/phpunit": "~4.6", "mikey179/vfsStream": "~1" } }
例 9.21 は、ファイルシステムを操作するクラスの例です。
例 9.21: ファイルシステムを操作するクラス
<?php use PHPUnit\Framework\TestCase; class Example { protected $id; protected $directory; public function __construct($id) { $this->id = $id; } public function setDirectory($directory) { $this->directory = $directory . DIRECTORY_SEPARATOR . $this->id; if (!file_exists($this->directory)) { mkdir($this->directory, 0700, true); } } }?>
vfsStream のような仮想ファイルシステムがなければ、外部への影響なしに
setDirectory()
メソッドを個別にテストすることができません
(例 9.22
を参照ください)。
例 9.22: ファイルシステムを操作するクラスのテスト
<?php use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { protected function setUp() { if (file_exists(dirname(__FILE__) . '/id')) { rmdir(dirname(__FILE__) . '/id'); } } public function testDirectoryIsCreated() { $example = new Example('id'); $this->assertFalse(file_exists(dirname(__FILE__) . '/id')); $example->setDirectory(dirname(__FILE__)); $this->assertTrue(file_exists(dirname(__FILE__) . '/id')); } protected function tearDown() { if (file_exists(dirname(__FILE__) . '/id')) { rmdir(dirname(__FILE__) . '/id'); } } } ?>
この方式には、次のような問題があります。
外部のリソースを使うため、ファイルシステムのテストが断続的になる可能性があります。その結果、テストがあまり当てにならないものになります。
setUp()
と tearDown()
で、テストの前後にそのディレクトリがないことを確認する必要があります。
tearDown()
メソッドを実行する前にテストが異常終了したときに、ファイルシステム上にディレクトリが残ったままとなります。
例 9.23 は、vfsStream を使ってファイルシステムのモックを作成し、 ファイルシステムを操作するクラスのテストを行う例です。
例 9.23: ファイルシステムを操作するクラスのテストにおけるファイルシステムのモックの作成
<?php use PHPUnit\Framework\TestCase; class ExampleTest extends TestCase { public function setUp() { vfsStreamWrapper::register(); vfsStreamWrapper::setRoot(new vfsStreamDirectory('exampleDir')); } public function testDirectoryIsCreated() { $example = new Example('id'); $this->assertFalse(vfsStreamWrapper::getRoot()->hasChild('id')); $example->setDirectory(vfsStream::url('exampleDir')); $this->assertTrue(vfsStreamWrapper::getRoot()->hasChild('id')); } } ?>
この方式には次のような利点があります。
テストが簡潔になります。
vfsStream が、テスト対象のコードから操作するファイルシステム環境を用意してくれるので、開発者はそれを自由に扱えるようになります。
実際のファイルシステムを操作することがなくなるので、tearDown()
メソッドでの後始末が不要になります。
You can always write more tests. However, you will quickly find that only a fraction of the tests you can imagine are actually useful. What you want is to write tests that fail even though you think they should work, or tests that succeed even though you think they should fail. Another way to think of it is in cost/benefit terms. You want to write tests that will pay you back with information. テストはいくらでも書くことができる。でも、じきにわかるだろうが、 きみが考えているテストの中で本当に有用なものはごくわずかだ。 本当に書かなきゃいけないのは、 これは動くだろうと考えているにもかかわらず失敗するテスト。それから、 これは失敗するだろうと考えているにもかかわらず実際は成功するテストだ。 あるいはコストと利益の観点から考えてみてもいいだろう。 きみに何らかの情報を返してくれるテストを書かないとね。 | ||
--Erich Gamma |
開発中のソフトウェアの内部構造を変更し、 わかりやすく変更が簡単なものにする必要が出てきたときのことを考えましょう。 それによってソフトウェアの外部的な振る舞いが変わってしまってはいけません。 この、いわゆる リファクタリング (日本語) を安全に行うにあたり、テストスイートが非常に重要となります。 もしテストスイートがなければ、リファクタリングによってシステムを壊してしまっても あなたはそれに気づかないでしょう。
以下の条件が、あなたのプロジェクトのコードや設計を改善するための助けとなるでしょう。 また、ユニットテストを使用することで、リファクタリングによって振る舞いが変化していないこと・ エラーが発生していないことが確認できます。
すべてのユニットテストが正常に動作すること。
コードが設計指針を満たしていること。
コードに冗長性がないこと。
コードには最小限のクラスおよびメソッドのみが含まれていること。
システムに新しい機能を追加する際には、まず最初にテストを書きます。 そのテストがきちんと実行できるようになった時点で、開発は終了です。 この手法については、次の章で詳しく説明します。
不具合の報告を受けたら、すぐにでもそれを修正したいと思われることでしょう。 しかし、あせって修正しようとしても、経験上なかなかうまくいきません。 不具合を修正したつもりが新たな不具合を引き起こしていたなんてこともありがちですね。
はやる気持ちを抑えて、以下のようにしてみましょう。
不具合を再現できることを確認します。
不具合が発生する最小限のコードを見つけます。例えば、 もしおかしな数値が出力されるのなら、 その数値を計算しているオブジェクトが何なのかを探します。
その不具合のせいで今は失敗する (そして、不具合が修正されたら成功する) テストを書きます。
不具合を修正します。
不具合が再現する最小限のコードを見つける過程で、 不具合の原因がわかるかもしれません。テストを書くことによって、 不具合を真の意味で修正できる可能性が高まるでしょう。なぜなら、 テストを書くことで、将来同じ間違いをする可能性を減らせるからです。 これまでに書いたすべてのテストが、 不注意によって別の問題を発生させる可能性を減らすために役立っているのです。
Unit testing offers many advantages:
Overall, integrated unit testing makes the cost and risk of any individual change smaller. It will allow the project to make [...] major architectural improvements [...] quickly and confidently. ユニットテストには、こんなに多くの利点がある。
まとめよう。ユニットテストをうまく組み込めば、 プログラムを変更する際の手間やリスクをより減らすことになるのだ。 プロジェクトが【中略】のアーキテクチャに関する大きな改修【中略】を素早く、自信を持って行うことを可能にするだろう。 | ||
--Benjamin Smedberg |
In computer science, code coverage is a measure used to describe the degree to which the source code of a program is tested by a particular test suite. A program with high code coverage has been more thoroughly tested and has a lower chance of containing software bugs than a program with low code coverage. | ||
--Wikipedia |
この章では、PHPUnit のコードカバレッジ機能について学びます。 これは、テストを実行したときに、実装コードのどの部分が実行されたかを調べるものです。 PHPUnit のコードカバレッジ解析では PHP_CodeCoverage コンポーネントを使っています。このコンポーネントは、 Xdebug 拡張モジュールが提供するステートメントカバレッジ機能を利用しています。
Xdebug は PHPUnit 本体には組み込まれていません。 テストを実行したときに Xdebug がロードできないという notice が出る場合は、 Xdebug がインストールされていないかあるいはうまく設定できていないのでしょう。 PHPUnit のコードカバレッジ機能を使う前に、まずは Xdebug のインストールガイド を読んでみましょう。
PHPUnit は、HTML ベースのコードカバレッジレポートを生成するだけでなく、 XML ベースのログファイルにコードカバレッジ情報を出力することもできます。 Clover、Crap4J、PHPUnit など、さまざまな形式に対応しています。 また、コードカバレッジ情報をテキスト形式で出力 (そして、標準出力に表示) したり、PHP のコードとして出力して後処理をしたりすることもできます。
コードカバレッジ機能を制御するための コマンドラインスイッチの一覧は、第 3 章 を参照ください。 また、設定項目については 「ログ出力」 を参照ください。
コードカバレッジを計測するための指標には、さまざまなものがあります。
ラインカバレッジ は、 実行可能な行が実行されたかどうかを計測します。
関数・メソッドカバレッジ は、 関数やメソッドが実行されたかどうかを計測します。 PHP_CodeCoverage は、その関数やメソッド内の実行可能な行がすべて実行された場合にのみ、 その関数やメソッドが実行されたとみなします。
クラス・トレイトカバレッジ は、 クラスやトレイトがカバーされたかどうかを計測します。 PHP_CodeCoverage は、クラスやトレイト内のすべてのメソッドがカバーされている場合にのみ、 そのクラスやトレイトがカバーされたとみなします。
オペコードカバレッジ は、関数やメソッドのオペコードが、 テストスイートの実行中に実行されたかどうかを計測します。 通常は、1 行のコードをコンパイルすると、複数のオペコードになります。 ラインカバレッジは、複数のオペコードのうち少なくともひとつが実行された時点で、 その行が実行されたとみなします。
ブランチカバレッジ は、テストスイートの実行中に、
制御構造内の boolean 式が true
あるいは false
のどちらかとして評価されたかどうかを計測します。
パスカバレッジ は、テストスイートの実行中に、 関数やメソッド内で取りうる実行パスが網羅されたかどうかを計測します。 実行パスとは、関数やメソッドに入ってから出るまでの間のルート内での分岐のことです。
Change Risk Anti-Patterns (CRAP) インデックス とは、循環的複雑度と、あるコード単位のコードカバレッジに基づいて算出される指標です。 複雑度が低く、適切なテストカバレッジが達成されているコードは、CRAPインデックスの値が低くなります。 CRAPインデックスを下げるには、テストを書くか、 あるいはリファクタリングでコードの複雑性を下げます。
オペコードカバレッジ、 ブランチカバレッジ、 パスカバレッジ については、 PHP_CodeCoverage ではまだサポートしていません。
ホワイトリスト を設定して、
PHPUnit に対してどのソースコードファイルをコードカバレッジレポートに含めるかを指定する必要があります。
ホワイトリストの設定には、コマンドラインオプションの --whitelist
を使うか、あるいは設定ファイルを使います (「コードカバレッジ対象のファイルのホワイトリスト」 を参照ください)。
オプションで、ホワイトリストに追加したファイルをすべて、コードカバレッジレポートに追加することもできます。
そのためには、PHPUnit の設定で addUncoveredFilesFromWhitelist="true"
とします (「コードカバレッジ対象のファイルのホワイトリスト」 を参照ください)。
こうすれば、まだテストされていないファイルもすべて、レポートに含めることができます。
カバーされていないファイルにおける、実行可能な行についての情報を知りたい場合は、同じく PHPUnit の設定で
processUncoveredFilesFromWhitelist="true"
とします
(「コードカバレッジ対象のファイルのホワイトリスト」 を参照ください)。
processUncoveredFilesFromWhitelist="true"
が設定されている場合のソースコードファイルの読み込みでは、
もしクラスや関数のスコープから外れるコードが含まれていたときに問題が起こる可能性があります。
どうしてもテストができないコードブロックなどを、
コードカバレッジ解析時に無視させたいこともあるでしょう。
PHPUnit でこれを実現するには、
@codeCoverageIgnore
、
@codeCoverageIgnoreStart
および
@codeCoverageIgnoreEnd
アノテーションを
例 11.1
のように使用します。
例 11.1: @codeCoverageIgnore
、@codeCoverageIgnoreStart
および @codeCoverageIgnoreEnd
アノテーションの使用法
<?php use PHPUnit\Framework\TestCase; /** * @codeCoverageIgnore */ class Foo { public function bar() { } } class Bar { /** * @codeCoverageIgnore */ public function foo() { } } if (false) { // @codeCoverageIgnoreStart print '*'; // @codeCoverageIgnoreEnd } exit; // @codeCoverageIgnore ?>
これらのアノテーションを使って無視するよう指定された行は、 もし実行可能なら (たとえ実行されていなくても) 実行されたものとみなされ、 強調表示されません。
テストコードで @covers
アノテーション
(表 B.1)
を参照ください) を使用すると、
そのテストメソッドがどのメソッドをテストしたいのかを指定することができます。
これを指定すると、指定したメソッドのコードカバレッジ情報のみを考慮します。
例 11.2
に例を示します。
例 11.2: どのメソッドを対象とするかを指定したテスト
<?php use PHPUnit\Framework\TestCase; class BankAccountTest extends TestCase { protected $ba; protected function setUp() { $this->ba = new BankAccount; } /** * @covers BankAccount::getBalance */ public function testBalanceIsInitiallyZero() { $this->assertEquals(0, $this->ba->getBalance()); } /** * @covers BankAccount::withdrawMoney */ public function testBalanceCannotBecomeNegative() { try { $this->ba->withdrawMoney(1); } catch (BankAccountException $e) { $this->assertEquals(0, $this->ba->getBalance()); return; } $this->fail(); } /** * @covers BankAccount::depositMoney */ public function testBalanceCannotBecomeNegative2() { try { $this->ba->depositMoney(-1); } catch (BankAccountException $e) { $this->assertEquals(0, $this->ba->getBalance()); return; } $this->fail(); } /** * @covers BankAccount::getBalance * @covers BankAccount::depositMoney * @covers BankAccount::withdrawMoney */ public function testDepositWithdrawMoney() { $this->assertEquals(0, $this->ba->getBalance()); $this->ba->depositMoney(1); $this->assertEquals(1, $this->ba->getBalance()); $this->ba->withdrawMoney(1); $this->assertEquals(0, $this->ba->getBalance()); } } ?>
あるテストが、一切メソッドをカバーしてはならないことも指定できます。
そのために使うのが @coversNothing
アノテーションです。
(「@coversNothing」 を参照ください)。
これは、インテグレーションテストを書く際に
ユニットテストだけのコードカバレッジを生成させたい場合に便利です。
例 11.3: どのメソッドもカバーすべきでないことを指定したテスト
<?php use PHPUnit\Framework\TestCase; class GuestbookIntegrationTest extends PHPUnit_Extensions_Database_TestCase { /** * @coversNothing */ public function testAddEntry() { $guestbook = new Guestbook(); $guestbook->addEntry("suzy", "Hello world!"); $queryTable = $this->getConnection()->createQueryTable( 'guestbook', 'SELECT * FROM guestbook' ); $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml") ->getTable("guestbook"); $this->assertTablesEqual($expectedTable, $queryTable); } } ?>
この節では、コードカバレッジ情報がわかりにくくなってしまうような、 エッジケースについて紹介します。
例 11.4:
<?php use PHPUnit\Framework\TestCase; // カバレッジは「行単位」であって文単位ではないので、 // 一行にまとめられた行はひとつのカバレッジ状態しか持ちません if (false) this_function_call_shows_up_as_covered(); // コードカバレッジの内部動作上、これら 2 行は特別です。 // 次の行は「実行されていない」となります if (false) // 次の行は「実行されている」となります // 実際のところ、ひとつ上の if 文のカバレッジ情報がここに表示されることになるからです! will_also_show_up_as_covered(); // これを避けるには、必ず波括弧を使わなければなりません if (false) { this_call_will_never_show_up_as_covered(); } ?>
自動テストに慣れてくると、 ほかの目的のためにもテストを使いたくなってくることでしょう。 ここではそんな例を説明します。
一般的に、エクストリームプログラミングのようなアジャイルプロセスを採用しているプロジェクトでは、 ドキュメントの内容が実際の設計やコードに追いついていないことが多いものです。 エクストリームプログラミングでは コードの共同所有 (collective code ownership) を要求しており、 すべての開発者がシステム全体の動作を知っておく必要があります。 作成するテストに対して、そのクラスが何を行うべきなのかを示すような 「わかりやすい」名前をつけられるようにさえしておけば、PHPUnit の TestDox 機能を使用して自動的にドキュメントを生成することができます。 このドキュメントにより、開発者たちはプロジェクト内の各クラスが どのようにふるまうべきなのかを知ることができます。
PHPUnit の TestDox 機能は、テストクラス内のすべてのテストメソッドの名前を抽出し、
それを PHP 風のキャメルケースから通常の文に変換します。つまり
testBalanceIsInitiallyZero()
が "Balance is initially zero"
のようになるわけです。最後のほうの数字のみが違うメソッド、例えば
testBalanceCannotBecomeNegative()
と
testBalanceCannotBecomeNegative2()
のようなものが存在した場合は、
文 "Balance cannot become negative" は一度のみ表示され、
全てのテストが成功したことを表します。
BankAccount
クラスのアジャイルな文書を見てみましょう。
phpunit --testdox BankAccountTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
BankAccount
[x] Balance is initially zero
[x] Balance cannot become negative
また、アジャイルな文書を HTML あるいはプレーンテキスト形式で作成してファイルに書き出すこともできます。
この場合は、引数 --testdox-html
あるいは --testdox-text
を使用します。
アジャイルな文書は、プロジェクト内であなたが作成しようとしている外部パッケージについて、 このように動作するであるという期待をまとめた文書にもなります。 外部のパッケージを使用するときには、 そのパッケージが期待通りに動作しなくなるというリスクに常にさらされています。 パッケージのバージョンアップにより知らないうちに挙動が変わってしまい、 あなたのコードが動作しなくなる可能性もあります。そのようなことを避けるため、 「このパッケージはこのように動作するはず」 ということを常にテストケースで記述しておくようにします。テストが成功すれば、 期待通りに動作していることがわかります。もし動作仕様をすべてテストで記述できているのなら、 外部パッケージが将来バージョンアップされたとしても何の心配もいりません。 テストをクリアしたということは、システムは期待通りに動作するということだからです。
あるパッケージについての機能を文書化するためにテストを書いているとき、 そのテストの所有者はあなたです。今あなたがテストを作成しているパッケージの作者は、 そのテストのことについては何も知りません。パッケージの作者とよりつながりを深めるため、 作成したテストを使用してコミュニケートしたり、 そのテストを使用して共同作業をしたりすることができるでしょう。
あなたが作成したテストを使用してパッケージの作者と共同作業をすることになれば、 テストも共同で書くことになります。そうすることで、 より多くのテストケースを挙げられるようになるでしょう。 「暗黙の了解」などに頼っていては、共同作業はできません。 テストと同時に、あなたはそのパッケージに対して期待していることを正確に文書化することになります。 また、すべてのテストにクリアした時点で、 作者はパッケージが完成したことを知ることになります。
スタブ (本書の前のほうで説明した "モックオブジェクト" の章を参照ください) を使用することで、パッケージの作者と別れても作業できるようになります。 パッケージ作者の仕事は、パッケージの実際の実装でテストをクリアするようにすること。 そしてあなたの仕事はあなたが書いたコードでテストをクリアするようにすることです。 この段階になれば、あなたはスタブオブジェクトを使用すればよいのです。 このやり方により、2 つのチームが独立して開発できるようになります。
PHPUnit は、いくつかの形式のログファイルを作成することができます。
PHPUnit が作成するテスト結果の XML のログファイルは、
Apache Ant の JUnit タスク
が使用しているものを参考にしています。
以下の例は、ArrayTest
のテストが生成した
XML ログファイルです。
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="ArrayTest" file="/home/sb/ArrayTest.php" tests="2" assertions="2" failures="0" errors="0" time="0.016030"> <testcase name="testNewArrayIsEmpty" class="ArrayTest" file="/home/sb/ArrayTest.php" line="6" assertions="1" time="0.008044"/> <testcase name="testArrayContainsAnElement" class="ArrayTest" file="/home/sb/ArrayTest.php" line="15" assertions="1" time="0.007986"/> </testsuite> </testsuites>
次の XML ログファイルは、テストクラス
FailureErrorTest
にある 2 つのテスト
testFailure
および testError
が出力したものです。失敗やエラーがどのように表示されるのかを確認しましょう。
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="FailureErrorTest" file="/home/sb/FailureErrorTest.php" tests="2" assertions="1" failures="1" errors="1" time="0.019744"> <testcase name="testFailure" class="FailureErrorTest" file="/home/sb/FailureErrorTest.php" line="6" assertions="1" time="0.011456"> <failure type="PHPUnit_Framework_ExpectationFailedException"> testFailure(FailureErrorTest) Failed asserting that <integer:2> matches expected value <integer:1>. /home/sb/FailureErrorTest.php:8 </failure> </testcase> <testcase name="testError" class="FailureErrorTest" file="/home/sb/FailureErrorTest.php" line="11" assertions="0" time="0.008288"> <error type="Exception">testError(FailureErrorTest) Exception: /home/sb/FailureErrorTest.php:13 </error> </testcase> </testsuite> </testsuites>
PHPUnit がコードカバレッジ情報のログ出力の際に使用している XML のフォーマットは、
Clover
のものを参考にしています。
以下の例は、BankAccountTest
のテストが生成した
XML ログファイルです。
<?xml version="1.0" encoding="UTF-8"?> <coverage generated="1184835473" phpunit="4.8.0"> <project name="BankAccountTest" timestamp="1184835473"> <file name="/home/sb/BankAccount.php"> <class name="BankAccountException"> <metrics methods="0" coveredmethods="0" statements="0" coveredstatements="0" elements="0" coveredelements="0"/> </class> <class name="BankAccount"> <metrics methods="4" coveredmethods="4" statements="13" coveredstatements="5" elements="17" coveredelements="9"/> </class> <line num="77" type="method" count="3"/> <line num="79" type="stmt" count="3"/> <line num="89" type="method" count="2"/> <line num="91" type="stmt" count="2"/> <line num="92" type="stmt" count="0"/> <line num="93" type="stmt" count="0"/> <line num="94" type="stmt" count="2"/> <line num="96" type="stmt" count="0"/> <line num="105" type="method" count="1"/> <line num="107" type="stmt" count="1"/> <line num="109" type="stmt" count="0"/> <line num="119" type="method" count="1"/> <line num="121" type="stmt" count="1"/> <line num="123" type="stmt" count="0"/> <metrics loc="126" ncloc="37" classes="2" methods="4" coveredmethods="4" statements="13" coveredstatements="5" elements="17" coveredelements="9"/> </file> <metrics files="1" loc="126" ncloc="37" classes="2" methods="4" coveredmethods="4" statements="13" coveredstatements="5" elements="17" coveredelements="9"/> </project> </coverage>
人間が読める形式のコードカバレッジ情報を、コマンドラインあるいはテキストファイルに出力します。
この出力フォーマットの狙いは、ちょっとしたクラス群のカバレッジの概要を手軽に把握することです。
大規模なプロジェクトでは、このフォーマットを使えばプロジェクト全体のカバレッジを大まかに把握しやすくなるでしょう。
--filter
と組み合わせて使うこともできます。
コマンドラインから使う場合は php://stdout
に書き込みます。
この出力は --colors
の設定を反映したものになります。
コマンドラインから使った場合は、デフォルトの出力先は標準出力となります。
デフォルトでは、テストで少なくとも一行はカバーしているファイルしか表示しません。
この設定は、xml の showUncoveredFiles
オプションでしか変更できません。
「ログ出力」 を参照ください。
デフォルトでは、すべてのファイルとそのカバレッジ情報が、詳細形式で表示されます。
この設定は、xml のオプション showOnlySummary
で変更できます。
テストを書きやすくする、あるいはテストの実行結果の表示方法を変更するなど、 PHPUnit はさまざまな方法で拡張することができます。 PHPUnit を拡張するための第一歩をここで説明します。
PHPUnit\Framework\TestCase
を継承した抽象サブクラスにカスタムアサーションやユーティリティメソッドを書き、
そのクラスをさらに継承してテストクラスを作成します。
これが、PHPUnit を拡張するための一番簡単な方法です。
カスタムアサーションを作成するときには、PHPUnit 自体のアサーションの実装方法を真似るのがおすすめです。
例 14.1 を見ればわかるとおり、
assertTrue()
メソッドは
isTrue()
および assertThat()
メソッドの単なるラッパーに過ぎません。
isTrue()
が matcher オブジェクトを作り、それを
assertThat()
に渡して評価しています。
例 14.1: PHPUnit_Framework_Assert クラスの assertTrue() および isTrue() メソッド
<?php use PHPUnit\Framework\TestCase; abstract class PHPUnit_Framework_Assert { // ... /** * Asserts that a condition is true. * * @param boolean $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertTrue($condition, $message = '') { self::assertThat($condition, self::isTrue(), $message); } // ... /** * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object. * * @return PHPUnit_Framework_Constraint_IsTrue * @since Method available since Release 3.3.0 */ public static function isTrue() { return new PHPUnit_Framework_Constraint_IsTrue; } // ... }?>
例 14.2 は、
PHPUnit_Framework_Constraint_IsTrue
が
matcher オブジェクト (あるいは制約) のために抽象クラス
PHPUnit_Framework_Constraint
を継承している部分です。
例 14.2: PHPUnit_Framework_Constraint_IsTrue クラス
<?php use PHPUnit\Framework\TestCase; class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ public function matches($other) { return $other === true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is true'; } }?>
assertTrue()
や
isTrue()
メソッドの実装を
PHPUnit_Framework_Constraint_IsTrue
クラスと同じようにしておけば、
アサーションの評価やタスクの記録 (テストの統計情報に自動的に更新するなど)
を assertThat()
が自動的に行ってくれるようになります。
さらに、モックオブジェクトを設定する際の matcher として isTrue()
メソッドを使えるようにもなります。
例 14.3 は、
PHPUnit\Framework\TestListener
インターフェイスのシンプルな実装例です。
例 14.3: シンプルなテストリスナー
<?php use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestListener; class SimpleTestListener implements TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' の実行中にエラーが発生\n", $test->getName()); } public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { printf("テスト '%s' に失敗\n", $test->getName()); } public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' は未完成\n", $test->getName()); } public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' は危険\n", $test->getName()); } public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("テスト '%s' をスキップ\n", $test->getName()); } public function startTest(PHPUnit_Framework_Test $test) { printf("テスト '%s' が開始\n", $test->getName()); } public function endTest(PHPUnit_Framework_Test $test, $time) { printf("テスト '%s' が終了\n", $test->getName()); } public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("テストスイート '%s' が開始\n", $suite->getName()); } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("テストスイート '%s' が終了\n", $suite->getName()); } } ?>
例 14.4
は、抽象クラス PHPUnit_Framework_BaseTestListener
のサブクラスを作る例です。これは、インターフェイスのメソッドのうち実際に使うものだけを指定し、
他のメソッドについては空の実装を提供します。
例 14.4: ベーステストリスナーの利用法
<?php use PHPUnit\Framework\TestCase; class ShortTestListener extends PHPUnit_Framework_BaseTestListener { public function endTest(PHPUnit_Framework_Test $test, $time) { printf("テスト '%s' が終了\n", $test->getName()); } } ?>
「テストリスナー」 に、自作のテストリスナーをテスト実行時にアタッチするための PHPUnit の設定方法についての説明があります。
PHPUnit_Extensions_TestDecorator
のサブクラスでテストケースあるいはテストスイートをラッピングし、
デコレータパターンを使用することで
各テストの実行前後に何らかの処理をさせることができます。
PHPUnit には、PHPUnit_Extensions_RepeatedTest
および PHPUnit_Extensions_TestSetup
という 2 つの具象テストデコレータが付属しています。
前者はテストを繰り返し実行し、それらが全て成功した場合にのみ成功とみなします。
後者については 第 4 章 で説明しました。
例 14.5
は、テストデコレータ PHPUnit_Extensions_RepeatedTest
の一部を抜粋したものです。独自のデコレータを作成するための参考にしてください。
例 14.5: RepeatedTest デコレータ
<?php use PHPUnit\Framework\TestCase; require_once 'PHPUnit/Extensions/TestDecorator.php'; class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator { private $timesRepeat = 1; public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1) { parent::__construct($test); if (is_integer($timesRepeat) && $timesRepeat >= 0) { $this->timesRepeat = $timesRepeat; } } public function count() { return $this->timesRepeat * $this->test->count(); } public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = $this->createResult(); } for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) { $this->test->run($result); } return $result; } } ?>
PHPUnit_Framework_Test
インターフェイスの機能は限られており、
実装するのは簡単です。PHPUnit_Framework_Test
を実装するのは PHPUnit\Framework\TestCase
の実装より単純で、
これを用いて例えば データ駆動のテスト (data-driven tests)
などを実行します。
カンマ区切り (CSV) ファイルの値と比較する、データ駆動のテストを
例 14.6
に示します。このファイルの各行は foo;bar
のような形式になっており (訳注: CSV じゃない……)、
最初の値が期待値で 2 番目の値が実際の値です。
例 14.6: データ駆動のテスト
<?php use PHPUnit\Framework\TestCase; class DataDrivenTest implements PHPUnit_Framework_Test { private $lines; public function __construct($dataFile) { $this->lines = file($dataFile); } public function count() { return 1; } public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = new PHPUnit_Framework_TestResult; } foreach ($this->lines as $line) { $result->startTest($this); PHP_Timer::start(); $stopTime = null; list($expected, $actual) = explode(';', $line); try { PHPUnit_Framework_Assert::assertEquals( trim($expected), trim($actual) ); } catch (PHPUnit_Framework_AssertionFailedError $e) { $stopTime = PHP_Timer::stop(); $result->addFailure($this, $e, $stopTime); } catch (Exception $e) { $stopTime = PHP_Timer::stop(); $result->addError($this, $e, $stopTime); } if ($stopTime === null) { $stopTime = PHP_Timer::stop(); } $result->endTest($this, $stopTime); } return $result; } } $test = new DataDrivenTest('data_file.csv'); $result = PHPUnit_TextUI_TestRunner::run($test); ?>
PHPUnit 6.5.0 by Sebastian Bergmann and contributors. .F Time: 0 seconds There was 1 failure: 1) DataDrivenTest Failed asserting that two strings are equal. expected string <bar> difference < x> got string <baz> /home/sb/DataDrivenTest.php:32 /home/sb/DataDrivenTest.php:53 FAILURES! Tests: 2, Failures: 1.
この節では、利用可能なアサーションメソッドの一覧を示します。
PHPUnit のアサーションの実装は、PHPUnit\Framework\Assert
およびそれを継承した PHPUnit\Framework\TestCase
にあります。
アサーションメソッドは static
宣言されていて、あらゆるコンテキストから
PHPUnit\Framework\Assert::assertTrue()
などのように使えます。
また、PHPUnit\Framework\TestCase
を継承したクラスの中では
$this->assertTrue()
や self::assertTrue()
などとしても使えます。
さらに、PHPUnit に含まれるファイル src/Framework/Assert/Functions.php
を (手動で) インクルードしてしまえば、グローバルなラッパー関数 assertTrue()
などを使うことさえできてしまいます。これは、PHPUnit\Framework\TestCase
を継承したクラスを含めてあらゆるコンテキストで使えます。
PHPUnit を使い始めたばかりの開発者の多くは、たとえばアサーションを実行するときに
$this->assertTrue()
と self::assertTrue()
のどちらを使うのが「正しい方法」なのだろうかと思うことでしょう。
端的に言って、どちらがよいなどということはありません。同様に、どちらが悪いというものでもありません。
どちらを使うかは、個人的な好みの問題です。
どちらかといえば $this->assertTrue()
を使うほうが自然に感じるという人が多いようです。テストメソッドは、テストオブジェクト上で実行されるからです。
アサーションメソッドが static
宣言されていることによって、
テストオブジェクトのスコープを超えた再利用が可能になります。
また、グローバル関数のラッパーを使えば、
($this->assertTrue()
や self::assertTrue()
ではなく
assertTrue()
と書くことによって) タイプ量を抑えることができます。
assertArrayHasKey(mixed $key, array $array[, string $message = ''])
$array
にキー $key
が存在しない場合にエラー $message
を報告します。
assertArrayNotHasKey()
はこのアサーションの逆で、同じ引数をとります。
例 A.1: assertArrayHasKey() の使用法
<?php use PHPUnit\Framework\TestCase; class ArrayHasKeyTest extends TestCase { public function testFailure() { $this->assertArrayHasKey('foo', ['bar' => 'baz']); } } ?>
phpunit ArrayHasKeyTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) ArrayHasKeyTest::testFailure
Failed asserting that an array has the key 'foo'.
/home/sb/ArrayHasKeyTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertClassHasAttribute(string $attributeName, string $className[, string $message = ''])
$className::attributeName
が存在しない場合にエラー $message
を報告します。
assertClassNotHasAttribute()
はこのアサーションの逆で、同じ引数をとります。
例 A.2: assertClassHasAttribute() の使用法
<?php use PHPUnit\Framework\TestCase; class ClassHasAttributeTest extends TestCase { public function testFailure() { $this->assertClassHasAttribute('foo', stdClass::class); } } ?>
phpunit ClassHasAttributeTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) ClassHasAttributeTest::testFailure
Failed asserting that class "stdClass" has attribute "foo".
/home/sb/ClassHasAttributeTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertArraySubset(array $subset, array $array[, bool $strict = '', string $message = ''])
$array
が $subset
を含まない場合にエラー $message
を報告します。
$strict
フラグを使うと、配列内のオブジェクトの比較にその識別子を利用します。
例 A.3: Usage of assertArraySubset()
<?php use PHPUnit\Framework\TestCase; class ArraySubsetTest extends TestCase { public function testFailure() { $this->assertArraySubset(['config' => ['key-a', 'key-b']], ['config' => ['key-a']]); } } ?>
phpunit ArrayHasKeyTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) Epilog\EpilogTest::testNoFollowOption
Failed asserting that an array has the subset Array &0 (
'config' => Array &1 (
0 => 'key-a'
1 => 'key-b'
)
).
/home/sb/ArraySubsetTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertClassHasStaticAttribute(string $attributeName, string $className[, string $message = ''])
$className::attributeName
が存在しない場合にエラー $message
を報告します。
assertClassNotHasStaticAttribute()
はこのアサーションの逆で、同じ引数をとります。
例 A.4: assertClassHasStaticAttribute() の使用法
<?php use PHPUnit\Framework\TestCase; class ClassHasStaticAttributeTest extends TestCase { public function testFailure() { $this->assertClassHasStaticAttribute('foo', stdClass::class); } } ?>
phpunit ClassHasStaticAttributeTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) ClassHasStaticAttributeTest::testFailure
Failed asserting that class "stdClass" has static attribute "foo".
/home/sb/ClassHasStaticAttributeTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertContains(mixed $needle, Iterator|array $haystack[, string $message = ''])
$needle
が $haystack
の要素でない場合にエラー $message
を報告します。
assertNotContains()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeContains()
と assertAttributeNotContains()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を haystack として使用することができます。
例 A.5: assertContains() の使用法
<?php use PHPUnit\Framework\TestCase; class ContainsTest extends TestCase { public function testFailure() { $this->assertContains(4, [1, 2, 3]); } } ?>
phpunit ContainsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) ContainsTest::testFailure
Failed asserting that an array contains 4.
/home/sb/ContainsTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertContains(string $needle, string $haystack[, string $message = '', boolean $ignoreCase = false])
$needle
が $haystack
の部分文字列でない場合にエラー $message
を報告します。
$ignoreCase
が true
の場合、テストで大文字小文字を区別しなくなります。
例 A.6: assertContains() の使用法
<?php use PHPUnit\Framework\TestCase; class ContainsTest extends TestCase { public function testFailure() { $this->assertContains('baz', 'foobar'); } } ?>
phpunit ContainsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) ContainsTest::testFailure
Failed asserting that 'foobar' contains "baz".
/home/sb/ContainsTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
例 A.7: assertContains() で $ignoreCase を使う方法
<?php use PHPUnit\Framework\TestCase; class ContainsTest extends TestCase { public function testFailure() { $this->assertContains('foo', 'FooBar'); } public function testOK() { $this->assertContains('foo', 'FooBar', '', true); } } ?>
phpunit ContainsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F.
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) ContainsTest::testFailure
Failed asserting that 'FooBar' contains "foo".
/home/sb/ContainsTest.php:6
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
assertContainsOnly(string $type, Iterator|array $haystack[, boolean $isNativeType = NULL, string $message = ''])
$haystack
の中身の型が $type
だけではない場合にエラー $message
を報告します。
$isNativeType
はフラグで、$type
がネイティブな PHP の型であるかどうかを表します。
assertNotContainsOnly()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeContainsOnly()
と assertAttributeNotContainsOnly()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を haystack として使用することができます。
例 A.8: assertContainsOnly() の使用法
<?php use PHPUnit\Framework\TestCase; class ContainsOnlyTest extends TestCase { public function testFailure() { $this->assertContainsOnly('string', ['1', '2', 3]); } } ?>
phpunit ContainsOnlyTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) ContainsOnlyTest::testFailure
Failed asserting that Array (
0 => '1'
1 => '2'
2 => 3
) contains only values of type "string".
/home/sb/ContainsOnlyTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertContainsOnlyInstancesOf(string $classname, Traversable|array $haystack[, string $message = ''])
$haystack
が $classname
クラスの唯一のインスタンスを含まない場合にエラー $message
を報告します。
例 A.9: assertContainsOnlyInstancesOf() の使用法
<?php use PHPUnit\Framework\TestCase; class ContainsOnlyInstancesOfTest extends TestCase { public function testFailure() { $this->assertContainsOnlyInstancesOf( Foo::class, [new Foo, new Bar, new Foo] ); } } ?>
phpunit ContainsOnlyInstancesOfTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) ContainsOnlyInstancesOfTest::testFailure
Failed asserting that Array ([0]=> Bar Object(...)) is an instance of class "Foo".
/home/sb/ContainsOnlyInstancesOfTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertCount($expectedCount, $haystack[, string $message = ''])
$haystack
の要素数が $expectedCount
でない場合にエラー $message
を報告します。
assertNotCount()
はこのアサーションの逆で、同じ引数をとります。
例 A.10: assertCount() の使用法
<?php use PHPUnit\Framework\TestCase; class CountTest extends TestCase { public function testFailure() { $this->assertCount(0, ['foo']); } } ?>
phpunit CountTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) CountTest::testFailure
Failed asserting that actual size 1 matches expected size 0.
/home/sb/CountTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertDirectoryExists(string $directory[, string $message = ''])
$directory
で指定したディレクトリが存在しない場合にエラー $message
を報告します。
assertDirectoryNotExists()
はこのアサーションの逆で、同じ引数をとります。
例 A.11: assertDirectoryExists() の使用法
<?php use PHPUnit\Framework\TestCase; class DirectoryExistsTest extends TestCase { public function testFailure() { $this->assertDirectoryExists('/path/to/directory'); } } ?>
phpunit DirectoryExistsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) DirectoryExistsTest::testFailure
Failed asserting that directory "/path/to/directory" exists.
/home/sb/DirectoryExistsTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertDirectoryIsReadable(string $directory[, string $message = ''])
$directory
で指定したディレクトリが読み込み可能でない場合にエラー $message
を報告します。
assertDirectoryNotIsReadable()
はこのアサーションの逆で、同じ引数をとります。
例 A.12: assertDirectoryIsReadable() の使用法
<?php use PHPUnit\Framework\TestCase; class DirectoryIsReadableTest extends TestCase { public function testFailure() { $this->assertDirectoryIsReadable('/path/to/directory'); } } ?>
phpunit DirectoryIsReadableTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) DirectoryIsReadableTest::testFailure
Failed asserting that "/path/to/directory" is readable.
/home/sb/DirectoryIsReadableTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertDirectoryIsWritable(string $directory[, string $message = ''])
$directory
で指定したディレクトリが書き込み可能でない場合にエラー $message
を報告します。
assertDirectoryNotIsWritable()
はこのアサーションの逆で、同じ引数をとります。
例 A.13: assertDirectoryIsWritable() の使用法
<?php use PHPUnit\Framework\TestCase; class DirectoryIsWritableTest extends TestCase { public function testFailure() { $this->assertDirectoryIsWritable('/path/to/directory'); } } ?>
phpunit DirectoryIsWritableTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) DirectoryIsWritableTest::testFailure
Failed asserting that "/path/to/directory" is writable.
/home/sb/DirectoryIsWritableTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertEmpty(mixed $actual[, string $message = ''])
$actual
が空でない場合にエラー $message
を報告します。
assertNotEmpty()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeEmpty()
および assertAttributeNotEmpty()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性に対して使えます。
例 A.14: assertEmpty() の使用法
<?php use PHPUnit\Framework\TestCase; class EmptyTest extends TestCase { public function testFailure() { $this->assertEmpty(['foo']); } } ?>
phpunit EmptyTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) EmptyTest::testFailure
Failed asserting that an array is empty.
/home/sb/EmptyTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement[, boolean $checkAttributes = false, string $message = ''])
$actualElement
の DOMElement の XML 構造が $expectedElement
の DOMElement の XML 構造と等しくない場合にエラー $message
を報告します。
例 A.15: assertEqualXMLStructure() の使用法
<?php use PHPUnit\Framework\TestCase; class EqualXMLStructureTest extends TestCase { public function testFailureWithDifferentNodeNames() { $expected = new DOMElement('foo'); $actual = new DOMElement('bar'); $this->assertEqualXMLStructure($expected, $actual); } public function testFailureWithDifferentNodeAttributes() { $expected = new DOMDocument; $expected->loadXML('<foo bar="true" />'); $actual = new DOMDocument; $actual->loadXML('<foo/>'); $this->assertEqualXMLStructure( $expected->firstChild, $actual->firstChild, true ); } public function testFailureWithDifferentChildrenCount() { $expected = new DOMDocument; $expected->loadXML('<foo><bar/><bar/><bar/></foo>'); $actual = new DOMDocument; $actual->loadXML('<foo><bar/></foo>'); $this->assertEqualXMLStructure( $expected->firstChild, $actual->firstChild ); } public function testFailureWithDifferentChildren() { $expected = new DOMDocument; $expected->loadXML('<foo><bar/><bar/><bar/></foo>'); $actual = new DOMDocument; $actual->loadXML('<foo><baz/><baz/><baz/></foo>'); $this->assertEqualXMLStructure( $expected->firstChild, $actual->firstChild ); } } ?>
phpunit EqualXMLStructureTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
FFFF
Time: 0 seconds, Memory: 5.75Mb
There were 4 failures:
1) EqualXMLStructureTest::testFailureWithDifferentNodeNames
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'foo'
+'bar'
/home/sb/EqualXMLStructureTest.php:9
2) EqualXMLStructureTest::testFailureWithDifferentNodeAttributes
Number of attributes on node "foo" does not match
Failed asserting that 0 matches expected 1.
/home/sb/EqualXMLStructureTest.php:22
3) EqualXMLStructureTest::testFailureWithDifferentChildrenCount
Number of child nodes of "foo" differs
Failed asserting that 1 matches expected 3.
/home/sb/EqualXMLStructureTest.php:35
4) EqualXMLStructureTest::testFailureWithDifferentChildren
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'
/home/sb/EqualXMLStructureTest.php:48
FAILURES!
Tests: 4, Assertions: 8, Failures: 4.
assertEquals(mixed $expected, mixed $actual[, string $message = ''])
2 つの変数 $expected
と $actual
が等しくない場合にエラー $message
を報告します。
assertNotEquals()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeEquals()
と assertAttributeNotEquals()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を実際の値として使用することができます。
例 A.16: assertEquals() の使用法
<?php use PHPUnit\Framework\TestCase; class EqualsTest extends TestCase { public function testFailure() { $this->assertEquals(1, 0); } public function testFailure2() { $this->assertEquals('bar', 'baz'); } public function testFailure3() { $this->assertEquals("foo\nbar\nbaz\n", "foo\nbah\nbaz\n"); } } ?>
phpunit EqualsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
FFF
Time: 0 seconds, Memory: 5.25Mb
There were 3 failures:
1) EqualsTest::testFailure
Failed asserting that 0 matches expected 1.
/home/sb/EqualsTest.php:6
2) EqualsTest::testFailure2
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'
/home/sb/EqualsTest.php:11
3) EqualsTest::testFailure3
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
'foo
-bar
+bah
baz
'
/home/sb/EqualsTest.php:16
FAILURES!
Tests: 3, Assertions: 3, Failures: 3.
引数 $expected
と $actual
の型により特化した比較については、以下を参照ください。
assertEquals(float $expected, float $actual[, string $message = '', float $delta = 0])
2 つの float 値 $expected
と $actual
の誤差が $delta
より大きい場合にエラー $message
を報告します。
なぜ $delta
が必要となるのかについては "What Every Computer Scientist Should Know About Floating-Point Arithmetic" を参照ください。
例 A.17: float 値での assertEquals() の使用法
<?php use PHPUnit\Framework\TestCase; class EqualsTest extends TestCase { public function testSuccess() { $this->assertEquals(1.0, 1.1, '', 0.2); } public function testFailure() { $this->assertEquals(1.0, 1.1); } } ?>
phpunit EqualsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
.F
Time: 0 seconds, Memory: 5.75Mb
There was 1 failure:
1) EqualsTest::testFailure
Failed asserting that 1.1 matches expected 1.0.
/home/sb/EqualsTest.php:11
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
assertEquals(DOMDocument $expected, DOMDocument $actual[, string $message = ''])
2 つの DOMDocument オブジェクト $expected
と $actual
で表される XML ドキュメントが (コメントを除去して正規化した状態で) 等しくない場合にエラー $message
を報告します。
例 A.18: DOMDocument オブジェクトでの assertEquals() の使用法
<?php use PHPUnit\Framework\TestCase; class EqualsTest extends TestCase { public function testFailure() { $expected = new DOMDocument; $expected->loadXML('<foo><bar/></foo>'); $actual = new DOMDocument; $actual->loadXML('<bar><foo/></bar>'); $this->assertEquals($expected, $actual); } } ?>
phpunit EqualsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) EqualsTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
<?xml version="1.0"?>
-<foo>
- <bar/>
-</foo>
+<bar>
+ <foo/>
+</bar>
/home/sb/EqualsTest.php:12
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertEquals(object $expected, object $actual[, string $message = ''])
2 つのオブジェクト $expected
と $actual
が同じ属性値を持たない場合にエラー $message
を報告します。
例 A.19: オブジェクトでの assertEquals() の使用法
<?php use PHPUnit\Framework\TestCase; class EqualsTest extends TestCase { public function testFailure() { $expected = new stdClass; $expected->foo = 'foo'; $expected->bar = 'bar'; $actual = new stdClass; $actual->foo = 'bar'; $actual->baz = 'bar'; $this->assertEquals($expected, $actual); } } ?>
phpunit EqualsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) EqualsTest::testFailure
Failed asserting that two objects are equal.
--- Expected
+++ Actual
@@ @@
stdClass Object (
- 'foo' => 'foo'
- 'bar' => 'bar'
+ 'foo' => 'bar'
+ 'baz' => 'bar'
)
/home/sb/EqualsTest.php:14
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertEquals(array $expected, array $actual[, string $message = ''])
2 つの配列 $expected
と $actual
が等しくない場合にエラー $message
を報告します。
例 A.20: 配列での assertEquals() の使用法
<?php use PHPUnit\Framework\TestCase; class EqualsTest extends TestCase { public function testFailure() { $this->assertEquals(['a', 'b', 'c'], ['a', 'c', 'd']); } } ?>
phpunit EqualsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) EqualsTest::testFailure
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
Array (
0 => 'a'
- 1 => 'b'
- 2 => 'c'
+ 1 => 'c'
+ 2 => 'd'
)
/home/sb/EqualsTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertFalse(bool $condition[, string $message = ''])
$condition
が true
の場合にエラー $message
を報告します。
assertNotFalse()
はこのアサーションの逆で、同じ引数をとります。
例 A.21: assertFalse() の使用法
<?php use PHPUnit\Framework\TestCase; class FalseTest extends TestCase { public function testFailure() { $this->assertFalse(true); } } ?>
phpunit FalseTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) FalseTest::testFailure
Failed asserting that true is false.
/home/sb/FalseTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertFileEquals(string $expected, string $actual[, string $message = ''])
$expected
で指定したファイルと $actual
で指定したファイルの内容が異なる場合にエラー $message
を報告します。
assertFileNotEquals()
はこのアサーションの逆で、同じ引数をとります。
例 A.22: assertFileEquals() の使用法
<?php use PHPUnit\Framework\TestCase; class FileEqualsTest extends TestCase { public function testFailure() { $this->assertFileEquals('/home/sb/expected', '/home/sb/actual'); } } ?>
phpunit FileEqualsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) FileEqualsTest::testFailure
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'expected
+'actual
'
/home/sb/FileEqualsTest.php:6
FAILURES!
Tests: 1, Assertions: 3, Failures: 1.
assertFileExists(string $filename[, string $message = ''])
ファイル $filename
が存在しない場合にエラー $message
を報告します。
assertFileNotExists()
はこのアサーションの逆で、同じ引数をとります。
例 A.23: assertFileExists() の使用法
<?php use PHPUnit\Framework\TestCase; class FileExistsTest extends TestCase { public function testFailure() { $this->assertFileExists('/path/to/file'); } } ?>
phpunit FileExistsTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) FileExistsTest::testFailure
Failed asserting that file "/path/to/file" exists.
/home/sb/FileExistsTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertFileIsReadable(string $filename[, string $message = ''])
$filename
で指定したファイルが読み込み可能でない場合、あるいはファイルでない場合にエラー $message
を報告します。
assertFileNotIsReadable()
はこのアサーションの逆で、同じ引数をとります。
例 A.24: assertFileIsReadable() の使用法
<?php use PHPUnit\Framework\TestCase; class FileIsReadableTest extends TestCase { public function testFailure() { $this->assertFileIsReadable('/path/to/file'); } } ?>
phpunit FileIsReadableTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) FileIsReadableTest::testFailure
Failed asserting that "/path/to/file" is readable.
/home/sb/FileIsReadableTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertFileIsWritable(string $filename[, string $message = ''])
$filename
で指定したファイルが書き込み可能でない場合、あるいはファイルでない場合にエラー $message
を報告します。
assertFileNotIsWritable()
はこのアサーションの逆で、同じ引数をとります。
例 A.25: assertFileIsWritable() の使用法
<?php use PHPUnit\Framework\TestCase; class FileIsWritableTest extends TestCase { public function testFailure() { $this->assertFileIsWritable('/path/to/file'); } } ?>
phpunit FileIsWritableTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) FileIsWritableTest::testFailure
Failed asserting that "/path/to/file" is writable.
/home/sb/FileIsWritableTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertGreaterThan(mixed $expected, mixed $actual[, string $message = ''])
$actual
の値が $expected
の値より大きくない場合にエラー $message
を報告します。
assertAttributeGreaterThan()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を実際の値として使用することができます。
例 A.26: assertGreaterThan() の使用法
<?php use PHPUnit\Framework\TestCase; class GreaterThanTest extends TestCase { public function testFailure() { $this->assertGreaterThan(2, 1); } } ?>
phpunit GreaterThanTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) GreaterThanTest::testFailure
Failed asserting that 1 is greater than 2.
/home/sb/GreaterThanTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertGreaterThanOrEqual(mixed $expected, mixed $actual[, string $message = ''])
$actual
の値が $expected
の値以上でない場合にエラー $message
を報告します。
assertAttributeGreaterThanOrEqual()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を実際の値として使用することができます。
例 A.27: assertGreaterThanOrEqual() の使用法
<?php use PHPUnit\Framework\TestCase; class GreatThanOrEqualTest extends TestCase { public function testFailure() { $this->assertGreaterThanOrEqual(2, 1); } } ?>
phpunit GreaterThanOrEqualTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) GreatThanOrEqualTest::testFailure
Failed asserting that 1 is equal to 2 or is greater than 2.
/home/sb/GreaterThanOrEqualTest.php:6
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
assertInfinite(mixed $variable[, string $message = ''])
$variable
が INF
でない場合にエラー $message
を報告します。
assertFinite()
はこのアサーションの逆で、同じ引数をとります。
例 A.28: assertInfinite() の使用法
<?php use PHPUnit\Framework\TestCase; class InfiniteTest extends TestCase { public function testFailure() { $this->assertInfinite(1); } } ?>
phpunit InfiniteTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) InfiniteTest::testFailure
Failed asserting that 1 is infinite.
/home/sb/InfiniteTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertInstanceOf($expected, $actual[, $message = ''])
$actual
が $expected
のインスタンスでない場合にエラー $message
を報告します。
assertNotInstanceOf()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeInstanceOf()
および assertAttributeNotInstanceOf()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性に対して使えます。
例 A.29: assertInstanceOf() の使用法
<?php use PHPUnit\Framework\TestCase; class InstanceOfTest extends TestCase { public function testFailure() { $this->assertInstanceOf(RuntimeException::class, new Exception); } } ?>
phpunit InstanceOfTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) InstanceOfTest::testFailure
Failed asserting that Exception Object (...) is an instance of class "RuntimeException".
/home/sb/InstanceOfTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertInternalType($expected, $actual[, $message = ''])
$actual
の型が $expected
でない場合にエラー $message
を報告します。
assertNotInternalType()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeInternalType()
および assertAttributeNotInternalType()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性に対して使えます。
例 A.30: assertInternalType() の使用法
<?php use PHPUnit\Framework\TestCase; class InternalTypeTest extends TestCase { public function testFailure() { $this->assertInternalType('string', 42); } } ?>
phpunit InternalTypeTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) InternalTypeTest::testFailure
Failed asserting that 42 is of type "string".
/home/sb/InternalTypeTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertIsReadable(string $filename[, string $message = ''])
$filename
で指定したファイルあるいはディレクトリが読み込み可能でない場合にエラー $message
を報告します。
assertNotIsReadable()
はこのアサーションの逆で、同じ引数をとります。
例 A.31: assertIsReadable() の使用法
<?php use PHPUnit\Framework\TestCase; class IsReadableTest extends TestCase { public function testFailure() { $this->assertIsReadable('/path/to/unreadable'); } } ?>
phpunit IsReadableTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) IsReadableTest::testFailure
Failed asserting that "/path/to/unreadable" is readable.
/home/sb/IsReadableTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertIsWritable(string $filename[, string $message = ''])
$filename
で指定したファイルあるいはディレクトリが書き込み可能でない場合にエラー $message
を報告します。
assertNotIsWritable()
はこのアサーションの逆で、同じ引数をとります。
例 A.32: assertIsWritable() の使用法
<?php use PHPUnit\Framework\TestCase; class IsWritableTest extends TestCase { public function testFailure() { $this->assertIsWritable('/path/to/unwritable'); } } ?>
phpunit IsWritableTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) IsWritableTest::testFailure
Failed asserting that "/path/to/unwritable" is writable.
/home/sb/IsWritableTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertJsonFileEqualsJsonFile(mixed $expectedFile, mixed $actualFile[, string $message = ''])
$actualFile
の値が
$expectedFile
の値にマッチしない場合にエラー $message
を報告します。
例 A.33: assertJsonFileEqualsJsonFile() の使用法
<?php use PHPUnit\Framework\TestCase; class JsonFileEqualsJsonFileTest extends TestCase { public function testFailure() { $this->assertJsonFileEqualsJsonFile( 'path/to/fixture/file', 'path/to/actual/file'); } } ?>
phpunit JsonFileEqualsJsonFileTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) JsonFileEqualsJsonFile::testFailure
Failed asserting that '{"Mascot":"Tux"}' matches JSON string "["Mascott", "Tux", "OS", "Linux"]".
/home/sb/JsonFileEqualsJsonFileTest.php:5
FAILURES!
Tests: 1, Assertions: 3, Failures: 1.
assertJsonStringEqualsJsonFile(mixed $expectedFile, mixed $actualJson[, string $message = ''])
$actualJson
の値が
$expectedFile
の値にマッチしない場合にエラー $message
を報告します。
例 A.34: assertJsonStringEqualsJsonFile() の使用法
<?php use PHPUnit\Framework\TestCase; class JsonStringEqualsJsonFileTest extends TestCase { public function testFailure() { $this->assertJsonStringEqualsJsonFile( 'path/to/fixture/file', json_encode(['Mascot' => 'ux']) ); } } ?>
phpunit JsonStringEqualsJsonFileTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) JsonStringEqualsJsonFile::testFailure
Failed asserting that '{"Mascot":"ux"}' matches JSON string "{"Mascott":"Tux"}".
/home/sb/JsonStringEqualsJsonFileTest.php:5
FAILURES!
Tests: 1, Assertions: 3, Failures: 1.
assertJsonStringEqualsJsonString(mixed $expectedJson, mixed $actualJson[, string $message = ''])
$actualJson
の値が
$expectedJson
の値にマッチしない場合にエラー $message
を報告します。
例 A.35: assertJsonStringEqualsJsonString() の使用法
<?php use PHPUnit\Framework\TestCase; class JsonStringEqualsJsonStringTest extends TestCase { public function testFailure() { $this->assertJsonStringEqualsJsonString( json_encode(['Mascot' => 'Tux']), json_encode(['Mascot' => 'ux']) ); } } ?>
phpunit JsonStringEqualsJsonStringTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) JsonStringEqualsJsonStringTest::testFailure
Failed asserting that two objects are equal.
--- Expected
+++ Actual
@@ @@
stdClass Object (
- 'Mascot' => 'Tux'
+ 'Mascot' => 'ux'
)
/home/sb/JsonStringEqualsJsonStringTest.php:5
FAILURES!
Tests: 1, Assertions: 3, Failures: 1.
assertLessThan(mixed $expected, mixed $actual[, string $message = ''])
$actual
の値が $expected
の値より小さくない場合にエラー $message
を報告します。
assertAttributeLessThan()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を実際の値として使用することができます。
例 A.36: assertLessThan() の使用法
<?php use PHPUnit\Framework\TestCase; class LessThanTest extends TestCase { public function testFailure() { $this->assertLessThan(1, 2); } } ?>
phpunit LessThanTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) LessThanTest::testFailure
Failed asserting that 2 is less than 1.
/home/sb/LessThanTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertLessThanOrEqual(mixed $expected, mixed $actual[, string $message = ''])
$actual
の値が $expected
の値以下でない場合にエラー $message
を報告します。
assertAttributeLessThanOrEqual()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を実際の値として使用することができます。
例 A.37: assertLessThanOrEqual() の使用法
<?php use PHPUnit\Framework\TestCase; class LessThanOrEqualTest extends TestCase { public function testFailure() { $this->assertLessThanOrEqual(1, 2); } } ?>
phpunit LessThanOrEqualTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) LessThanOrEqualTest::testFailure
Failed asserting that 2 is equal to 1 or is less than 1.
/home/sb/LessThanOrEqualTest.php:6
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
assertNan(mixed $variable[, string $message = ''])
$variable
が NAN
でない場合にエラー $message
を報告します。
例 A.38: assertNan() の使用法
<?php use PHPUnit\Framework\TestCase; class NanTest extends TestCase { public function testFailure() { $this->assertNan(1); } } ?>
phpunit NanTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) NanTest::testFailure
Failed asserting that 1 is nan.
/home/sb/NanTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertNull(mixed $variable[, string $message = ''])
$variable
が NULL
でないときにエラー $message
を報告します。
assertNotNull()
はこのアサーションの逆で、同じ引数をとります。
例 A.39: assertNull() の使用法
<?php use PHPUnit\Framework\TestCase; class NullTest extends TestCase { public function testFailure() { $this->assertNull('foo'); } } ?>
phpunit NotNullTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) NullTest::testFailure
Failed asserting that 'foo' is null.
/home/sb/NotNullTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertObjectHasAttribute(string $attributeName, object $object[, string $message = ''])
$object->attributeName
が存在しない場合にエラー $message
を報告します。
assertObjectNotHasAttribute()
はこのアサーションの逆で、同じ引数をとります。
例 A.40: assertObjectHasAttribute() の使用法
<?php use PHPUnit\Framework\TestCase; class ObjectHasAttributeTest extends TestCase { public function testFailure() { $this->assertObjectHasAttribute('foo', new stdClass); } } ?>
phpunit ObjectHasAttributeTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) ObjectHasAttributeTest::testFailure
Failed asserting that object of class "stdClass" has attribute "foo".
/home/sb/ObjectHasAttributeTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertRegExp(string $pattern, string $string[, string $message = ''])
$string
が正規表現 $pattern
にマッチしない場合にエラー $message
を報告します。
assertNotRegExp()
はこのアサーションの逆で、同じ引数をとります。
例 A.41: assertRegExp() の使用法
<?php use PHPUnit\Framework\TestCase; class RegExpTest extends TestCase { public function testFailure() { $this->assertRegExp('/foo/', 'bar'); } } ?>
phpunit RegExpTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) RegExpTest::testFailure
Failed asserting that 'bar' matches PCRE pattern "/foo/".
/home/sb/RegExpTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertStringMatchesFormat(string $format, string $string[, string $message = ''])
$string
が書式文字列 $format
にマッチしない場合にエラー $message
を報告します。
assertStringNotMatchesFormat()
はこのアサーションの逆で、同じ引数をとります。
例 A.42: assertStringMatchesFormat() の使用法
<?php use PHPUnit\Framework\TestCase; class StringMatchesFormatTest extends TestCase { public function testFailure() { $this->assertStringMatchesFormat('%i', 'foo'); } } ?>
phpunit StringMatchesFormatTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) StringMatchesFormatTest::testFailure
Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+$/s".
/home/sb/StringMatchesFormatTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
書式文字列には次のプレースホルダを含めることができます。
%e
: ディレクトリ区切り文字、たとえば Linux なら /
を表します。
%s
: 一文字以上の何か (文字あるいは空白)、ただし改行文字は含みません。
%S
: ゼロ文字以上の何か (文字あるいは空白)、ただし改行文字は含みません。
%a
: 一文字以上の何か (文字あるいは空白)、改行文字も含みます。
%A
: ゼロ文字以上の何か (文字あるいは空白)、改行文字も含みます。
%w
: ゼロ文字以上の空白。
%i
: 符号付き整数値。例: +3142
, -3142
%d
: 符号なし整数値。例: 123.66
%x
: 一文字以上の十六進文字 (0-9
, a-f
, A-F
)。
%f
: 浮動小数点数値。例: 3.142
, -3.142
, 3.142E-10
, 3.142e+10
%c
: 任意の一文字。
assertStringMatchesFormatFile(string $formatFile, string $string[, string $message = ''])
$string
が $formatFile
の内容にマッチしない場合にエラー $message
を報告します。
assertStringNotMatchesFormatFile()
はこのアサーションの逆で、同じ引数をとります。
例 A.43: assertStringMatchesFormatFile() の使用法
<?php use PHPUnit\Framework\TestCase; class StringMatchesFormatFileTest extends TestCase { public function testFailure() { $this->assertStringMatchesFormatFile('/path/to/expected.txt', 'foo'); } } ?>
phpunit StringMatchesFormatFileTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) StringMatchesFormatFileTest::testFailure
Failed asserting that 'foo' matches PCRE pattern "/^[+-]?\d+
$/s".
/home/sb/StringMatchesFormatFileTest.php:6
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
assertSame(mixed $expected, mixed $actual[, string $message = ''])
2 つの変数 $expected
と $actual
が同じ型・同じ値でない場合にエラー $message
を報告します。
assertNotSame()
はこのアサーションの逆で、同じ引数をとります。
assertAttributeSame()
と assertAttributeNotSame()
は便利なラッパーで、クラスやオブジェクトの public
、protected
、private
属性を実際の値として使用することができます。
例 A.44: assertSame() の使用法
<?php use PHPUnit\Framework\TestCase; class SameTest extends TestCase { public function testFailure() { $this->assertSame('2204', 2204); } } ?>
phpunit SameTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) SameTest::testFailure
Failed asserting that 2204 is identical to '2204'.
/home/sb/SameTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertSame(object $expected, object $actual[, string $message = ''])
2 つの変数 $expected
と $actual
が同じオブジェクトを参照していない場合にエラー $message
を報告します。
例 A.45: オブジェクトでの assertSame() の使用法
<?php use PHPUnit\Framework\TestCase; class SameTest extends TestCase { public function testFailure() { $this->assertSame(new stdClass, new stdClass); } } ?>
phpunit SameTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 4.75Mb
There was 1 failure:
1) SameTest::testFailure
Failed asserting that two variables reference the same object.
/home/sb/SameTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertStringEndsWith(string $suffix, string $string[, string $message = ''])
$string
が $suffix
で終わっていない場合にエラー $message
を報告します。
assertStringEndsNotWith()
はこのアサーションの逆で、同じ引数をとります。
例 A.46: assertStringEndsWith() の使用法
<?php use PHPUnit\Framework\TestCase; class StringEndsWithTest extends TestCase { public function testFailure() { $this->assertStringEndsWith('suffix', 'foo'); } } ?>
phpunit StringEndsWithTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 1 second, Memory: 5.00Mb
There was 1 failure:
1) StringEndsWithTest::testFailure
Failed asserting that 'foo' ends with "suffix".
/home/sb/StringEndsWithTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertStringEqualsFile(string $expectedFile, string $actualString[, string $message = ''])
$expectedFile
で指定したファイルの内容に $actualString
が含まれない場合にエラー $message
を報告します。
assertStringNotEqualsFile()
はこのアサーションの逆で、同じ引数をとります。
例 A.47: assertStringEqualsFile() の使用法
<?php use PHPUnit\Framework\TestCase; class StringEqualsFileTest extends TestCase { public function testFailure() { $this->assertStringEqualsFile('/home/sb/expected', 'actual'); } } ?>
phpunit StringEqualsFileTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) StringEqualsFileTest::testFailure
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'expected
-'
+'actual'
/home/sb/StringEqualsFileTest.php:6
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
assertStringStartsWith(string $prefix, string $string[, string $message = ''])
$string
が $prefix
で始まっていない場合にエラー $message
を報告します。
assertStringStartsNotWith()
はこのアサーションの逆で、同じ引数をとります。
例 A.48: assertStringStartsWith() の使用法
<?php use PHPUnit\Framework\TestCase; class StringStartsWithTest extends TestCase { public function testFailure() { $this->assertStringStartsWith('prefix', 'foo'); } } ?>
phpunit StringStartsWithTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) StringStartsWithTest::testFailure
Failed asserting that 'foo' starts with "prefix".
/home/sb/StringStartsWithTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
もっと複雑なアサーションを行う場合には、
PHPUnit_Framework_Constraint
クラスを使用します。
これらは、assertThat()
メソッドを使用して評価されます。
例 A.49 は、
logicalNot()
と equalTo()
を用いて assertNotEquals()
と同じアサーションを行う方法を示すものです。
assertThat(mixed $value, PHPUnit_Framework_Constraint $constraint[, $message = ''])
$value
が $constraint
にマッチしない場合にエラー $message
を報告します。
例 A.49: assertThat() の使用法
<?php use PHPUnit\Framework\TestCase; class BiscuitTest extends TestCase { public function testEquals() { $theBiscuit = new Biscuit('Ginger'); $myBiscuit = new Biscuit('Ginger'); $this->assertThat( $theBiscuit, $this->logicalNot( $this->equalTo($myBiscuit) ) ); } } ?>
表 A.1 に、
使用できる PHPUnit_Framework_Constraint
クラスをまとめます。
表A.1 制約
制約 | 意味 |
---|---|
PHPUnit_Framework_Constraint_Attribute attribute(PHPUnit_Framework_Constraint $constraint, $attributeName) | 別の制約を、クラスあるいはオブジェクトの属性として適用する制約。 |
PHPUnit_Framework_Constraint_IsAnything anything() | あらゆる入力値を受け入れる制約。 |
PHPUnit_Framework_Constraint_ArrayHasKey arrayHasKey(mixed $key) | 配列が指定したキーを保持していることを保証する制約。 |
PHPUnit_Framework_Constraint_TraversableContains contains(mixed $value) | Iterator インターフェイスを実装している array やオブジェクトが、指定した値を保持していることを保証する制約。 |
PHPUnit_Framework_Constraint_TraversableContainsOnly containsOnly(string $type) | 評価対象の array 、あるいは Iterator インターフェイスを実装したオブジェクトが、指定した型の唯一の値を含むことを保証する制約。 |
PHPUnit_Framework_Constraint_TraversableContainsOnly containsOnlyInstancesOf(string $classname) | 評価対象の array 、あるいは Iterator インターフェイスを実装したオブジェクトが、指定したクラスの唯一のインスタンスを含むことを保証する制約。 |
PHPUnit_Framework_Constraint_IsEqual equalTo($value, $delta = 0, $maxDepth = 10) | ある値が別の値と等しいかどうかを調べる制約。 |
PHPUnit_Framework_Constraint_Attribute attributeEqualTo($attributeName, $value, $delta = 0, $maxDepth = 10) | ある値がクラスあるいはオブジェクトの属性と等しいかどうかを調べる制約。 |
PHPUnit_Framework_Constraint_DirectoryExists directoryExists() | 指定した名前のディレクトリが存在するかどうかを調べる制約。 |
PHPUnit_Framework_Constraint_FileExists fileExists() | 指定した名前のファイルが存在するかどうかを調べる制約。 |
PHPUnit_Framework_Constraint_IsReadable isReadable() | 指定した名前のファイルが読み込み可能かどうかを調べる制約。 |
PHPUnit_Framework_Constraint_IsWritable isWritable() | 指定した名前のファイルが書き込み可能かどうかを調べる制約。 |
PHPUnit_Framework_Constraint_GreaterThan greaterThan(mixed $value) | 評価される値が、指定した値より大きいことを保証する制約。 |
PHPUnit_Framework_Constraint_Or greaterThanOrEqual(mixed $value) | 評価される値が、指定した値以上であることを保証する制約。 |
PHPUnit_Framework_Constraint_ClassHasAttribute classHasAttribute(string $attributeName) | 評価されるクラスに、指定した属性があることを保証する制約。 |
PHPUnit_Framework_Constraint_ClassHasStaticAttribute classHasStaticAttribute(string $attributeName) | 評価されるクラスに、指定した static 属性があることを保証する制約。 |
PHPUnit_Framework_Constraint_ObjectHasAttribute hasAttribute(string $attributeName) | 評価されるオブジェクトが、指定した属性を保持していることを保証する制約。 |
PHPUnit_Framework_Constraint_IsIdentical identicalTo(mixed $value) | ある値が別の値と同一であることを保証する制約。 |
PHPUnit_Framework_Constraint_IsFalse isFalse() | 評価される値が false であることを保証する制約。 |
PHPUnit_Framework_Constraint_IsInstanceOf isInstanceOf(string $className) | 評価されるオブジェクトが、指定したクラスのインスタンスであることを保証する制約。 |
PHPUnit_Framework_Constraint_IsNull isNull() | 評価される値が NULL であることを保証する制約。 |
PHPUnit_Framework_Constraint_IsTrue isTrue() | 評価される値が true であることを保証する制約。 |
PHPUnit_Framework_Constraint_IsType isType(string $type) | 評価される値が、指定した型であることを保証する制約。 |
PHPUnit_Framework_Constraint_LessThan lessThan(mixed $value) | 評価される値が、指定した値より小さいことを保証する制約。 |
PHPUnit_Framework_Constraint_Or lessThanOrEqual(mixed $value) | 評価される値が、指定した値以下であることを保証する制約。 |
logicalAnd() | 論理積 (AND)。 |
logicalNot(PHPUnit_Framework_Constraint $constraint) | 論理否定 (NOT)。 |
logicalOr() | 論理和 (OR)。 |
logicalXor() | 排他的論理和 (XOR)。 |
PHPUnit_Framework_Constraint_PCREMatch matchesRegularExpression(string $pattern) | 評価される文字列が、正規表現にマッチすることを保証する制約。 |
PHPUnit_Framework_Constraint_StringContains stringContains(string $string, bool $case) | 評価される文字列が、指定した文字列を含むことを保証する制約。 |
PHPUnit_Framework_Constraint_StringEndsWith stringEndsWith(string $suffix) | 評価される文字列が、指定したサフィックスで終わることを保証する制約。 |
PHPUnit_Framework_Constraint_StringStartsWith stringStartsWith(string $prefix) | 評価される文字列が、指定したプレフィックスで始まることを保証する制約。 |
assertTrue(bool $condition[, string $message = ''])
$condition
が false
の場合にエラー $message
を報告します。
assertNotTrue()
はこのアサーションの逆で、同じ引数をとります。
例 A.50: assertTrue() の使用法
<?php use PHPUnit\Framework\TestCase; class TrueTest extends TestCase { public function testFailure() { $this->assertTrue(false); } } ?>
phpunit TrueTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) TrueTest::testFailure
Failed asserting that false is true.
/home/sb/TrueTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile[, string $message = ''])
$actualFile
の XML ドキュメントが $expectedFile
の XML ドキュメントと異なる場合にエラー $message
を報告します。
assertXmlFileNotEqualsXmlFile()
はこのアサーションの逆で、同じ引数をとります。
例 A.51: assertXmlFileEqualsXmlFile() の使用法
<?php use PHPUnit\Framework\TestCase; class XmlFileEqualsXmlFileTest extends TestCase { public function testFailure() { $this->assertXmlFileEqualsXmlFile( '/home/sb/expected.xml', '/home/sb/actual.xml'); } } ?>
phpunit XmlFileEqualsXmlFileTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) XmlFileEqualsXmlFileTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
<?xml version="1.0"?>
<foo>
- <bar/>
+ <baz/>
</foo>
/home/sb/XmlFileEqualsXmlFileTest.php:7
FAILURES!
Tests: 1, Assertions: 3, Failures: 1.
assertXmlStringEqualsXmlFile(string $expectedFile, string $actualXml[, string $message = ''])
$actualXml
の XML ドキュメントが $expectedFile
の XML ドキュメントと異なる場合にエラー $message
を報告します。
assertXmlStringNotEqualsXmlFile()
はこのアサーションの逆で、同じ引数をとります。
例 A.52: assertXmlStringEqualsXmlFile() の使用法
<?php use PHPUnit\Framework\TestCase; class XmlStringEqualsXmlFileTest extends TestCase { public function testFailure() { $this->assertXmlStringEqualsXmlFile( '/home/sb/expected.xml', '<foo><baz/></foo>'); } } ?>
phpunit XmlStringEqualsXmlFileTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.25Mb
There was 1 failure:
1) XmlStringEqualsXmlFileTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
<?xml version="1.0"?>
<foo>
- <bar/>
+ <baz/>
</foo>
/home/sb/XmlStringEqualsXmlFileTest.php:7
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
assertXmlStringEqualsXmlString(string $expectedXml, string $actualXml[, string $message = ''])
$actualXml
の XML ドキュメントが $expectedXml
の XML ドキュメントと異なる場合にエラー $message
を報告します。
assertXmlStringNotEqualsXmlString()
はこのアサーションの逆で、同じ引数をとります。
例 A.53: assertXmlStringEqualsXmlString() の使用法
<?php use PHPUnit\Framework\TestCase; class XmlStringEqualsXmlStringTest extends TestCase { public function testFailure() { $this->assertXmlStringEqualsXmlString( '<foo><bar/></foo>', '<foo><baz/></foo>'); } } ?>
phpunit XmlStringEqualsXmlStringTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) XmlStringEqualsXmlStringTest::testFailure
Failed asserting that two DOM documents are equal.
--- Expected
+++ Actual
@@ @@
<?xml version="1.0"?>
<foo>
- <bar/>
+ <baz/>
</foo>
/home/sb/XmlStringEqualsXmlStringTest.php:7
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
アノテーションとはメタデータを表す特別な構文のことで、
プログラミング言語のソースコードに追加することができます。
PHP そのものにはソースコードにアノテーションする専用の仕組みはありませんが、
ドキュメンテーションブロックに @アノテーション名 引数
のようなタグを書くことでアノテーションを表すという記法が
PHP コミュニティ内で一般に使われています。
PHP では、リフレクション API の getDocComment()
メソッドを使えば関数、クラス、メソッド、属性
それぞれのドキュメンテーションブロックにアクセスすることができます。
PHPUnit などのアプリケーションでは、
この情報をもとに実行時の振る舞いを設定するのです。
PHP の doc コメントは、/**
で始めて
*/
で終わる必要があります。
その他の形式のコメントで書いたアノテーションは、無視されます。
本章では、PHPUnit がサポートするすべてのアノテーションについて解説します。
@author
アノテーションは
@group
アノテーション (「@group」 を参照ください) のエイリアスで、
テストの作者にもとづいたフィルタリングができるようになります。
@after
アノテーションを使うと、
テストケースクラス内の各テストメソッドを実行した後に呼ぶメソッドを指定できます。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @after */ public function tearDownSomeFixtures() { // ... } /** * @after */ public function tearDownSomeOtherFixtures() { // ... } }
@afterClass
アノテーションを使うと、
テストケースクラス内の各テストメソッドを実行した後に呼ぶ静的メソッドを指定できます。
ここで共有フィクスチャの後始末をします。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @afterClass */ public static function tearDownSomeSharedFixtures() { // ... } /** * @afterClass */ public static function tearDownSomeOtherSharedFixtures() { // ... } }
グローバル変数の保存や復元を、テストケースクラスのすべてのテストで完全に無効にすることができます。 このように使います。
use PHPUnit\Framework\TestCase; /** * @backupGlobals disabled */ class MyTest extends TestCase { // ... }
@backupGlobals
アノテーションは、テストメソッドレベルで使うこともできます。
これによって、保存と復元の操作をより細やかに制御できるようになります。
use PHPUnit\Framework\TestCase; /** * @backupGlobals disabled */ class MyTest extends TestCase { /** * @backupGlobals enabled */ public function testThatInteractsWithGlobalVariables() { // ... } }
@backupStaticAttributes
アノテーションを使うと、
宣言されたクラス内のすべての static プロパティの値をバックアップしてからテストを始め、
テストが終わった後でそれらの値を復元することができます。
テストケースクラス単位、あるいはテストメソッド単位で使えます。
use PHPUnit\Framework\TestCase; /** * @backupStaticAttributes enabled */ class MyTest extends TestCase { /** * @backupStaticAttributes disabled */ public function testThatInteractsWithStaticAttributes() { // ... } }
PHP の内部的な制約のため、
@backupStaticAttributes
が、
意図していない static 値を保存してしまい、
その後のテストに影響してしまうことがあります。
詳細は 「グローバルな状態」 を参照ください。
@before
アノテーションを使うと、
テストケースクラス内の各テストメソッドを実行する前に呼ぶメソッドを指定できます。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @before */ public function setupSomeFixtures() { // ... } /** * @before */ public function setupSomeOtherFixtures() { // ... } }
@beforeClass
アノテーションを使うと、
テストケースクラス内の各テストメソッドを実行する前に呼ぶ静的メソッドを指定できます。
ここで共有フィクスチャの準備をします。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @beforeClass */ public static function setUpSomeSharedFixtures() { // ... } /** * @beforeClass */ public static function setUpSomeOtherSharedFixtures() { // ... } }
@codeCoverageIgnore
や
@codeCoverageIgnoreStart
、そして
@codeCoverageIgnoreEnd
アノテーションを使うと、
コード内の特定の行をカバレッジ解析の対象外にできます。
利用法は 「コードブロックの無視」 を参照ください。
@covers
アノテーションをテストコードで使うと、
そのテストメソッドがどのメソッドをテストするのかを指定することができます。
/** * @covers BankAccount::getBalance */ public function testBalanceIsInitiallyZero() { $this->assertEquals(0, $this->ba->getBalance()); }
これを指定した場合は、指定したメソッドのみのコードカバレッジ情報を考慮することになります。
表 B.1
に @covers
アノテーションの構文を示します。
表B.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
アノテーションを使うと、
デフォルトの名前空間あるいはクラス名を指定できます。
こうすることで、
@covers
アノテーションのたびに長い名前を繰り返す必要がなくなります。
例 B.1
を参照ください。
例 B.1: @coversDefaultClass を使ったアノテーションの短縮
<?php use PHPUnit\Framework\TestCase; /** * @coversDefaultClass \Foo\CoveredClass */ class CoversDefaultClassTest extends TestCase { /** * @covers ::publicMethod */ public function testSomething() { $o = new Foo\CoveredClass; $o->publicMethod(); } } ?>
@coversNothing
アノテーションをテストコードで使うと、
そのテストケースについてはコードカバレッジ情報を記録しないように指定できます。
これはインテグレーションテストで使えます。例として 例 11.3 を参照ください。
このメソッドはクラスレベルおよびメソッドレベルで使え、
あらゆる @covers
タグを上書きします。
テストメソッドには任意の引数を渡すことができます。
引数は、データプロバイダメソッド
(例 2.5 の
provider()
) から渡されます。
使用するデータプロバイダメソッドを指定するには
@dataProvider
アノテーションを使います。
詳細は 「データプロバイダ」 を参照ください。
PHPUnit は、テストメソッド間の依存性の明示的な宣言をサポートしています。
この依存性とは、テストメソッドが実行される順序を定義するものではありません。
プロデューサーがテストフィクスチャを作ってそのインスタンスを返し、
依存するコンシューマーがそれを受け取って利用するというものです。
例 2.2
は、@depends
アノテーションを使ってテストメソッドの依存性をあらわす例です。
詳細は 「テストの依存性」 を参照ください。
例 2.10
は、テストするコード内で例外がスローされたかどうかを
@expectedException
アノテーションを使用して調べる方法を示すものです。
詳細は 「例外のテスト」 を参照ください。
@expectedExceptionCode
アノテーションを
@expectedException
と組み合わせて使うと、
スローされた例外のエラーコードについてのアサーションが可能となり、
例外をより狭い範囲に特定できるようになります。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @expectedException MyException * @expectedExceptionCode 20 */ public function testExceptionHasErrorcode20() { throw new MyException('Some Message', 20); } }
テストを実行しやすくし、重複を減らすために、
ショートカットを使ってクラス定数を指定することができます。
@expectedExceptionCode
で
"@expectedExceptionCode ClassName::CONST
" のようにして使います。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @expectedException MyException * @expectedExceptionCode MyClass::ERRORCODE */ public function testExceptionHasErrorcode20() { throw new MyException('Some Message', 20); } } class MyClass { const ERRORCODE = 20; }
@expectedExceptionMessage
アノテーションは
@expectedExceptionCode
と似ており、
例外のエラーメッセージに関するアサーションを行います。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @expectedException MyException * @expectedExceptionMessage Some Message */ public function testExceptionHasRightMessage() { throw new MyException('Some Message', 20); } }
期待するメッセージを、例外メッセージの一部にすることもできます。 これは、特定の名前や渡したパラメータが例外に表示されることを確かめたいけれども 例外メッセージ全体は固定していない場合に便利です。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @expectedException MyException * @expectedExceptionMessage broken */ public function testExceptionHasRightMessage() { $param = "broken"; throw new MyException('Invalid parameter "'.$param.'".', 20); } }
テストを実行しやすくし、重複を減らすために、
ショートカットを使ってクラス定数を指定することができます。
@expectedExceptionMessage
で
"@expectedExceptionMessage ClassName::CONST
" のようにして使います。
サンプルコードは 「@expectedExceptionCode」 を参照ください。
期待するメッセージを、@expectedExceptionMessageRegExp
アノテーションを使って正規表現で指定することもできます。
これは、部分文字列だけでは指定したメッセージとのマッチングが不十分なときに便利です。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @expectedException MyException * @expectedExceptionMessageRegExp /Argument \d+ can not be an? \w+/ */ public function testExceptionHasRightMessage() { throw new MyException('Argument 2 can not be an integer'); } }
あるテストを、ひとつあるいは複数のグループに属するものとすることができます。
@group
アノテーションをこのように使用します。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @group specification */ public function testSomething() { } /** * @group regresssion * @group bug2204 */ public function testSomethingElse() { } }
特定のグループに属するテストのみを選んで実行するには、
コマンドラインのテストランナーの場合は
--group
オプションあるいは --exclude-group
オプションを指定します。XML 設定ファイルの場合は、
それぞれ対応するディレクティブを指定します。
@large
アノテーションは、
@group large
のエイリアスです。
PHP_Invoker
パッケージがインストールされていて strict モードが有効な場合に、
large テストは実行時間が 60 秒を超えたら失敗します。
このタイムアウト時間は、XML 設定ファイルの
timeoutForLargeTests
属性で変更できます。
@medium
アノテーションは
@group medium
のエイリアスです。
medium テストは、@large
とマークしたテストに依存してはいけません。
PHP_Invoker
パッケージがインストールされていて strict モードが有効な場合に、
medium テストは実行時間が 10 秒を超えたら失敗します。
このタイムアウト時間は、XML 設定ファイルの
timeoutForMediumTests
属性で変更できます。
テストを別プロセスで実行するときに、
PHPUnit は親プロセスのグローバルな状態を保存しようと試みます。
親プロセスのすべてのグローバル状態をシリアライズし、
子プロセス内で最後にそれをアンシリアライズするのです。
しかし、親プロセスのグローバル状態の中にもしシリアライズできないものがあれば、
問題が発生します。この問題に対応するために、グローバル状態の保存を無効にすることができます。
そのために使うのが @preserveGlobalState
アノテーションです。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @runInSeparateProcess * @preserveGlobalState disabled */ public function testInSeparateProcess() { // ... } }
@requires
アノテーションを使うと、共通の事前条件
(たとえば PHP のバージョンや拡張モジュールのインストール状況)
を満たさないときにテストをスキップできます。
条件に指定できる内容やその例については 表 7.3 を参照ください。
テストクラス内のすべてのテストケースを、個別の PHP プロセスで実行するように指示します。
use PHPUnit\Framework\TestCase; /** * @runTestsInSeparateProcesses */ class MyTest extends TestCase { // ... }
注意: デフォルトで、PHPUnit は親プロセスのグローバルな状態を保存しようと試みます。 親プロセスのすべてのグローバル状態をシリアライズし、 子プロセス内で最後にそれをアンシリアライズするのです。 しかし、親プロセスのグローバル状態の中にもしシリアライズできないものがあれば、 問題が発生します。この問題に対応するために、グローバル状態の保存を無効にすることができます。 この問題の対処法については 「@preserveGlobalState」 を参照ください。
そのテストを個別の PHP プロセスで実行するように指示します。
use PHPUnit\Framework\TestCase; class MyTest extends TestCase { /** * @runInSeparateProcess */ public function testInSeparateProcess() { // ... } }
注意: デフォルトで、PHPUnit は親プロセスのグローバルな状態を保存しようと試みます。 親プロセスのすべてのグローバル状態をシリアライズし、 子プロセス内で最後にそれをアンシリアライズするのです。 しかし、親プロセスのグローバル状態の中にもしシリアライズできないものがあれば、 問題が発生します。この問題に対応するために、グローバル状態の保存を無効にすることができます。 この問題の対処法については 「@preserveGlobalState」 を参照ください。
@small
アノテーションは
@group small
のエイリアスです。
small テストは、@medium
や @large
とマークしたテストに依存してはいけません。
PHP_Invoker
パッケージがインストールされていて strict モードが有効な場合に、
small テストは実行時間が 1 秒を超えたら失敗します。
このタイムアウト時間は、XML 設定ファイルの
timeoutForSmallTests
属性で変更できます。
テストの実行時間の制限を有効にするには、@small
、
@medium
、@large
のいずれかのアノテーションで明示的に指定する必要があります。
テストメソッド名の先頭に test
をつけるかわりに、メソッドのドキュメンテーションブロックで @test
アノテーションを使ってそのメソッドがテストメソッドであることを指定することができます。
/** * @test */ public function initialBalanceShouldBe0() { $this->assertEquals(0, $this->ba->getBalance()); }
@uses
アノテーションは、
テストから実行されてはいるが、そのテストでカバーするつもりはないコードを指定します。
たとえば、コード片をテストするために必要な値オブジェクトなどに使います。
/** * @covers BankAccount::deposit * @uses Money */ public function testMoneyCanBeDepositedInAccount() { // ... }
このアノテーションは、厳密なカバレッジモードで使うと特に有用です。 このモードの場合、意図せずカバーしてしまったコードがテストを失敗させてしまうことがあるからです。 厳密なカバレッジモードに関する詳細な情報は 「意図せぬうちにカバーされているコード」 を参照ください。
<phpunit>
要素の属性を使って
PHPUnit のコア機能を設定します。
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd" backupGlobals="true" backupStaticAttributes="false" <!--bootstrap="/path/to/bootstrap.php"--> cacheTokens="false" colors="false" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" forceCoversAnnotation="false" mapTestClassNameToCoveredClassName="false" printerClass="PHPUnit_TextUI_ResultPrinter" <!--printerFile="/path/to/ResultPrinter.php"--> processIsolation="false" stopOnError="false" stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" stopOnRisky="false" testSuiteLoaderClass="PHPUnit_Runner_StandardTestSuiteLoader" <!--testSuiteLoaderFile="/path/to/StandardTestSuiteLoader.php"--> timeoutForSmallTests="1" timeoutForMediumTests="10" timeoutForLargeTests="60" verbose="false"> <!-- ... --> </phpunit>
上の XML 設定ファイルは、TextUI テストランナーをデフォルトの設定で起動します。 その詳細は 「コマンドラインオプション」 で説明します。
その他、コマンドラインからは設定できないオプションもあります。
convertErrorsToExceptions
false
にすると、
すべての PHP のエラーを例外に変換するエラーハンドラをインストールしません。
convertNoticesToExceptions
false
にすると、
convertErrorsToExceptions
でインストールしたエラーハンドラが
E_NOTICE
や E_USER_NOTICE
そして
E_STRICT
を例外に変換しなくなります。
convertWarningsToExceptions
false
にすると、
convertErrorsToExceptions
でインストールしたエラーハンドラが
E_WARNING
や E_USER_WARNING
を例外に変換しなくなります。
forceCoversAnnotation
コードカバレッジの記録を、
@covers
アノテーションを使っている関数だけに限定します。
このアノテーションについては
「@covers」 で説明します。
timeoutForLargeTests
テストのサイズによるタイムアウト値を制限する場合に、
@large
とマークされたすべてのテストのタイムアウトをこの属性で設定します。
ここで指定した時間内にテストが完了しなかった場合、テストは失敗します。
timeoutForMediumTests
テストのサイズによるタイムアウト値を制限する場合に、
@medium
とマークされたすべてのテストのタイムアウトをこの属性で設定します。
ここで指定した時間内にテストが完了しなかった場合、テストは失敗します。
timeoutForSmallTests
テストのサイズによるタイムアウト値を制限する場合に、
@medium
あるいは @large
のいずれのマークもついていないすべてのテストのタイムアウトをこの属性で設定します。
ここで指定した時間内にテストが完了しなかった場合、テストは失敗します。
<testsuites>
要素とその子要素である
<testsuite>
を使って、
テストスイート群やテストケース群の中からテストスイートを構成します。
<testsuites> <testsuite name="My Test Suite"> <directory>/path/to/*Test.php files</directory> <file>/path/to/MyTest.php</file> <exclude>/path/to/exclude</exclude> </testsuite> </testsuites>
phpVersion
および
phpVersionOperator
属性を使うと、必要な PHP
のバージョンを指定できます。次の例は、PHP のバージョンが 5.3.0 以降である場合にのみ
/path/to/*Test.php
と
/path/to/MyTest.php
を追加します。
<testsuites> <testsuite name="My Test Suite"> <directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">/path/to/files</directory> <file phpVersion="5.3.0" phpVersionOperator=">=">/path/to/MyTest.php</file> </testsuite> </testsuites>
phpVersionOperator
属性はオプションで、デフォルトは
>=
です。
<groups>
要素とその子要素である
<include>
、
<exclude>
および
<group>
を使って、
@group
アノテーション
(「@group」 を参照ください)
でマークされたテストグループから実行する (しない) ものを選びます。
<groups> <include> <group>name</group> </include> <exclude> <group>name</group> </exclude> </groups>
上の XML 設定ファイルは、 TextUI テストランナーを以下の引数で起動します。
--group name
--exclude-group name
<filter>
要素とその子要素を使って、
コードカバレッジレポートのホワイトリストを設定します。
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">/path/to/files</directory> <file>/path/to/file</file> <exclude> <directory suffix=".php">/path/to/files</directory> <file>/path/to/file</file> </exclude> </whitelist> </filter>
<logging>
要素とその子要素である
<log>
を使って、
テストの実行結果のログ出力を設定します。
<logging> <log type="coverage-html" target="/tmp/report" lowUpperBound="35" highLowerBound="70"/> <log type="coverage-clover" target="/tmp/coverage.xml"/> <log type="coverage-php" target="/tmp/coverage.serialized"/> <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> <log type="junit" target="/tmp/logfile.xml" logIncompleteSkipped="false"/> <log type="testdox-html" target="/tmp/testdox.html"/> <log type="testdox-text" target="/tmp/testdox.txt"/> </logging>
上の XML 設定ファイルは、 TextUI テストランナーを以下の引数で起動します。
--coverage-html /tmp/report
--coverage-clover /tmp/coverage.xml
--coverage-php /tmp/coverage.serialized
--coverage-text
> /tmp/logfile.txt
--log-junit /tmp/logfile.xml
--testdox-html /tmp/testdox.html
--testdox-text /tmp/testdox.txt
lowUpperBound
、highLowerBound
、
logIncompleteSkipped
および showUncoveredFiles
属性には、TextUI テストランナーで対応するオプションがありません。
lowUpperBound
: カバー率がこの値に満たないときに、カバー率が "低い" とみなします。
highLowerBound
: カバー率がこの値を超えるときに、カバー率が "高い" とみなします。
showUncoveredFiles
: --coverage-text
の出力で、カバレッジ情報だけではなくホワイトリストの全ファイル一覧も表示します。
showOnlySummary
: Show only the summary in --coverage-text
output.
<listeners>
要素とその子要素である
<listener>
を使って、
テスト実行時にテストリスナーをアタッチします。
<listeners> <listener class="MyListener" file="/optional/path/to/MyListener.php"> <arguments> <array> <element key="0"> <string>Sebastian</string> </element> </array> <integer>22</integer> <string>April</string> <double>19.78</double> <null/> <object class="stdClass"/> </arguments> </listener> </listeners>
上の XML 設定は、
$listener
オブジェクト (以下を参照ください)
をテストの実行時にアタッチします。
$listener = new MyListener( ['Sebastian'], 22, 'April', 19.78, null, new stdClass );
<php>
要素とその子要素を使って、
PHP の設定や定数、グローバル変数を設定します。また、
include_path
の先頭にパスを追加することもできます。
<php> <includePath>.</includePath> <ini name="foo" value="bar"/> <const name="foo" value="bar"/> <var name="foo" value="bar"/> <env name="foo" value="bar"/> <post name="foo" value="bar"/> <get name="foo" value="bar"/> <cookie name="foo" value="bar"/> <server name="foo" value="bar"/> <files name="foo" value="bar"/> <request name="foo" value="bar"/> </php>
上の XML 設定は、次の PHP コードに対応します。
ini_set('foo', 'bar'); define('foo', 'bar'); $GLOBALS['foo'] = 'bar'; $_ENV['foo'] = 'bar'; $_POST['foo'] = 'bar'; $_GET['foo'] = 'bar'; $_COOKIE['foo'] = 'bar'; $_SERVER['foo'] = 'bar'; $_FILES['foo'] = 'bar'; $_REQUEST['foo'] = 'bar';
Copyright (c) 2005-2017 Sebastian Bergmann. この作品は、Creative Commons Attribution License の下で ライセンスされています。このライセンスの内容を確認するには、 http://creativecommons.org/licenses/by/2.0/ を訪問するか、あるいは Creative Commons, 559 Nathan Abbott Way, Stanford, California 943.6, USA. に手紙を送ってください。 このライセンスの概要を以下に示します。その後に、完全な文書を示します。 -------------------------------------------------------------------- あなたは以下の条件に従う場合に限り、自由に * 本作品を複製、頒布、展示、実演することができます。 * 二次的著作物を作成することができます。 * 本作品を営利目的で利用することができます。 あなたの従うべき条件は以下の通りです。 帰属. あなたは原著作者のクレジットを表示しなければなりません。 * 再利用や頒布にあたっては、この作品の使用許諾条件を他の人々に 明らかにしなければなりません。 * 著作[権]者から許可を得ると、これらの条件は適用されません。 上記によってあなたのフェアユースその他の権利が影響を受けることは まったくありません。 これは、以下に示す完全なライセンスの要約です。 ==================================================================== Creative Commons Legal Code Attribution 3.0 Unported CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. License THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. c. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three- dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, d. to Distribute and Publicly Perform Adaptations. e. For the avoidance of doubt: i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. b. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv), consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. c. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. Creative Commons may be contacted at http://creativecommons.org/. ====================================================================