テストの作成

Nette Tester のテスト作成がユニークなのは、各テストが個別に実行できる PHP スクリプトであることです。これには大きな可能性があります。 テストを作成しているときでさえ、それを簡単に実行して、正しく機能するかどうかを確認できます。そうでない場合は、IDE で簡単にステップ実行してエラーを探すことができます。

テストをブラウザで開くことさえできます。しかし何よりも – それを実行することによって、テストを実行します。合格したか失敗したかをすぐに確認できます。

導入章では、配列を操作する本当に簡単なテストを 示しました。今回は、テストする独自のクラスを作成しますが、それも単純なものになります。

ライブラリまたはプロジェクトの典型的なディレクトリ構造から始めます。テストをコードの残りの部分から分離することが重要です。たとえば、デプロイメントのためです。なぜなら、テストを本番サーバーにアップロードしたくないからです。構造は次のようになります。

├── src/           # テストするコード
│   ├── Rectangle.php
│   └── ...
├── tests/         # テスト
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json

そして今、個々のファイルを作成します。テスト対象のクラスから始め、それを src/Rectangle.php ファイルに配置します。

<?php
class Rectangle
{
	private float $width;
	private float $height;

	public function __construct(float $width, float $height)
	{
		if ($width < 0 || $height < 0) {
			throw new InvalidArgumentException('The dimension must not be negative.');
		}
		$this->width = $width;
		$this->height = $height;
	}

	public function getArea(): float
	{
		return $this->width * $this->height;
	}

	public function isSquare(): bool
	{
		return $this->width === $this->height;
	}
}

そして、それに対するテストを作成します。テストファイルの名前は *Test.php または *.phpt のマスクに一致する必要があります。たとえば、RectangleTest.php バリアントを選択します。

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// 一般的な長方形
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());  # 期待される結果を確認します
Assert::false($rect->isSquare());

ご覧のとおり、Assert::same() のような、いわゆる アサーションメソッド は、実際の値が期待値に対応することを確認するために使用されます。

残りの最後のステップは、bootstrap.php ファイルです。これには、すべてのテストに共通のコードが含まれています。たとえば、クラスのオートロード、環境設定、一時ディレクトリの作成、ヘルパー関数などです。すべてのテストはブートストラップを読み込み、その後はテストのみに専念します。ブートストラップは次のようになります。

<?php
require __DIR__ . '/vendor/autoload.php';   # Composer オートローダーを読み込みます

Tester\Environment::setup();                # Nette Tester を初期化します

// その他の設定(これは単なる例であり、私たちの場合には必要ありません)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');

上記のブートストラップは、Composer オートローダーが Rectangle.php クラスも読み込めることを前提としています。これは、たとえば composer.json などで autoload セクションを設定 することによって実現できます。

テストは、他の独立した PHP スクリプトと同様に、コマンドラインから実行できるようになりました。最初の実行で構文エラーが明らかになり、タイプミスがなければ、次のように表示されます。

$ php RectangleTest.php

OK

テストでアサーションを偽の Assert::same(123, $rect->getArea()); に変更すると、次のようになります。

$ php RectangleTest.php

Failed: 200.0 should be 123

in RectangleTest.php(5) Assert::same(123, $rect->getArea());

FAILURE

テストを作成するときは、すべての境界状況をカバーすることをお勧めします。たとえば、入力がゼロ、負の数、他のケースでは空の文字列、null などです。実際には、そのような状況でコードがどのように動作するかを考え、決定することを強制します。テストはその後、その動作を固定します。

私たちの場合、負の値は例外をスローする必要があり、これは Assert::exception() を使用して検証します。

// 幅は負であってはなりません
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'The dimension must not be negative.',
);

そして、高さに対して同様のテストを追加します。最後に、両方の寸法が同じ場合に isSquare()true を返すことをテストします。演習として、そのようなテストを作成してみてください。

より明確なテスト

テストファイルのサイズは大きくなり、すぐにわかりにくくなる可能性があります。したがって、個々のテスト領域を個別の関数にグループ化することが実用的です。

まず、より単純ですがエレガントなバリアント、つまりグローバル関数 test() を使用する方法を示します。Tester は、コード内に同じ名前の関数がある場合に衝突を避けるために、自動的に作成しません。bootstrap.php ファイルで呼び出す setupFunctions() メソッドによって作成されます。

Tester\Environment::setup();
Tester\Environment::setupFunctions();

