アサーション

アサーションは、実際の値が期待した値と一致することを確かめるのに使います。これらは Tester\Assert クラスのメソッドです。

いちばんふさわしいアサーションを選んでください。Assert::same($a, $b)Assert::true($a === $b) より優れています。失敗したときに意味のあるエラーのメッセージを見せるからです。後者では false should be true としか得られず、変数 $a$b の中身については何も分かりません。

ほとんどのアサーションには $description パラメータで省略できる説明を付けられます。期待が外れたとき、それがエラーのメッセージに表示されます。

例では次の別名が作られているものとします。

use Tester\Assert;

Assert::same ($expected, $actual, ?string $description=null)

$expected$actual と同一でなければなりません。PHP の === 演算子と同じです。

Assert::notSame ($expected, $actual, ?string $description=null)

Assert::same() の逆です。つまり PHP の !== 演算子と同じです。

Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

$expected$actual と等しくなければなりません。Assert::same() と違って、オブジェクトの同一性、配列の キー ⇒ 値 の組の順序、わずかに違う小数は無視されます。これは $matchIdentity$matchOrder を設定して変えられます。

次の場合は equal() から見れば等しいのですが、same() から見れば等しくありません。

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

ただし気をつけてください。配列 [1, 2][2, 1] は同じではありません。違うのは値の順序だけで、キー ⇒ 値 の組ではないからです。配列 [1, 2][0 => 1, 1 => 2] とも書けるので、[1 => 2, 0 => 1] は同じと見なされます。

$expected にはいわゆる期待も使えます。

Assert::notEqual ($expected, $actual, ?string $description=null)

Assert::equal() の逆です。

Assert::contains ($needle, string|array $actual, ?string $description=null)

$actual が文字列なら、部分文字列 $needle を含まなければなりません。配列なら、要素 $needle を含まなければなりません(厳密に比べられます)。

Assert::notContains ($needle, string|array $actual, ?string $description=null)

Assert::contains() の逆です。

Assert::hasKey (string|int $needle, array $actual, ?string $description=null)

$actual は配列でなければならず、キー $needle を含まなければなりません。

Assert::hasNotKey (string|int $needle, array $actual, ?string $description=null)

$actual は配列でなければならず、キー $needle を含んでいてはいけません。

Assert::true ($value, ?string $description=null)

$valuetrue でなければなりません。つまり $value === true です。

Assert::truthy ($value, ?string $description=null)

$value は真と見なせる値でなければなりません。つまり if ($value) ... の条件を満たします。

Assert::false ($value, ?string $description=null)

$valuefalse でなければなりません。つまり $value === false です。

Assert::falsey ($value, ?string $description=null)

$value は偽と見なせる値でなければなりません。つまり if (!$value) ... の条件を満たします。

Assert::null ($value, ?string $description=null)

$valuenull でなければなりません。つまり $value === null です。

Assert::notNull ($value, ?string $description=null)

$valuenull であってはいけません。つまり $value !== null です。

Assert::nan ($value, ?string $description=null)

$value は Not a Number でなければなりません。NAN の値のテストにはかならず Assert::nan() を使ってください。NAN の値はとても特殊で、Assert::same()Assert::equal() のようなアサーションは思いがけない振る舞いをすることがあります。

Assert::count ($count, Countable|array $value, ?string $description=null)

$value の要素の数は $count でなければなりません。count($value) === $count と同じです。

Assert::type (string|object $type, $value, ?string $description=null)

$value は指定した型でなければなりません。$type には文字列を使えます。

  • array
  • list – ゼロから昇順に並ぶ数のキーで添字が付けられた配列
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • クラス名、あるいはオブジェクトそのもの。その場合 $value instanceof $type でなければなりません

Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)

$callable を呼ぶと、クラス $class の例外が投げられなければなりません。$message を指定すると、例外のメッセージもその形に合わなければなりません。そして $code を指定すると、コードも厳密に一致しなければなりません。

たとえば次のテストは、例外のメッセージが合わないので落ちます。

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is too low',
);

Assert::exception() は投げられた例外を返すので、入れ子の例外もテストできます。

$e = Assert::exception(
	fn() => throw new MyException('Something is wrong', 0, new RuntimeException),
	MyException::class,
	'Something is wrong',
);

Assert::type(RuntimeException::class, $e->getPrevious());

Assert::error (string $callable, int|string|array $type, ?string $message=null)

