アサーション
******
.[perex]
アサーションは、実際の値が期待した値と一致することを確かめるのに使います。これらは `Tester\Assert` クラスのメソッドです。
いちばんふさわしいアサーションを選んでください。`Assert::same($a, $b)` は `Assert::true($a === $b)` より優れています。失敗したときに意味のあるエラーのメッセージを見せるからです。後者では `false should be true` としか得られず、変数 `$a` と `$b` の中身については何も分かりません。
ほとんどのアサーションには `$description` パラメータで省略できる説明を付けられます。期待が外れたとき、それがエラーのメッセージに表示されます。
例では次の別名が作られているものとします。
```php
use Tester\Assert;
```
Assert::same($expected, $actual, ?string $description=null) .[method]
---------------------------------------------------------------------
`$expected` は `$actual` と同一でなければなりません。PHP の `===` 演算子と同じです。
Assert::notSame($expected, $actual, ?string $description=null) .[method]
------------------------------------------------------------------------
`Assert::same()` の逆です。つまり PHP の `!==` 演算子と同じです。
Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method]
-------------------------------------------------------------------------------------------------------------------------
`$expected` は `$actual` と等しくなければなりません。`Assert::same()` と違って、オブジェクトの同一性、配列の キー => 値 の組の順序、わずかに違う小数は無視されます。これは `$matchIdentity` と `$matchOrder` を設定して変えられます。
次の場合は `equal()` から見れば等しいのですが、`same()` から見れば等しくありません。
```php
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) .[method]
-------------------------------------------------------------------------
`Assert::equal()` の逆です。
Assert::contains($needle, string|array $actual, ?string $description=null) .[method]
------------------------------------------------------------------------------------
`$actual` が文字列なら、部分文字列 `$needle` を含まなければなりません。配列なら、要素 `$needle` を含まなければなりません(厳密に比べられます)。
Assert::notContains($needle, string|array $actual, ?string $description=null) .[method]
---------------------------------------------------------------------------------------
`Assert::contains()` の逆です。
Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4.0}
----------------------------------------------------------------------------------------------------------
`$actual` は配列でなければならず、キー `$needle` を含まなければなりません。
Assert::hasNotKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4.0}
-------------------------------------------------------------------------------------------------------------
`$actual` は配列でなければならず、キー `$needle` を含んでいてはいけません。
Assert::true($value, ?string $description=null) .[method]
---------------------------------------------------------
`$value` は `true` でなければなりません。つまり `$value === true` です。
Assert::truthy($value, ?string $description=null) .[method]
-----------------------------------------------------------
`$value` は真と見なせる値でなければなりません。つまり `if ($value) ...` の条件を満たします。
Assert::false($value, ?string $description=null) .[method]
----------------------------------------------------------
`$value` は `false` でなければなりません。つまり `$value === false` です。
Assert::falsey($value, ?string $description=null) .[method]
-----------------------------------------------------------
`$value` は偽と見なせる値でなければなりません。つまり `if (!$value) ...` の条件を満たします。
Assert::null($value, ?string $description=null) .[method]
---------------------------------------------------------
`$value` は `null` でなければなりません。つまり `$value === null` です。
Assert::notNull($value, ?string $description=null) .[method]
------------------------------------------------------------
`$value` は `null` であってはいけません。つまり `$value !== null` です。
Assert::nan($value, ?string $description=null) .[method]
--------------------------------------------------------
`$value` は Not a Number でなければなりません。NAN の値のテストにはかならず `Assert::nan()` を使ってください。NAN の値はとても特殊で、`Assert::same()` や `Assert::equal()` のようなアサーションは思いがけない振る舞いをすることがあります。
Assert::count($count, Countable|array $value, ?string $description=null) .[method]
----------------------------------------------------------------------------------
`$value` の要素の数は `$count` でなければなりません。`count($value) === $count` と同じです。
Assert::type(string|object $type, $value, ?string $description=null) .[method]
------------------------------------------------------------------------------
`$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) .[method]
-------------------------------------------------------------------------------------------------
`$callable` を呼ぶと、クラス `$class` の例外が投げられなければなりません。`$message` を指定すると、例外のメッセージも[その形に合わなければなりません |#Assert::match()]。そして `$code` を指定すると、コードも厳密に一致しなければなりません。
たとえば次のテストは、例外のメッセージが合わないので落ちます。
```php
Assert::exception(
fn() => throw new App\InvalidValueException('Zero value'),
App\InvalidValueException::class,
'Value is too low',
);
```
`Assert::exception()` は投げられた例外を返すので、入れ子の例外もテストできます。
```php
$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) .[method]
----------------------------------------------------------------------------------------
関数 `$callable` が期待したエラー(つまり warning、notice など)を出したかを確かめます。`$type` には `E_...` の定数のどれか、たとえば `E_WARNING` を指定します。そして `$message` を指定すると、エラーのメッセージも[その形に合わなければなりません |#Assert::match()]。たとえば次のようにです。
```php
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
```
コールバックがもっと多くのエラーを出すなら、そのすべてをきっちり順序どおりに期待しなければなりません。その場合は `$type` に配列を渡します。
```php
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
```
.[note]
`$type` にクラス名を指定すると、`Assert::exception()` と同じように振る舞います。
Assert::noError(callable $callable) .[method]
---------------------------------------------
関数 `$callable` が warning もエラーも例外も出さなかったことを確かめます。ほかにアサーションのないコードの断片をテストするのに役立ちます。
Assert::match(string $pattern, $actual, ?string $description=null) .[method]
----------------------------------------------------------------------------
`$actual` は形 `$pattern` に合わなければなりません。形には 2 とおりの書き方が使えます。正規表現とワイルドカードです。
`$pattern` として正規表現を渡すなら、区切りには `~` か `#` を使わなければなりません。ほかの区切りには対応していません。たとえば `$var` が 16 進数の数字だけを含まなければならないテストです。
```php
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 つ
例です。
```php
# ここでも 16 進数のテスト
Assert::match('%h%', $var);
# ファイルのパスと行番号を一般化します
Assert::match('Error in file %a% on line %i%', $errorMessage);
```
Assert::notMatch(string $pattern, $actual, ?string $description=null) .[method]{data-version:2.5.6}
---------------------------------------------------------------------------------------------------
`Assert::match()` の逆です。
Assert::matchFile(string $file, $actual, ?string $description=null) .[method]
-----------------------------------------------------------------------------
このアサーションは [#Assert::match()]と同じですが、形はファイル `$file` から読み込まれます。とても長い文字列をテストするのに役立ちます。テストのファイルは見通しよく保たれます。
Assert::fail(string $message, $actual=null, $expected=null) .[method]
---------------------------------------------------------------------
このアサーションはいつも失敗します。それが役に立つこともあります。必要なら期待した値と実際の値も指定できます。
期待
---
定数でない要素を含む、もっと込み入った構造を比べたいとき、上のアサーションでは足りないことがあります。たとえば新しいユーザーを作ってその属性を配列として返すメソッドをテストしているとします。パスワードのハッシュの値は分かりませんが、16 進数の文字列でなければならないことは分かっています。そして次の要素については、`DateTime` オブジェクトでなければならないことだけが分かっています。
こうした場面では、`Assert::equal()` と `Assert::notEqual()` メソッドの `$expected` パラメータの中で `Tester\Expect` を使えます。これで構造を簡単に書き表せます。
```php
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()` などのメソッドが使えます。さらにそれらをつなげられます。
```php
Expect::type(MyIterator::class)->andCount(5); # MyIterator で、要素の数が 5 であることを期待します
```
あるいは自分でアサーションのハンドラを書けます。
```php
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` で変えられます。
アサーション
アサーションは、実際の値が期待した値と一致することを確かめるのに使います。これらは
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)
$value は true でなければなりません。つまり
$value === true です。
Assert::truthy ($value, ?string
$description=null)
$value は真と見なせる値でなければなりません。つまり if ($value) ...
の条件を満たします。
Assert::false ($value, ?string
$description=null)
$value は false でなければなりません。つまり
$value === false です。
Assert::falsey ($value, ?string
$description=null)
$value は偽と見なせる値でなければなりません。つまり if (!$value) ...
の条件を満たします。
Assert::null ($value, ?string
$description=null)
$value は null でなければなりません。つまり
$value === null です。
Assert::notNull ($value, ?string
$description=null)
$value は null であってはいけません。つまり
$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 で変えられます。