この関数を使用すると、テストファイルを名前付きのユニットにきれいに分割できます。実行すると、説明が順番に出力されます。

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

test('一般的な長方形', function () {
	$rect = new Rectangle(10, 20);
	Assert::same(200.0, $rect->getArea());
	Assert::false($rect->isSquare());
});

test('一般的な正方形', function () {
	$rect = new Rectangle(5, 5);
	Assert::same(25.0, $rect->getArea());
	Assert::true($rect->isSquare());
});

test('寸法は負であってはなりません', function () {
	Assert::exception(
		fn() => new Rectangle(-1, 20),
        InvalidArgumentException::class,
	);

	Assert::exception(
		fn() => new Rectangle(10, -1),
        InvalidArgumentException::class,
	);
});

各テストの前後にコードを実行する必要がある場合は、それを関数 setUp() または tearDown() に渡します。

setUp(function () {
	// 各 test() の前に実行される初期化コード
});

2 番目のバリアントはオブジェクト指向です。いわゆる TestCase を作成します。これは、個々のユニットが test– で始まる名前を持つメソッドであるクラスです。

class RectangleTest extends Tester\TestCase
{
	public function testGeneralOblong()
	{
		$rect = new Rectangle(10, 20);
		Assert::same(200.0, $rect->getArea());
		Assert::false($rect->isSquare());
	}

	public function testGeneralSquare()
	{
		$rect = new Rectangle(5, 5);
		Assert::same(25.0, $rect->getArea());
		Assert::true($rect->isSquare());
	}

	/** @throws InvalidArgumentException */
	public function testWidthMustNotBeNegative()
	{
		$rect = new Rectangle(-1, 20);
	}

	/** @throws InvalidArgumentException */
	public function testHeightMustNotBeNegative()
	{
		$rect = new Rectangle(10, -1);
	}
}

// テストメソッドの実行
(new RectangleTest)->run();

今回は例外をテストするために @throw アノテーションを使用しました。詳細は TestCase の章で確認できます。

ヘルパー関数

Nette Tester には、たとえば HTML ドキュメントの内容のテスト、ファイルを操作する関数のテストなどを容易にするいくつかのクラスと関数が含まれています。

それらの説明は ヘルパークラス ページにあります。

アノテーションとテストのスキップ

テストの実行は、ファイルの先頭にある phpDoc コメント形式のアノテーションによって影響を受ける可能性があります。たとえば、次のようになります。

/**
 * @phpExtension pdo, pdo_pgsql
 * @phpVersion >= 7.2
 */

リストされているアノテーションは、テストが PHP バージョン 7.2 以降でのみ実行され、PHP 拡張機能 pdo と pdo_pgsql が存在する場合にのみ実行されることを示します。これらのアノテーションは コマンドラインテストランナー によって従われ、条件が満たされない場合、テストをスキップし、出力で文字 s – skipped でマークします。ただし、テストを手動で実行する場合、何の効果もありません。

アノテーションの説明は テストアノテーション ページにあります。

Environment::skip() を使用して、独自の条件に基づいてテストをスキップすることもできます。たとえば、これは Windows でのテストをスキップします。

if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}

ディレクトリ構造

少し大きなライブラリやプロジェクトでは、テスト対象クラスの名前空間に従って、テストを含むディレクトリをサブディレクトリに分割することをお勧めします。

└── tests/
	├── NamespaceOne/
	│   ├── MyClass.getUsers.phpt
	│   ├── MyClass.setUsers.phpt
	│   └── ...
	│
	├── NamespaceTwo/
	│   ├── MyClass.creating.phpt
	│   ├── MyClass.dropping.phpt
	│   └── ...
	│
	├── bootstrap.php
	└── ...

これにより、単一の名前空間、つまりサブディレクトリからテストを実行できます。

tester tests/NamespaceOne

特殊な状況

アサーションメソッドを 1 つも呼び出さないテストは疑わしく、エラーとして評価されます。

Error: This test forgets to execute an assertion.

アサーション呼び出しなしのテストが本当に有効と見なされる必要がある場合は、たとえば Assert::true(true) を呼び出します。

また、エラーメッセージでテストを終了するために exit()die() を使用するのは危険な場合があります。たとえば、exit('Error in connection') はリターンコード 0 でテストを終了します。これは成功を示します。Assert::fail('Error in connection') を使用してください。