PHP
ユニットテスト
サーチ…
構文
- アサーションの完全なリスト例:
-
assertTrue(bool $condition[, string $messageIfFalse = '']);
-
assertEquals(mixed $expected, mixed $actual[, string $messageIfNotEqual = '']);
備考
Unit
テストは、我々が期待通りに入力して取引が含まれているかどうかを確認するためのテストのソースコードのために使用されています。 Unit
テストはフレームワークの大部分でサポートされています。いくつかの異なるPHPUnitテストがあり、構文が異なる場合があります。この例ではPHPUnit
を使用していPHPUnit
。
クラスルールのテスト
ルール()メソッドを持つ簡単なLoginForm
クラスがあるとします(ログイン・ページでフレームワーク・テンプレートとして使用されます)。
class LoginForm {
public $email;
public $rememberMe;
public $password;
/* rules() method returns an array with what each field has as a requirement.
* Login form uses email and password to authenticate user.
*/
public function rules() {
return [
// Email and Password are both required
[['email', 'password'], 'required'],
// Email must be in email format
['email', 'email'],
// rememberMe must be a boolean value
['rememberMe', 'boolean'],
// Password must match this pattern (must contain only letters and numbers)
['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
];
}
/** the validate function checks for correctness of the passed rules */
public function validate($rule) {
$success = true;
list($var, $type) = $rule;
foreach ((array) $var as $var) {
switch ($type) {
case "required":
$success = $success && $this->$var != "";
break;
case "email":
$success = $success && filter_var($this->$var, FILTER_VALIDATE_EMAIL);
break;
case "boolean":
$success = $success && filter_var($this->$var, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null;
break;
case "match":
$success = $success && preg_match($rule["pattern"], $this->$var);
break;
default:
throw new \InvalidArgumentException("Invalid filter type passed")
}
}
return $success;
}
}
このクラスでテストを実行するために、 Unitテスト(ソースコードを調べて、期待通りのものかどうかを調べる)を使用します。
class LoginFormTest extends TestCase {
protected $loginForm;
// Executing code on the start of the test
public function setUp() {
$this->loginForm = new LoginForm;
}
// To validate our rules, we should use the validate() method
/**
* This method belongs to Unit test class LoginFormTest and
* it's testing rules that are described above.
*/
public function testRuleValidation() {
$rules = $this->loginForm->rules();
// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");
// Test email validation
// Since we made email to be in email format, it cannot be empty
$this->loginForm->email = '';
$this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (empty)");
// It does not contain "@" in string so it's invalid
$this->loginForm->email = 'invalid.email.com';
$this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (invalid format)");
// Revert email to valid for next test
$this->loginForm->email = '[email protected]';
// Test password validation
// Password cannot be empty (since it's required)
$this->loginForm->password = '';
$this->assertFalse($this->loginForm->validate($rules), "Password should not be valid (empty)");
// Revert password to valid for next test
$this->loginForm->password = 'ThisIsMyPassword';
// Test rememberMe validation
$this->loginForm->rememberMe = 999;
$this->assertFalse($this->loginForm->validate($rules), "RememberMe should not be valid (integer type)");
// Revert remeberMe to valid for next test
$this->loginForm->rememberMe = true;
}
}
Unit
テストは、ここでどの程度正確に(一般的な例を除いて)助けてくれるでしょうか?たとえば、予期せぬ結果が得られたときは非常によく適合します。たとえば、このルールを以前のものから取ってみましょう。
['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
代わりに、私たちが1つの重要なことを見逃してこれを書いたなら、
['password', 'match', 'pattern' => '/^[a-z0-9]$/i'],
何十もの異なるルール(電子メールとパスワードだけでなく、私たちが使用していることを前提としています)では、間違いを検出することは困難です。このユニットテスト:
// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");
私たちの最初の例は通過しますが、 秒は通過しません。どうして? 2番目の例では、タイプミス( +
記号がありません)というパターンを書いています。これは、1文字/数字のみを受け入れることを意味します。
ユニットテストはコンソールでphpunit [path_to_file]
コマンドで実行できます。すべてがOKであれば、すべてのテストがOK
状態であることがわかるはずです。そうでなければ、 Error
(構文エラー)またはFail
(そのメソッドの少なくとも1行が合格しませんでした)が表示されます。
--coverage
ような追加のパラメータを使用すると、バックエンドコードのいくつの行がテストされたか、合格/不合格であったかを視覚的に見ることができます。これは、 PHPUnitをインストールしたフレームワークに適用されます 。
コンソールでPHPUnit
テストがどのように見えるかの例(この例ではない一般的な外観):
PHPUnitデータプロバイダ
テストメソッドでは、しばしばテストするデータが必要です。いくつかのメソッドを完全にテストするには、可能なすべてのテスト条件に対して異なるデータセットを提供する必要があります。もちろん、次のようにループを使って手動で行うことができます:
...
public function testSomething()
{
$data = [...];
foreach($data as $dataSet) {
$this->assertSomething($dataSet);
}
}
...
誰かがそれを便利に見つけることができます。しかし、このアプローチにはいくつかの欠点があります。まず、テスト関数がいくつかのパラメータを受け入れる場合、データを抽出するために追加のアクションを実行する必要があります。第2に、障害が発生した場合、追加のメッセージとデバッグなしで、失敗したデータセットを区別することは困難です。第3に、PHPUnitはデータプロバイダを使用してテストデータセットを自動的に処理します 。
データプロバイダは、特定のテストケースのデータを返す関数です。
データプロバイダメソッドはpublicで、 配列の配列またはIteratorインターフェイスを実装するオブジェクトを返し、各繰り返しステップごとに配列を生成する必要があります。コレクションの一部である配列ごとに、配列の内容を引数としてtestメソッドが呼び出されます。
テストでデータプロバイダを使用するには、指定されたデータプロバイダ関数の名前で@dataProvider
アノテーションを使用します。
/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a, $b)
{
$this->assertEquals($a, $b);
}
public function dataProviderForTest()
{
return [
[1,1],
[2,2],
[3,2] //this will fail
];
}
配列の配列
dataProviderForTest()
は配列の配列を返します。ネストされた各配列には2つの要素があり、testEquals()
必要なパラメータを1つずつ入力します。このようなエラーがスローされます。十分な要素がない場合Missing argument 2 for Test::testEquals()
がありません。 PHPUnitはデータを自動的にループしてテストを実行します:
public function dataProviderForTest()
{
return [
[1,1], // [0] testEquals($a = 1, $b = 1)
[2,2], // [1] testEquals($a = 2, $b = 2)
[3,2] // [2] There was 1 failure: 1) Test::testEquals with data set #2 (3, 4)
];
}
それぞれのデータセットは便利のために名前を付けることができます。障害のあるデータを簡単に検出できます。
public function dataProviderForTest()
{
return [
'Test 1' => [1,1], // [0] testEquals($a = 1, $b = 1)
'Test 2' => [2,2], // [1] testEquals($a = 2, $b = 2)
'Test 3' => [3,2] // [2] There was 1 failure:
// 1) Test::testEquals with data set "Test 3" (3, 4)
];
}
イテレータ
class MyIterator implements Iterator {
protected $array = [];
public function __construct($array) {
$this->array = $array;
}
function rewind() {
return reset($this->array);
}
function current() {
return current($this->array);
}
function key() {
return key($this->array);
}
function next() {
return next($this->array);
}
function valid() {
return key($this->array) !== null;
}
}
...
class Test extends TestCase
{
/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a)
{
$toCompare = 0;
$this->assertEquals($a, $toCompare);
}
public function dataProviderForTest()
{
return new MyIterator([
'Test 1' => [0],
'Test 2' => [false],
'Test 3' => [null]
]);
}
}
ご覧のとおり、単純なイテレータも機能します。
単一のパラメータであっても、データプロバイダは配列
[$parameter]
返す必要があり[$parameter]
current()
メソッド(実際には各繰り返しでデータを返す)をthisに変更すると、
function current() {
return current($this->array)[0];
}
または実際のデータを変更する:
return new MyIterator([
'Test 1' => 0,
'Test 2' => false,
'Test 3' => null
]);
エラーが表示されます:
There was 1 warning:
1) Warning
The data provider specified for Test::testEquals is invalid.
もちろん、単純な配列に対して
Iterator
オブジェクトを使用することは有用ではありません。それはあなたのケースのためのいくつかの特定のロジックを実装する必要があります。
発電機
マニュアルには明示的に記載されていませんが、データプロバイダとしてジェネレータを使用することもできます。 Generator
クラスは実際にIterator
インタフェースを実装していることに注意してください。
そこで、 generator
と組み合わせたDirectoryIterator
の使用例を次に示します。
/**
* @param string $file
*
* @dataProvider fileDataProvider
*/
public function testSomethingWithFiles($fileName)
{
//$fileName is available here
//do test here
}
public function fileDataProvider()
{
$directory = new DirectoryIterator('path-to-the-directory');
foreach ($directory as $file) {
if ($file->isFile() && $file->isReadable()) {
yield [$file->getPathname()]; // invoke generator here.
}
}
}
プロバイダ注
yield
Sアレイ。代わりに無効なデータプロバイダの警告が表示されます。
テスト例外
例外をスローするメソッドをテストしたいとしましょう
class Car
{
/**
* @throws \Exception
*/
public function drive()
{
throw new \Exception('Useful message', 1);
}
}
メソッド呼び出しをtry / catchブロックに囲み、実行オブジェクトのプロパティに対してアサーションを作成することで、これを行うことができますが、より便利には、例外アサーションメソッドを使用できます。 PHPUnit 5.2では、expectX()メソッドを使用して例外タイプ、メッセージ&コードをアサートできます
class DriveTest extends PHPUnit_Framework_TestCase
{
public function testDrive()
{
// prepare
$car = new \Car();
$expectedClass = \Exception::class;
$expectedMessage = 'Useful message';
$expectedCode = 1;
// test
$this->expectException($expectedClass);
$this->expectMessage($expectedMessage);
$this->expectCode($expectedCode);
// invoke
$car->drive();
}
}
以前のバージョンのPHPUnitを使用している場合は、expectX()メソッドの代わりにsetExpectedExceptionメソッドを使用できますが、非推奨でバージョン6で削除されることに注意してください。
class DriveTest extends PHPUnit_Framework_TestCase
{
public function testDrive()
{
// prepare
$car = new \Car();
$expectedClass = \Exception::class;
$expectedMessage = 'Useful message';
$expectedCode = 1;
// test
$this->setExpectedException($expectedClass, $expectedMessage, $expectedCode);
// invoke
$car->drive();
}
}