関数 $callable が期待したエラー(つまり warning、notice など)を出したかを確かめます。$type には E_... の定数のどれか、たとえば E_WARNING を指定します。そして $message を指定すると、エラーのメッセージもその形に合わなければなりません。たとえば次のようにです。

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

コールバックがもっと多くのエラーを出すなら、そのすべてをきっちり順序どおりに期待しなければなりません。その場合は $type に配列を渡します。

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

$type にクラス名を指定すると、Assert::exception() と同じように振る舞います。

Assert::noError (callable $callable)

関数 $callable が warning もエラーも例外も出さなかったことを確かめます。ほかにアサーションのないコードの断片をテストするのに役立ちます。

Assert::match (string $pattern, $actual, ?string $description=null)

$actual は形 $pattern に合わなければなりません。形には 2 とおりの書き方が使えます。正規表現とワイルドカードです。

$pattern として正規表現を渡すなら、区切りには ~# を使わなければなりません。ほかの区切りには対応していません。たとえば $var が 16 進数の数字だけを含まなければならないテストです。

Assert::match('#^[0-9a-f]+$#i', $var);

もうひとつの書き方はふつうの文字列の比較に似ていますが、$pattern の中でいろいろなワイルドカードを使えます。

  • %a% 改行の文字を除く任意の文字が 1 つ以上
  • %a?% 改行の文字を除く任意の文字が 0 個以上
  • %A% 改行の文字を含む任意の文字が 1 つ以上
  • %A?% 改行の文字を含む任意の文字が 0 個以上
  • %s% 改行の文字を除く空白の文字が 1 つ以上
  • %s?% 改行の文字を除く空白の文字が 0 個以上
  • %S% 空白の文字を除く文字が 1 つ以上
  • %S?% 空白の文字を除く文字が 0 個以上
  • %c% 任意の種類の文字 1 つ(改行を除く)
  • %d% 数字が 1 つ以上
  • %d?% 数字が 0 個以上
  • %i% 符号付きの整数の値
  • %f% 浮動小数点数
  • %h% 16 進数の数字が 1 つ以上
  • %w% 英数字が 1 つ以上
  • %ds% ディレクトリの区切り(/ または \
  • %% % の文字 1 つ

例です。

# ここでも 16 進数のテスト
Assert::match('%h%', $var);

# ファイルのパスと行番号を一般化します
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::notMatch (string $pattern, $actual, ?string $description=null)

Assert::match() の逆です。

Assert::matchFile (string $file, $actual, ?string $description=null)

このアサーションは Assert::match()と同じですが、形はファイル $file から読み込まれます。とても長い文字列をテストするのに役立ちます。テストのファイルは見通しよく保たれます。

Assert::fail (string $message, $actual=null, $expected=null)

このアサーションはいつも失敗します。それが役に立つこともあります。必要なら期待した値と実際の値も指定できます。

期待

定数でない要素を含む、もっと込み入った構造を比べたいとき、上のアサーションでは足りないことがあります。たとえば新しいユーザーを作ってその属性を配列として返すメソッドをテストしているとします。パスワードのハッシュの値は分かりませんが、16 進数の文字列でなければならないことは分かっています。そして次の要素については、DateTime オブジェクトでなければならないことだけが分かっています。

こうした場面では、Assert::equal()Assert::notEqual() メソッドの $expected パラメータの中で Tester\Expect を使えます。これで構造を簡単に書き表せます。

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # 整数を期待します
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # その形に合う文字列を期待します
	'created_at' => Expect::type(DateTime::class), # そのクラスのインスタンスを期待します
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Expect では Assert とほぼ同じアサーションを行えます。ですから Expect::same()Expect::match()Expect::count() などのメソッドが使えます。さらにそれらをつなげられます。

Expect::type(MyIterator::class)->andCount(5);  # MyIterator で、要素の数が 5 であることを期待します

あるいは自分でアサーションのハンドラを書けます。

Expect::that(function ($value) {
	# 期待が外れたら false を返します
});

落ちたアサーションを調べる

アサーションが落ちると、Tester は何が間違っていたかを出力します。込み入った構造を比べている場合、Tester は比べた値のダンプを作って output ディレクトリに保存します。たとえば架空のテスト Arrays.recursive.phpt が落ちると、ダンプは次のように保存されます。

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # 実際の値
	│   └── Arrays.recursive.expected  # 期待した値
	│
	└── Arrays.recursive.phpt          # 落ちたテスト

ディレクトリの名前は Tester\Dumper::$dumpDir で変えられます。