在PHPUnit中,断言(Assertions)是其提供的一系列对程序执行结果测试的方法。
通俗的讲,就是断言执行程序结果为我们期待的值,如果不是则测试失败,下面是断言方法的详细介绍,内容全部来翻译自PHPUnit的官方文档,部分方法官方介绍的很模糊。
以下根据官方的源码注释增加了说明和注释,分享给大家作个参考。
assertArrayHasKey(mixed $key, array $array[, string $message = ''])
断言数组$array含有索引$key, $message用于自定义输出的错误信息,后同
assertClassHasAttribute(string $attributeName, string $className[, string $message = ''])
断言类$className含有属性$attributeName
assertClassHasStaticAttribute(string $attributeName, string $className[, string $message = ''])
断言类$className含有静态属性$attributeName
assertContains(mixed $needle, Iterator|array $haystack[, string $message = ''])
断言迭代器对象$haystack/数组$haystack含有$needle
assertNotContains()
与上条相反
assertAttributeContains(mixed $needle, Class|Object $haystack[, string $message = ''])
断言$needle为一个类/对象$haystack可访问到的属性(public, protected 和 private)
assertAttributeNotContains()
与上条相反
assertContains(string $needle, string $haystack[, string $message = ''])
断言字符串$needle在字符串$haystack中
assertContainsOnly(string $type, Iterator|array $haystack[, boolean $isNativeType = NULL, string $message = ''])
断言迭代器对象/数组$haystack中只有$type类型的值, $isNativeType 设定为PHP原生类型,$message同上
assertNotContainsOnly()
与上条相反
assertAttributeContainsOnly() 和 assertAttributeNotContainsOnly()
断言对象的属性只有$type类型和非含有$type类型
assertEmpty(mixed $actual[, string $message = ''])
断言$actual为空
assertNotEmpty()
遇上条相反
assertAttributeEmpty() 和 assertAttributeNotEmpty()
断言对象的所有属性为空或不为空
assertEqualXMLStructure(DOMNode $expectedNode, DOMNode $actualNode[, boolean $checkAttributes = FALSE, string $message = ''])
断言Dom节点$actualNode和DOM节点$expectedNode相同,$checkAttributes FALSE 不断言节点属性,TRUE则断言属性$message同上
assertEquals(mixed $expected, mixed $actual[, string $message = ''])
断言复合类型$actual与$expected相同
assertNotEquals()
与上条相反
assertAttributeEquals() and assertAttributeNotEquals()
断言类属性$actual与$expected相同
assertEquals(array $expected, array $actual[, string $message = ''])
断言数组$actual和数组$expected相同
assertFalse(bool $condition[, string $message = ''])
断言$condition的结果为false
assertFileEquals(string $expected, string $actual[, string $message = ''])
断言文件$actual和$expected相同
assertFileExists(string $filename[, string $message = ''])
断言文件$filename存在
assertFileNotExists()
与上条相反
assertGreaterThan(mixed $expected, mixed $actual[, string $message = ''])
断言$actual比$expected大
assertAttributeGreaterThan()
断言类的属性用
assertGreaterThanOrEqual(mixed $expected, mixed $actual[, string $message = ''])
断言$actual大于等于$expected
assertAttributeGreaterThanOrEqual()
断言类的属性
assertInstanceOf($expected, $actual[, $message = ''])
断言$actual为$expected的实例
assertNotInstanceOf()
与上相反
assertAttributeInstanceOf() and assertAttributeNotInstanceOf()
断言类属性用
assertInternalType($expected, $actual[, $message = ''])
断言$actual的类型为$expected
assertNotInternalType()
与上相反
assertAttributeInternalType() and assertAttributeNotInternalType()
断言类属性用
assertLessThan(mixed $expected, mixed $actual[, string $message = ''])
断言$actual小于$expected
assertAttributeLessThan()
断言类属性小于$expected
assertLessThanOrEqual(mixed $expected, mixed $actual[, string $message = ''])
断言$actual小于等于$expected
assertAttributeLessThanOrEqual()
断言类属性小于等于$expected
assertNull(mixed $variable[, string $message = ''])
断言$variable的值为null
assertNotNull()
与上条相反
assertObjectHasAttribute(string $attributeName, object $object[, string $message = ''])
断言$object含有属性$attributeName
assertObjectNotHasAttribute()
与上条相反
assertRegExp(string $pattern, string $string[, string $message = ''])
断言字符串$string符合正则表达式$pattern
assertNotRegExp()
与上条相反
assertStringMatchesFormat(string $format, string $string[, string $message = ''])
断言$string符合$format定义的格式,例如 %i %s等等
assertStringNotMatchesFormat()
与上条相反
assertStringMatchesFormatFile(string $formatFile, string $string[, string $message = ''])
断言$string路径的文件的格式和$formatFile文件的格式相同
assertStringNotMatchesFormatFile()
与上条相反
assertSame(mixed $expected, mixed $actual[, string $message = ''])
断言$actual和$expected的类型和值相同
assertNotSame()
与上条相反
assertAttributeSame() and assertAttributeNotSame()
断言类属性用
assertSame(object $expected, object $actual[, string $message = ''])
断言对象$actual和对象$expected相同
assertSelectCount(array $selector, integer $count, mixed $actual[, string $message = '', boolean $isHtml = TRUE])
断言在$actual文档中(格式为html或xml)css选择器$selector有$count个,或有符合$selector的元素(设定$count为true),或没有符合$selector的元素(设定$count为false)
assertSelectCount("#binder", true, $xml); // 有一个就行
assertSelectCount(".binder", 3, $xml); // 必须有3个?
assertSelectEquals(array $selector, string $content, integer $count, mixed $actual[, string $message = '', boolean $isHtml = TRUE])
断言在文档$actual中有符合根据$selector的找到符合$content的$count个元素,当$count等于true和false的时候作用如下:
assertSelectEquals("#binder .name", "Chuck", true, $xml); // 所有的name等于Chuck
assertSelectEquals("#binder .name", "Chuck", false, $xml); // 所有的name不等于Chuck
assertSelectRegExp(array $selector, string $pattern, integer $count, mixed $actual[, string $message = '', boolean $isHtml = TRUE])
assertStringEndsWith(string $suffix, string $string[, string $message = ''])
断言$string的末尾为$suffix结束
assertStringEndsNotWith()
与上条相反
assertStringEqualsFile(string $expectedFile, string $actualString[, string $message = ''])
断言$actualString在文件$expectedFile的内容中
assertStringNotEqualsFile()
与上条相反
assertStringStartsWith(string $prefix, string $string[, string $message = ''])
断言$string的开头为$suffix
assertStringStartsNotWith()
与上条相反
assertTag(array $matcher, string $actual[, string $message = '', boolean $isHtml = TRUE])
断言$actual的内容符合$matcher的定义,matcher的定义如下:
# id: 节点必须带有id属性且名称与id设定的相同
# tags: 节点的名称必须与tags的值匹配
# attributes: 节点的属性必须与$attributes数组中的值相匹配
# content: 文本内容必须与$content的值相同.
# parent: 节点的父节点必须符合$parent数组中定义的内容.
# child: 节点的字节点中有至少一个直系子节点满足 $child 数组中定义的内容.
At least one of the node's immediate children must meet the criteria described by the $child associative array.
# ancestor: 节点的父节点中有至少一个节点满足 $ancestor 数组中定义的内容.
At least one of the node's ancestors must meet the criteria described by the $ancestor associative array.
# descendant: 节点的字节点中有至少一个子节点满足 $descendant 数组中定义的内容.
At least one of the node's descendants must meet the criteria described by the $descendant associative array.
# children: 用于计算字节点的联合数组
Associative array for counting children of a node.
* count: 符合匹配标准的字节点数目需要和count的值相同
The number of matching children must be equal to this number.
* less_than: 符合匹配标准的字节点数目需要比count的值少
The number of matching children must be less than this number.
* greater_than: 符合匹配标准的字节点数目需要比count的值多
The number of matching children must be greater than this number.
* only: 另外一个联合数组用于定义配合标准的节点,只有这些节点才会被计算入内
Another associative array consisting of the keys to use to match on the children, and only matching children will be counted
assertTag的代码例子(图片点击放大):
More complex assertions can be formulated using the PHPUnit_Framework_Constraint classes
更加复杂的断言可以通过PHPUnit_Framework_Constraint类来制定
测试方法间的依赖关系
PHPUnit可以实现测试方法的依赖关系,也就是说,一个测试方法的参数的内容和是否会运行依赖于另外一个测试方法结果,依赖关系通过注释@depends来定义.这个特性一般用于检查代码的逻辑过程,一个逻辑的执行前提是另外一个逻辑的执行结果。
例子:
在上面的测试类StackTest中,定义了2个依赖测试方法,testPush依赖于testEmpty, testPop依赖于testPush。
那么在测试运行时,testEmpty中的断言执行完毕也没有出现问题时,方法中的return语句会将 $stack传给依赖于它的testPush,作为testPush的参数传入到testPush中,testPush执行完毕之后,也会将$stack传给依赖于它的testPop,只要断言检查没有出现异常,那么PHPUnit就会根据依赖关系依次执行依赖的测试方法,直到依赖关系结束为止。
另外,为了方便快速的确定问题的所在,如果某个测试方法依赖的方法测试没有功过,那么PHPUnit会自动跳过后面所有的依赖测试。
例子:
testOne中断言为True,但是传入的是false,testOne的测试不会通过,那么依赖于testOne的testTwo也会被自动跳过.
数据提供者(Data Providers)
在前面的例子我们可以看到:测试方法是可以有参数的,在依赖关系中参数的值是它依赖的测试方法传入.那么某个测试方法没有依赖的方法,我们怎么给它传入参数做测试呢?PHPUnit给我们提供了数据提供者方法来为测试方法传入数据.
数据提供者方法需要定义在当前的测试类中,在测试方法的注释中使用@dataProvider标签标注给它提供数据的方法名,定义之后PHPUnit会自动的将数据提供者方法返回的数据依次传入到测试方法中测试.
数据提供者相当于都取大量数据测试时的封装
代码:
在上面的代码中,方法add_provider就是测试方法testAdd的数据提供者方法,它会依次给testAdd传入4组测试数据,testAdd会测试4次.
注意:数据提供者方法返回数据的格式: 需要返回的是2维数组,第二维数组值的位置,对应测试方法参数的位置,参数个数和数组长度要相等,否则PHPUnit会报错,下面是个用于理解的简单的例子:
array(参数1,参数2,参数3,参数4,参数N),
array(参数1,参数2,参数3,参数4,参数N),
);
除了数组外,PHPUnit还支持数据提供者方法返回迭代器对象,迭代器的介绍估计大部分PHPer比较陌生,详细的介绍可以去http://php.net/manual/en/class.iterator.php 查看。
来看代码(点击图片放大察看):
和前一个例子实现的测试内容相同,但是数据提供者返回的是一个迭代器对象而不是是数组
数据提供者方法和依赖关系的限制
当一个测试方法依赖于另外一个使用data providers测试方法时,这个测试方法将会在它依赖的方法至少测试成功一次后运行,同时使用data providers的测试方法的执行的结果不能传入一个依赖它的测试方法中.这个解释来自官方的文档,理解起来可能有点难,我们通过代码来描述下这个限制:
上面代码例子中:
情况1: add_provider提供的数据至少有一对数据相等
testC会执行一次, 因为testC是依赖于testB的, 但是testB使用了数据提供者方法,那么testC中是无法收到testB return的值的
情况2: add_provider提供的数据没有一对数据相等
testC永远不会执行
测试异常
有时需要测试某些情况下代码是否按照要求抛出了相关的异常。
在PHPUnit中,有3种方式来检查异常是否抛出。
方法一: 注释法, 用@expectedException 标定期待的异常
* @expectedException InvalidArgumentException
*/
public function testException()
{
}
方法二: 设定法,使用 $this->setExpectedException 设定期待的异常
{
$this->setExpectedException('InvalidArgumentException');
}
方法三: try catch + fail法
try {
// 这里写上会引发异常的代码
} catch (InvalidArgumentException $expected) {
// 抓到异常测试通过
return ;
}
// 没抓到异常就算失败
$this->fail('An expected exception has not been raised.' );
}
测试PHP错误
有时,代码在运行时会出现php错误,如整除0,文件不存在等等.在PHPUnit中,它会自动把错误转换为异常PHPUnit_Framework_Error并抛出,我们只需要在测试方法中设定抓取这个异常即可:
* @expectedException PHPUnit_Framework_Error // 期待PHPUnit_Framework_Error的异常
*/
public function testFailingInclude()
{
// include一个不存在的文件,就会抛出一个PHPUnit_Framework_Error的异常
include 'not_existing_file.php';
}
本节就介绍到这里了,下篇将详细介绍PHPUnit测试的核心:断言,敬请关注。
首先,引入一个测试PHP数组操作的测试用例,这个例子会给你展示PHPUnit常规的用法和测试用例编写的步骤。
当前目标主要是对PHPUnit的基本使用有一个大概的了解,类似开发程序教程中的Hello World程序,知道程序怎么写,怎么去运行。
编写一个栈测试的例子的步骤:
1. 定义一个测试类StackTest保存于StackTest.php中
2. 这个类继承于PHPUnit_Framework_TestCase
3. 定义类中的测试方法,类的所有的测试用例方法需要用test开头,当然,你也可以在使用@test注释来定义一个名字不为test开头的方法为测试方法
4. 在这些测试方法中,我们需要使用断言方法(assertion methods)如assertEquals()来断言实际传入的参数和期待的参数的值相同来达到测试的目的.
代码:
以上代码中,使用断言方法assertEquals来断言我期待$stack相关的值
注意:代码中高亮的方法indexEquals,它并没有使用test开头,但是我在注释中增加了@test标签,那么phpunit依然会将其作为一个测试方法运行。
下面来看运行方法和运行结果,进入命令行,使用phpunit StackTest来执行StackTest.php的测试,运行结果如下:
.F 表示执行完毕且出现断言失败
Time: 0 seconds, Memory: 5.50Mb
表示执行时间为0秒,使用5.50MB内存
There was 1 failure:
这里有一个失败
1) StackTest::indexEuqals
StackTest类的indexEquals方法
Failed asserting that <integer:1> matches expected <integer:2>.
断言值为2但是传入的值却是1
/home/colt/workspace/PHP/test/StackTest.php:28
FAILURES!
Tests: 2, Assertions: 6, Failures: 1.
执行了2个测试,共6个断言,失败1
由于在indexEquals断言$stack索引0的值为2,但是实际上值的是1,所以这个断言没有通过测试并向我们报告了失败。
本例子通过一个简单易懂的测试类解开测试的神秘面纱:
通俗的讲,单元测试就是在测试用例类中,定义一系列的测试方法,在方法中使用断言(assert)来测试你程序中的相关函数、类、接口、过程的执行结果是否和你预期的是一样的,如果某个部分的执行结果没有与你期待的结果相同,PHPUnit就会向你报告问题,你也可以方便的根据报告确认和修复程序中的Bug。
针对你的程序写好详细的完整单元测试,你就不用每次完成一个新功能后逐个的测试你软件的所有功能,而且在程序发布之前,你至少可以通过测试消灭大部分的内部逻辑Bug和缺陷。
同样,你在之后的维护开发中,如果在某个功能的修改中不小心改坏了与其相关连的其他模块,那么先前完善的单元测试也会向你报告出相关的问题让你及时发现和修复问题。
本篇笔记完结,下篇将继续介绍使用PHPUnit编写测试用例的进阶知识。