PHPUnit9.0 编写PHPUnit测试-测试的依赖关系
示例 2.1 展示了如何用 PHPUnit 编写测试来对 PHP 数组操作进行测试。此示例介绍了用 PHPUnit 编写测试的基本惯例与步骤:
- 针对类
Class
的测试写在类 ClassTest
中。 -
ClassTest
(通常)继承自 PHPUnit\Framework\TestCase
。 - 测试都是命名为
test*
的公用方法。也可以在方法的文档注释块(docblock)中使用 @test
标注将其标记为测试方法。 - 在测试方法内,类似于
assertSame()
这样的断言方法用来对实际值与预期值的匹配做出断言。
示例 2.1 用 PHPUnit 测试数组操作
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class StackTest extends TestCase
{
public function testPushAndPop(): void
{
$stack = [];
$this->assertSame(0, count($stack));
array_push($stack, 'foo');
$this->assertSame('foo', $stack[count($stack)-1]);
$this->assertSame(1, count($stack));
$this->assertSame('foo', array_pop($stack));
$this->assertSame(0, count($stack));
}
}
当你想把一些东西写到 print
语句或者调试表达式中时,别这么做,改为将其写成测试。
测试的依赖关系
单元测试主要是作为一种良好实践来编写的,它能帮助开发人员识别并修复 bug、重构代码,还可以看作被测软件单元的文档。要实现这些好处,理想的单元测试应当覆盖程序中所有可能的路径。一个单元测试通常覆盖一个函数或方法中的一个特定路径。但是,测试方法不一定是封装良好的独立实体。测试方法之间经常有隐含的依赖关系暗藏在测试的实现方案中。
PHPUnit支持对测试方法之间的显式依赖关系进行声明。这种依赖关系并不是定义在测试方法的执行顺序中,而是允许生产者(producer)返回一个测试基境(fixture)的实例,并将此实例传递给依赖于它的消费者(consumer)们。
- 生产者(producer),是能生成被测单元并将其作为返回值的测试方法。
- 消费者(consumer),是依赖于一个或多个生产者及其返回值的测试方法。
示例 2.2 展示了如何用 @depends
标注来表示测试方法之间的依赖关系。
示例 2.2 用 @depends
标注来表示依赖关系
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class StackTest extends TestCase
{
public function testEmpty(): array
{
$stack = [];
$this->assertEmpty($stack);
return $stack;
}
/**
* @depends testEmpty
*/
public function testPush(array $stack): array
{
array_push($stack, 'foo');
$this->assertSame('foo', $stack[count($stack)-1]);
$this->assertNotEmpty($stack);
return $stack;
}
/**
* @depends testPush
*/
public function testPop(array $stack): void
{
$this->assertSame('foo', array_pop($stack));
$this->assertEmpty($stack);
}
}
在上例中,第一个测试testEmpty()
创建了一个新数组,并断言其为空。随后,此测试将此基境作为结果返回。第二个测试 testPush()
依赖于 testEmpty()
,并将所依赖的测试之结果作为参数传入。最后,testPop()
依赖于 testPush()
。
注解:
默认情况下,生产者所产生的返回值将原样传递给相应的消费者。这意味着,如果生产者返回的是一个对象,那么传递给消费者的将是指向此对象的引用。但同样也可以(a)通过 @depends clone
来传递指向深拷贝对象的引用,或(b)通过 @depends shallowClone
来传递指向正常浅克隆对象(基于 PHP 关键字 clone
)的引用。
为了定位缺陷,我们希望把注意力集中于相关的失败测试上。这就是为什么当某个测试所依赖的测试失败时,PHPUnit 会跳过这个测试。利用测试之间的依赖关系可以改进缺陷定位,如示例 2.3 所示。
示例 2.3 利用测试之间的依赖关系
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class DependencyFailureTest extends TestCase
{
public function testOne(): void
{
$this->assertTrue(false);
}
/**
* @depends testOne
*/
public function testTwo(): void
{
}
}
$ phpunit --verbose DependencyFailureTest
PHPUnit latest.0 by Sebastian Bergmann and contributors.
FS
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) DependencyFailureTest::testOne
Failed asserting that false is true.
/home/sb/DependencyFailureTest.php:6
There was 1 skipped test:
1) DependencyFailureTest::testTwo
This test depends on "DependencyFailureTest::testOne" to pass.
FAILURES!
Tests: 1, Assertions: 1, Failures: 1, Skipped: 1.
测试可以使用多个 @depends
标注。PHPUnit 不会更改测试的运行顺序,因此你需要自行保证某个测试所依赖的所有测试均出现于这个测试之前。
拥有多个 @depends
标注的测试,其第一个参数是第一个生产者提供的基境,第二个参数是第二个生产者提供的基境,以此类推。参见示例 2.4
示例 2.4 有多个依赖项的测试
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class MultipleDependenciesTest extends TestCase
{
public function testProducerFirst(): string
{
$this->assertTrue(true);
return 'first';
}
public function testProducerSecond(): string
{
$this->assertTrue(true);
return 'second';
}
/**
* @depends testProducerFirst
* @depends testProducerSecond
*/
public function testConsumer(string $a, string $b): void
{
$this->assertSame('first', $a);
$this->assertSame('second', $b);
}
}
$ phpunit --verbose MultipleDependenciesTest
PHPUnit latest.0 by Sebastian Bergmann and contributors.
...
Time: 0 seconds, Memory: 3.25Mb
OK (3 tests, 4 assertions)
更多建议: