概述
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。
测试DEMO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
namespace Controller;
class Transfer { private $accountA = 100; private $accountB = 100;
public function aToB(int $money) { $this->accountA -= $money; $this->accountB += $money; }
public function bToA(int $money) { $this->accountB -= $money; $this->accountA += $money; }
public function getAccountA() { return $this->accountA; }
public function getAccountB() { return $this->accountB; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
namespace Test;
use Controller\Transfer; use PHPUnit\Framework\TestCase;
class TransferTest extends TestCase { private $transferObj;
public function setUp() : void { $this->transferObj = new Transfer(); }
public function testAtoB() { $originalA = $this->transferObj->getAccountA(); $originalB = $this->transferObj->getAccountB();
$this->transferObj->aToB(10); $this->assertEquals($originalA - 10, $this->transferObj->getAccountA()); $this->assertEquals($originalB + 10, $this->transferObj->getAccountB()); } }
|
1 2 3 4 5 6 7
| PHPUnit 9.5-gd3b55c36f by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 00:00.029, Memory: 8.00 MB
OK (1 test, 2 assertions)
|
详细使用
基境
https://phpunit.readthedocs.io/en/9.3/fixtures.html
PHPUnit 支持共享建立基境的代码。
提供了以下几个模板方法:
setUpBeforeClass
: 测试用例类的第一个测试运行之前执行
tearDownAfterClass
: 测试用例类的最后一个测试运行之后执行
setUp
: 每个测试方法运行之前执行
tearDown
: 每个测试方法运行之后执行
注意:每个测试方法都是在一个全新的测试类实例上运行的
全局状态
https://phpunit.readthedocs.io/en/9.3/fixtures.html#global-state
- 全局变量:有时候测试代码中用到了全局变量($_GLOBALS),但是如果对这里面的变量进行了修改,可能会导致其他测试方法出现问题,那么怎么保证每个测试方法都使用的是一样的全局变量呢? 通过:
@backupGlobals disabled|enabled
它可标注在:
- 测试类: 作用范围为整个测试类
- 测试方法: 作用范围为这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
namespace Test;
use PHPUnit\Framework\TestCase;
class MyTest extends TestCase {
public function testThatInteractsWithGlobalVariables() {
} }
|
支持设置 “全局变量黑名单” 黑名单中的全局变量将被排除于备份与还原操作之外:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
class MyTest extends TestCase { protected $backupGlobalsBlacklist = ['globalVariable'];
public function testThatInteractsWithGlobalVariables() {
} }
|
对于全局变量的备份和还原的原理是使用了:serialize()
与 unserialize()
。
同样提供了黑名单支持:
1 2 3 4 5 6 7 8
| class MyTest extends TestCase { protected $backupStaticAttributesBlacklist = [ 'className' => ['attributeName'] ]; }
|
依赖关系
使用 @depends
声明测试方法所依赖的其他测试方法。 依赖方法的返回值,会作为被依赖方法的参数,其顺序和 @depends
的顺序一致,但是不会影响代码的执行顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public function testOne() { $this->assertTrue(true);
return "depends1"; }
public function testTwo() { $this->assertTrue(true); return "depends2"; }
public function testDepends() { $this->assertEquals(['depends1', 'depends2'], func_get_args()); }
|
1 2 3
| Time: 00:00.315, Memory: 6.00 MB
OK (3 tests, 3 assertions)
|
- 注意:
- 当被依赖的测试方法失败时,不会再执行依赖方法的测试。
- 如果被依赖方法返回的是对象,默认是引用传递,如果希望传递对象的副本时,使用:
@depends clone
数据供给器
使用 @dataProvider
声明数据供给器。 对应的方法需要返回:
- 数组(每个元素也是数组)
- 可遍历的对象(实现了迭代接口)
然后测试时,会将每次迭代器提供的一组数据进行测试,直到全部遍历完毕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public function testSum($a, $b, $sum) { $this->assertEquals($sum, $a + $b); }
public function additionProvider() { return [ [1, 3, 4], [1, 1, 2], [1, 1, 3], ]; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| Failed asserting that 2 matches expected 3. Expected :3 Actual :2 <Click to see difference>
/Users/caoxl/WWW/test.com/tests/MyTest.php:56
Time: 00:00.403, Memory: 8.00 MB
FAILURES! Tests: 3, Assertions: 3, Failures: 1.
|
- 注意:
- 和
@depends
同时使用时,@provider
提供的参数会优先于 @depends
提供的参数,并且,依赖关系提供的参数不会变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public function testOne() { $this->assertTrue(true); return 'Depends1'; }
public function testSum() { $this->assertEquals(['Provider1', 'Depends1'], func_get_args()); }
public function additionProvider() { return [ ['Provider1'], ['Provider2'], ]; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| PHPUnit 9.5-gd3b55c36f by Sebastian Bergmann and contributors.
Failed asserting that two arrays are equal. <Click to see difference>
/Users/caoxl/WWW/test.com/tests/MyTest.php:57
Time: 00:00.378, Memory: 8.00 MB
FAILURES! Tests: 3, Assertions: 3, Failures: 1.
|
异常测试
异常测试有两种方式:
- 在代码中使用:
$this->expectException(InvalidArgumentException::class);
- 使用标注:
@expectException
断言方法/标注:
expectException
expectExceptionCode
expectExceptionMessage
expectExceptionMessageRegExp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public function testException1() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("hello"); throw new Exception('hello'); }
public function testException2() { throw new InvalidArgumentException('hello'); }
|
注意:不允许对 :Exception
类进行测试,异常类越明确越好。
错误调试
默认情况下,在测试过程中如果触发到了 PHP
的错误/警告,PHPUnit
会将其转换为异常:
PHPUnit\Framework\Error\Notice
PHPUnit\Framework\Error\Warning
PHPUnit\Framework\Error\Error
1 2 3 4 5 6
| public function testError() { $this->expectException(Error::class); include 'file_not_existing_file.php'; }
|
1 2 3 4 5
| Time: 00:00.319, Memory: 8.00 MB
FAILURES! Tests: 1, Assertions: 1, Failures: 1.
|
输出内容测试
有时候,想要断言 某方法的运行过程中生成了预期的输出(例如,通过 echo
或 print
)。PHPUnit\Framework\TestCase
类使用 PHP 的 输出缓冲 特性来为此提供必要的功能支持。
1 2 3 4 5 6 7 8 9 10 11
| public function testOutput1() { $this->expectOutputString("Hello"); echo "Hello"; }
public function testOutput2() { $this->expectOutputRegex("/\d+/"); print "Hello World"; }
|
1 2 3 4 5 6 7 8
| // testOutput2 Failed asserting that 'Hello World' matches PCRE pattern "/\d+/".
Time: 00:00.368, Memory: 8.00 MB
FAILURES! Tests: 1, Assertions: 1, Failures: 1.
|
标记未完成 与 跳过
1 2 3 4 5 6
| public function testMark() { $this->assertTrue(true); $this->markTestIncomplete("后续还未完成"); }
|
1 2 3 4 5 6 7 8 9 10
| PHPUnit 9.5-gd3b55c36f by Sebastian Bergmann and contributors.
后续还未完成
/Users/caoxl/WWW/test.com/tests/MyTest.php:114 Time: 00:00.393, Memory: 8.00 MB
OK, but incomplete, skipped, or risky tests! Tests: 1, Assertions: 1, Incomplete: 1.
|
1 2 3 4 5 6
| public function setUp(): void { if (!extension_loaded('mysqli')) { $this->markTestSkipped("MySQLi 扩展不可用"); } }
|
1 2 3 4 5 6 7 8 9 10
| PHPUnit 9.5-gd3b55c36f by Sebastian Bergmann and contributors.
MySQLi 扩展不可用
/Users/caoxl/WWW/test.com/tests/MyTest.php:24
Time: 00:00.425, Memory: 8.00 MB
OK, but incomplete, skipped, or risky tests! Tests: 1, Assertions: 0, Skipped: 1.
|