bovigo / assert
为单元测试提供断言。
Requires
- php: ^8.2
- sebastian/comparator: ^5.0
- sebastian/exporter: ^5.0
Requires (Dev)
- mikey179/vfsstream: ^1.6.11
- phpunit/phpunit: ^10.5
- dev-main / 8.0.x-dev
- v8.0.1
- v8.0.0
- v7.0.1
- v7.0.0
- v6.2.0
- v6.1.0
- v6.0.0
- v5.1.1
- v5.1.0
- v5.0.1
- v5.0.0
- v4.0.0
- v3.2.0
- v3.1.0
- v3.0.0
- v2.2.0
- v2.1.0
- v2.0.0
- v1.7.1
- v1.7.0
- v1.6.1
- v1.6.0
- v1.5.0
- v1.4.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.0
- dev-dependabot/composer/phpunit/phpunit-10.5.24
- dev-dependabot/github_actions/actions/checkout-4.1.7
This package is auto-updated.
Last update: 2024-08-31 11:59:53 UTC
README
为单元测试提供断言。
包状态
安装
bovigo/assert 以 Composer 包的形式分发。要将它安装为您的包的开发依赖项,请使用以下命令
composer require --dev "bovigo/assert": "^8.0"
要将它安装为您的包的运行时依赖项,请使用以下命令
composer require "bovigo/assert=^7.0"
要求
bovigo/assert 8.x 至少需要 PHP 8.2。
为什么?
最初的想法是探索如何使用断言在单元测试中采用更函数式的方法,并看看这是否会使得测试代码的阅读变得更好。我个人认为结果足够有说服力,因此我想在自己的代码中使用它,所以我创建了一个包。
用法
所有断言都使用函数以相同的方式编写
assertThat(303, equals(303)); assertThat($someArray, isOfSize(3), 'array always must have size 3');
第一个参数是要测试的值,第二个是要用来测试值的谓词。此外,还可以提供一个可选的描述以增强断言失败时的清晰度。
如果谓词失败,则会抛出 AssertionFailure
,并提供有关测试失败原因的有用信息。如果使用 PHPUnit,则 AssertionFailure
是 \PHPUnit\Framework\AssertionFailedError
的实例,因此它可以很好地集成到 PHPUnit 中,产生与 PHPUnit 约束类似的测试输出。以下是测试失败时输出的示例
1) bovigo\assert\predicate\RegexTest::stringRepresentationContainsRegex
Failed asserting that 'matches regular expression "/^([a-z]{3})$/"' is equal to <string:matches regular expession "/^([a-z]{3})$/">.
--- Expected
+++ Actual
@@ @@
-'matches regular expession "/^([a-z]{3})$/"'
+'matches regular expression "/^([a-z]{3})$/"'
bovigo-assert/src/test/php/predicate/RegexTest.php:99
为了简洁起见,下面假设使用的函数通过以下方式导入到当前命名空间
use function bovigo\assert\assertThat; use function bovigo\assert\predicate\isOfSize; use function bovigo\assert\predicate\equals; // ... and so on
谓词列表
以下是默认包含在 bovigo/assert 中的谓词列表。
isNull()
测试值是否为 null
。
assertThat($value, isNull());
别名: bovigo\assert\assertNull($value, $description = null)
isNotNull()
测试值是否不为 null
。
assertThat($value, isNotNull());
别名: bovigo\assert\assertNotNull($value, $description = null)
isEmpty()
测试值是否为空。空定义为以下内容
- 如果值是
\Countable
的实例,则当它的计数为 0 时为空。 - 对于所有其他值,PHP 的
empty()
规则适用。
assertThat($value, isEmpty());
别名
bovigo\assert\assertEmpty($value, $description = null)
bovigo\assert\assertEmptyString($value, $description = null)
bovigo\assert\assertEmptyArray($value, $description = null)
isNotEmpty()
测试值是否不为空。有关空白的定义,请参阅 isEmpty()
。
assertThat($value, isNotEmpty());
别名: bovigo\assert\assertNotEmpty($value, $description = null)
isTrue()
测试值是否为真。值必须是布尔值真,不进行值转换。
assertThat($value, isTrue());
别名: bovigo\assert\assertTrue($value, $description = null)
isFalse()
测试值是否为假。值必须是布尔值假,不进行值转换。
assertThat($value, isFalse());
别名: bovigo\assert\assertFalse($value, $description = null)
equals($expected)
测试值是否等于预期值。如果需要测试浮点数的相等性,则可以使用可选参数 $delta
,它允许在一定范围内将两个浮点数视为相等。
assertThat($value, equals('Roland TB 303'));
如果需要 delta,例如对于浮点数,可以设置所需的 delta
assertThat($value, equals(5)->withDelta(0.1));
isNotEqualTo($unexpected)
测试一个值是否不等于意外值。当需要测试浮点值的相等性时,可以使用可选参数 $delta
,它允许在两个浮点值被认为是相等的一定范围内。
assertThat($value, isNotEqualTo('Roland TB 303'));
如果需要 delta,例如对于浮点数,可以设置所需的 delta
assertThat($value, isNotEqualTo(5)->withDelta(0.1));
isInstanceOf($expectedType)
测试一个值是否是期望类型的实例。
assertThat($value, isInstanceOf(\stdClass::class));
isNotInstanceOf($unexpectedType)
测试一个值不是意外类型的实例。
assertThat($value, isNotInstanceOf(\stdClass::class));
isSameAs($expected)
测试一个值与期望值完全相同。两个值都使用 ===
进行比较,适用相应的规则。
assertThat($value, isSameAs($anotherValue));
isNotSameAs($unexpected)
测试一个值与意外值不完全相同。两个值都使用 ===
进行比较,适用相应的规则。
assertThat($value, isNotSameAs($anotherValue));
isOfSize($expectedSize)
测试一个值具有期望的大小。大小规则如下:
- 对于字符串,使用其字节数长度。
- 对于数组以及
\Countable
的实例,使用count()
的值。 - 对于
\Traversable
的实例,使用iterator_count()
的值。为了避免移动可遍历对象的指针,对可遍历对象的副本应用iterator_count()
。 - 所有其他值类型都将被拒绝。
assertThat($value, isOfSize(3));
isNotOfSize($unexpectedSize)
测试一个值不具有意外的大小。规则与 isOfSize($expectedSize)
相同。
assertThat($value, isNotOfSize(3));
isOfType($expectedType)
测试一个值是期望的内部 PHP 类型。
assertThat($value, isOfType('resource'));
别名
从 5.0 版本开始,提供了一些别名函数以防止在该函数使用中出错。
bovigo\assert\predicate\isArray()
bovigo\assert\predicate\isBool()
bovigo\assert\predicate\isFloat()
bovigo\assert\predicate\isInt()
bovigo\assert\predicate\isNumeric()
bovigo\assert\predicate\isObject()
bovigo\assert\predicate\isResource()
bovigo\assert\predicate\isString()
bovigo\assert\predicate\isScalar()
bovigo\assert\predicate\isCallable()
bovigo\assert\predicate\isIterable()
isNotOfType($unexpectedType)
测试一个值不是意外的内部 PHP 类型。
assertThat($value, isNotOfType('resource'));
别名
从 5.0 版本开始,提供了一些别名函数以防止在该函数使用中出错。请注意,其中一些函数是特定的,以确保使用它们编写的代码构成语法上有效的句子。
bovigo\assert\predicate\isNotAnArray()
bovigo\assert\predicate\isNotBool()
bovigo\assert\predicate\isNotFloat()
bovigo\assert\predicate\isNotInt()
bovigo\assert\predicate\isNotNumeric()
bovigo\assert\predicate\isNotAnObject()
bovigo\assert\predicate\isNotAResource()
bovigo\assert\predicate\isNotAString()
bovigo\assert\predicate\isNotScalar()
bovigo\assert\predicate\isNotCallable()
bovigo\assert\predicate\isNotIterable()
isGreaterThan($expected)
测试一个值是否大于期望值。
assertThat($value, isGreaterThan(3));
isGreaterThanOrEqualTo($expected)
测试一个值是否大于或等于期望值。
assertThat($value, isGreaterThanOrEqualTo(3));
isLessThan($expected)
测试一个值是否小于期望值。
assertThat($value, isLessThan(3));
isLessThanOrEqualTo($expected)
测试一个值是否小于或等于期望值。
assertThat($value, isLessThanOrEqualTo(3));
contains($needle)
测试 $needle
是否包含在值中。以下规则适用:
null
包含在null
中。- 字符串可以包含在另一个字符串中。比较是区分大小写的。
$needle
可以是数组或\Traversable
的值。值和$needle
使用===
进行比较。- 对于所有其他情况,值将被拒绝。
assertThat($value, contains('Roland TB 303'));
有时需要区分数组、可遍历和字符串。如果需要强制特定类型,建议组合谓词。
assertThat($value, isArray()->and(contains('Roland TB 303'))); assertThat($value, isString()->and(contains('Roland TB 303'))); assertThat($value, isInstanceOf(\Iterator::class)->and(contains('Roland TB 303')));
doesNotContain($needle)
测试 $needle
不包含在值中。适用于 contains($needle)
的规则。
assertThat($value, doesNotContain('Roland TB 303'));
hasKey($key)
测试数组或 \ArrayAccess
的实例是否具有给定名称的键。键必须是 integer
或 string
类型。既不是数组也不是 \ArrayAccess
实例的值将被拒绝。
assertThat($value, hasKey('roland'));
doesNotHaveKey($key)
测试数组或 \ArrayAccess
实例是否没有给定名称的键。该键必须是 integer
或 string
类型。既不是数组也不是 \ArrayAccess
实例的值将被拒绝。
assertThat($value, doesNotHaveKey('roland'));
containsSubset($other)
自 6.2.0 版本起可用。
测试 $other
是否包含该值。
assertThat($value, containsSubset(['TB-303', 'TR-808']));
matches($pattern)
测试字符串是否与正则表达式的给定模式匹配。如果值不是字符串,它将被拒绝。如果模式在值中至少产生一个匹配,则测试成功。
assertThat($value, matches('/^([a-z]{3})$/'));
doesNotMatch($pattern)
测试字符串是否与正则表达式的给定模式不匹配。如果值不是字符串,它将被拒绝。如果模式在值中没有匹配,则测试成功。
assertThat($value, doesNotMatch('/^([a-z]{3})$/'));
matchesFormat($format)
自 3.2.0 版本起可用。
测试字符串是否与给定的 PHP 格式表达式匹配。如果值不是字符串,它将被拒绝。如果格式在值中至少产生一个匹配,则测试成功。格式字符串可以包含以下占位符
%e
:代表目录分隔符,例如 Linux 上的 /。%s
:一个或多个任何字符(字符或空白)除了换行符字符。%S
:零个或多个任何字符(字符或空白)除了换行符字符。%a
:一个或多个任何字符(字符或空白),包括换行符字符。%A
:零个或多个任何字符(字符或空白),包括换行符字符。%w
:零个或多个空白字符。%i
:有符号整数,例如 +3142, -3142。%d
:无符号整数,例如 123456。%x
:一个或多个十六进制字符。即 0-9, a-f, A-F 范围内的字符。%f
:浮点数,例如:3.142, -3.142, 3.142E-10, 3.142e+10。%c
:任何类型的单个字符。
assertThat($value, matchesFormat('%w'));
doesNotMatchFormat($format)
自 3.2.0 版本起可用。
测试字符串是否与给定的 PHP 格式表达式不匹配。如果值不是字符串,它将被拒绝。如果模式在值中没有匹配,则测试成功。参见上面的格式列表。
assertThat($value, doesNotMatchFormat('%w'));
isExistingFile($basePath = null)
测试值是否表示一个现有文件。如果没有提供 $basepath
,值必须是绝对路径或当前工作目录的相对路径。当提供 $basepath
时,值必须是此基本路径的相对路径。
assertThat($value, isExistingFile()); assertThat($value, isExistingFile('/path/to/files'));
isNonExistingFile($basePath = null)
测试值是否表示一个不存在的文件。如果没有提供 $basepath
,值必须是绝对路径或当前工作目录的相对路径。当提供 $basepath
时,值必须是此基本路径的相对路径。
assertThat($value, isNonExistingFile()); assertThat($value, isNonExistingFile('/path/to/files'));
isExistingDirectory($basePath = null)
测试值是否表示一个现有目录。如果没有提供 $basepath
,值必须是绝对路径或当前工作目录的相对路径。当提供 $basepath
时,值必须是此基本路径的相对路径。
assertThat($value, isExistingDirectory()); assertThat($value, isExistingDirectory('/path/to/directories'));
isNonExistingDirectory($basePath = null)
测试值是否表示一个不存在的目录。如果没有提供 $basepath
,值必须是绝对路径或当前工作目录的相对路径。当提供 $basepath
时,值必须是此基本路径的相对路径。
assertThat($value, isNonExistingDirectory()); assertThat($value, isNonExistingDirectory('/path/to/directories'));
startsWith($prefix)
自 1.1.0 版本起可用。
测试值必须是一个字符串,并且以给定的前缀开头。
assertThat($value, startsWith('foo'));
doesNotStartWith($prefix)
自 1.1.0 版本起可用。
测试值必须是一个字符串,并且不以给定的前缀开头。
assertThat($value, startsWith('foo'));
endsWith($suffix)
自 1.1.0 版本起可用。
测试值必须是一个字符串,并且以给定的后缀结尾。
assertThat($value, endsWith('foo'));
doesNotEndWith($suffix)
自 1.1.0 版本起可用。
测试必须为字符串的值不以给定后缀结尾。
assertThat($value, doesNotEndWith('foo'));
each($predicate)
自 1.1.0 版本起可用。
对数组或可遍历的每个值应用一个谓词。
assertThat($value, each(isInstanceOf($expectedType));
请注意,空数组或可遍历的结果将导致测试成功。如果它不能为空,请使用 isNotEmpty()->and(each($predicate))
assertThat($value, isNotEmpty()->and(each(isInstanceOf($expectedType))));
它也可以与任何可调用函数一起使用。
assertThat($value, each('is_nan')); assertThat($value, each(function($value) { return substr($value, 4, 3) === 'foo'; }));
eachKey($predicate)
自1.3.0版本起可用。
对数组或可遍历的每个键应用一个谓词。
assertThat($value, eachKey(isOfType('int'));
请注意,空数组或可遍历的结果将导致测试成功。如果它不能为空,请使用 isNotEmpty()->and(eachKey($predicate))
assertThat($value, isNotEmpty()->and(eachKey(isOfType('int'))));
它也可以与任何可调用函数一起使用。
assertThat($value, eachKey('is_int')); assertThat($value, eachKey(function($value) { return substr($value, 4, 3) === 'foo'; }));
not($predicate)
反转谓词的意义。
assertThat($value, not(isTrue()));
它也可以与任何可调用函数一起使用。
assertThat($value, not('is_nan')); assertThat($value, not(function($value) { return substr($value, 4, 3) === 'foo'; }));
组合谓词
每个谓词提供两种方法将此谓词与另一个谓词组合成新的谓词。
and($predicate)
创建一个谓词,其中组合谓词必须都是 true
才能使组合谓词为 true
。如果其中一个谓词失败,组合谓词也将失败。
assertThat($value, isNotEmpty()->and(eachKey(isOfType('int'))));
它也可以与任何可调用函数一起使用。
assertThat($value, isNotEmpty()->and('is_string'));
or($predicate)
创建一个谓词,其中一个组合谓词必须为 true
。只有当所有谓词都失败时,组合谓词才会失败。
assertThat($value, equals(5)->or(isLessThan(5)));
它也可以与任何可调用函数一起使用。
assertThat($value, isNull()->or('is_finite'));
用户定义的谓词
要在断言中使用谓词,有两种可能性
使用可调用函数
您可以将任何 callable
传递给 assertThat()
函数
assertThat($value, 'is_nan');
这将创建一个使用 PHP 内置 is_nan()
函数测试值的谓词。
可调用函数应接受单个值(显然是要测试的值)并必须在成功时返回 true
,在失败时返回 false
。也可以抛出任何异常。
以下是一个使用闭包的示例
assertThat( $value, function($value) { if (!is_string($value)) { throw new \InvalidArgumentException( 'Given value is not a string.' ); } return substr($value, 4, 3) === 'foo'; } );
扩展 bovigo\assert\predicate\Predicate
另一种可能性是扩展 bovigo\assert\predicate\Predicate
类。您需要实现以下方法之一
public function test($value)
此方法接收要测试的值并应在成功时返回 true
,在失败时返回 false
。也可以抛出任何异常。
public function __toString()
此方法必须返回谓词的正确描述,该描述适合在断言失败时显示的句子。这些句子如下组成
断言失败:[值的描述] [谓词的描述]。
此外,谓词可以通过覆盖 describeValue(Exporter $exporter, $value)
方法来影响 [值的描述]。
立即失败
自1.2.0版本起可用。
如果在某些点上测试需要失败,而断言不足以做到这一点,可以使用 bovigo\assert\fail($description)
触发立即断言失败。
try { somethingThatThrowsFooException(); fail('Expected ' . FooException::class . ', gone none'); } catch (FooException $fo) { // some assertions on FooException }
phpstan 和早期终止函数调用
自5.1.0版本起可用
如果您使用 phpstan,bovigo/assert 提供了一个配置文件,您可以将其包含在您的 phpstan 配置中,以便识别带有 fail()
的早期终止函数调用。
includes:
- vendor/bovigo/assert/src/main/resources/phpstan/bovigo-assert.neon
期望
自1.6.0版本起可用
期望可以用来检查特定的代码片段是否抛出异常或触发错误。它还可以用来检查在特定的代码片段运行后,断言仍然为真,尽管相关的代码成功或失败。
对异常的期望
注意:自2.1.0版本起,也可以使用期望与 \Error
一起使用。
检查一个代码片段,例如函数或方法,是否抛出异常
expect(function() { // some piece of code which is expected to throw SomeException })->throws(SomeException::class);
您还可以期望抛出任何异常,而不仅仅是特定的异常,通过省略异常的类名
expect(function() { // some piece of code which is expected to throw any exception })->throws();
自2.1.0版本起,可以验证是否正好抛出了给定的异常
$exception = new \Exception('failure'); expect(function() use ($exception) { throw $exception; })->throws($exception);
这将执行一个使用 isSameAs($exception)
的断言来验证抛出的异常。
此外,还可以对抛出的异常进行额外检查
expect(function() { // some piece of code which is expected to throw SomeException }) ->throws(SomeException::class) ->withMessage('some failure occured');
以下是对异常的检查方法
withMessage(string $expectedMessage)
对异常消息执行equals()
断言。message($predicate)
使用给定的谓词对异常消息执行断言。withCode(int $expectedCode)
对异常代码执行equals()
断言。with($predicate)
使用给定的谓词对整个异常执行断言。谓词将接收异常作为参数,并可以执行任何检查。
expect(function() { // some piece of code which is expected to throw SomeException }) ->throws(SomeException::class) ->with( function(SomeException $e) { return null !== $e->getPrevious(); }, 'exception does have a previous exception' );
当然,你也可以检查是否未发生特定异常
expect(function() { // some piece of code which is expected to not throw SomeException })->doesNotThrow(SomeException::class);
通过省略异常名称,确保代码不会抛出任何异常
expect(function() { // some piece of code which is expected to not throw any exception })->doesNotThrow();
如果这些期望中的任何一个失败,将抛出 AssertionFailure
。
对错误的期望
自版本 2.1.0 起可用
检查代码片段(例如函数或方法)是否触发错误
expect(function() { // some piece of code which is expected to trigger an error })->triggers(E_USER_ERROR);
也可以通过省略错误级别来期望任何错误,而不仅仅是特定错误
expect(function() { // some piece of code which is expected to trigger an error })->triggers();
此外,还可以对触发的错误执行额外检查
expect(function() { // some piece of code which is expected to trigger an error }) ->triggers(E_USER_WARNING) ->withMessage('some error occured');
以下是对异常的检查方法
withMessage(string $expectedMessage)
对错误消息执行equals()
断言。message($predicate)
使用给定的谓词对错误消息执行断言。
如果这些期望中的任何一个失败,将抛出 AssertionFailure
。
对代码执行后的状态的期望
有时,在执行某些代码片段后断言特定状态存在可能非常有用,无论执行是否成功。
expect(function() { // some piece of code here }) ->after(SomeClass::$value, equals(303));
可以将此与是否抛出异常的期望相结合
expect(function() { // some piece of code here }) ->doesNotThrow() ->after(SomeClass::$value, equals(303)); expect(function() { // some piece of code here }) ->throws(SomeException::class) ->after(SomeClass::$value, equals(303));
验证函数或方法的输出
自版本 2.1.0 起可用
当函数或方法使用 echo
时,检查它是否打印正确的输出可能很繁琐。为此,引入了 outputOf()
函数
outputOf( function() { echo 'Hello you!'; }, equals('Hello world!') );
第一个参数是一个可调用的函数,它打印一些输出,第二个参数是任何将应用于输出的谓词。outputOf()
负责启用和禁用输出缓冲区以捕获输出。
常见问题解答(FAQ)
我如何访问类或对象的属性进行断言?
与 PHPUnit 不同,bovigo/assert 不提供断言类属性是否满足特定约束的机制。如果属性是公开的,您可以将其直接作为值传递给 assertThat()
函数。在所有其他情况下,bovigo/assert 不支持访问受保护的或私有属性。它们之所以受保护或私有,有原因,测试应该仅针对类的公共 API,而不是它们的内部工作。