PHPUnit_Framework_Assert单元测试
先发下简书的干货:
教你一步一步写一个phpunit testcase:https://www.jianshu.com/p/ba6829a6f3ec
程序地址
https://github.com/yezuozuo/how-to-write-a-phpunit-testcase
使用方法
1 2 | composer install phpunit tests/EventTest.php |
背景
https://phpunit.de/manual/current/zh_cn/index.html 这个是phpunit的文档,但是看完了我完全不知道怎么入手好吗,从0到1这个过程就在下面。
综述
目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | . |-- reports |-- src | -- PHPUnitEventDemo | -- Event.php | -- EventException.php | -- User.php |-- tests | -- EventTest.php |-- .gitignore |-- composer.json |-- phpunit.xml |-- README.md PHPUnitEventDemo - 下面是要测试的类 Event.php - Event类 EventException.php - Event异常类 User.php - User类 tests - 单元测试目录 EventTest.php - 测试Event类的测试用例 |
Assertions(断言)
断言为PHPUnit的主要功能,用来验证单元的执行结果是不是预期值。
例子:
1 2 3 4 | assertTrue(true); # SUCCESSFUL assertEquals( 'orz' , 'oxz' , 'The string is not equal with orz' ); #UNSUCCESSFUL assertCount(1, array ( 'Monday' )); # SUCCESSFUL assertContains( 'PHP' , array ( 'PHP' , 'Java' , 'Ruby' )); # SUCCESSFUL |
assertTrue():判断实际值是否为true。
assertEquals():预期值是orz,实际值是oxz,因为两个值不相等,所以这一个断言失败,会显示The string is not equal with orz的字串。
assertCount():预期数组大小为1。
assertContains():预期数组中有一个PHP字串的元素存在。
从上面的后三个assertions可以发现,预期值都是在第一个参数,而后面则是实际值。
代码示例
src/PHPUnitEventDemo/User.php
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 32 33 34 35 36 37 38 | <?php namespace PHPUnitEventDemo; /** * Class User * * @package PHPUnitEventDemo */ class User { /** * @var int 用户id */ public $id ; /** * @var string 用户名 */ public $name ; /** * @var string 用户邮箱 */ public $email ; /** * User constructor. * * @param $id * @param $name * @param $email */ public function __construct( $id , $name , $email ) { $this ->id = $id ; $this ->name = $name ; $this ->email = $email ; } } |
User类很单纯,主要就是建立User对象。
src/PHPUnitEventDemo/Event.php
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | <?php namespace PHPUnitEventDemo; /** * Class Event * * @package PHPUnitEventDemo */ class Event { /** * @var int event id */ public $id ; /** * @var string 事件名 */ public $name ; /** * @var string 事件开始时间 */ public $startDate ; /** * @var string 事件结束时间 */ public $endDate ; /** * @var int 参加者限制 */ public $attendLimit ; /** * @var array 参加者列表 */ public $attendArr = array (); /** * Event constructor. * * @param $id * @param $name * @param $startDate * @param $endDate * @param $attendLimit */ public function __construct( $id , $name , $startDate , $endDate , $attendLimit ) { $this ->id = $id ; $this ->name = $name ; $this ->startDate = $startDate ; $this ->endDate = $endDate ; $this ->attendLimit = $attendLimit ; } /** * 用户报名,将报名的用户存在数组中,数组的索引值就是用户的id * * @param $user * @return bool * @throws EventException */ public function reserve( $user ) { // 报名人数是否超过限制 if ( $this ->attendLimit > $this ->getAttendNumber()) { if ( array_key_exists ( $user ->id, $this ->attendArr)) { throw new EventException( 'Duplicated reservation' , EventException::DUPLICATED_RESERVATION); } // 使用者报名 $this ->attendArr[ $user ->id] = $user ; return true; } return false; } /** * 获取报名用户的人数 * * @return int */ public function getAttendNumber() { return sizeof( $this ->attendArr); } /** * 取消报名 * * @param $user */ public function unReserve( $user ) { unset( $this ->attendArr[ $user ->id]); } } |
Event类就是用来让用户报名的,也很简单。
接着我们需要写EventTest来测试Event的单元测试结果是不是符合预期。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | <?php /** * Class EventTest */ class EventTest extends PHPUnit_Framework_TestCase { /** * @var PHPUnitEventDemo\Event 事件 */ private $event ; /** * @var PHPUnitEventDemo\User 用户 */ private $user ; public function setUp() { $eventId = 1; $eventName = '活动1' ; $eventStartDate = '2016-11-01 18:00:00' ; $eventEndDate = '2016-11-01 20:00:00' ; $eventAttendLimit = 10; $this ->event = new \PHPUnitEventDemo\Event( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendLimit ); $userId = 1; $userName = 'User1' ; $userEmail = 'user1@zoco.space' ; $this ->user = new \PHPUnitEventDemo\User( $userId , $userName , $userEmail ); } public function tearDown() { $this ->event = null; $this ->user = null; } /** * 测试报名 * * @return array */ public function testReserve() { $this ->event->reserve( $this ->user); $expectedNumber = 1; // 预期报名人数 $this ->assertEquals( $expectedNumber , $this ->event->getAttendNumber()); // 报名清单中有已经报名的人 $this ->assertContains( $this ->user, $this ->event->attendArr); return [ $this ->event, $this ->user]; } /** * 测试取消报名 * * @param $obj * @depends testReserve */ public function testUnReserve( $obj ) { $event = $obj [0]; $user = $obj [1]; // 使用者取消报名 $event ->unReserve( $user ); $unReserveExpectedCount = 0; // 预期报名人数 $this ->assertEquals( $unReserveExpectedCount , $event ->getAttendNumber()); // 报名清单中没有已经取消报名的人 $this ->assertNotContains( $user , $event ->attendArr); } /** * @param $eventId * @param $eventName * @param $eventStartDate * @param $eventEndDate * @param $eventAttendLimit * @dataProvider eventsDataProvider */ public function testAttendeeLimitReserve( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendLimit ) { // 测试报名人数限制 $event = new \PHPUnitEventDemo\Event( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendLimit ); $userNumber = 6; // 建立不同使用者报名 for ( $userCount = 1; $userCount < $userNumber ; $userCount ++) { $userId = $userCount ; $userName = 'User ' . $userId ; $userEmail = 'user' . $userId . '@zoco.space' ; $user = new \PHPUnitEventDemo\User( $userId , $userName , $userEmail ); $reservedResult = $event ->reserve( $user ); // 报名人數是否超过 if ( $userCount > $eventAttendLimit ) { // 无法报名 $this ->assertFalse( $reservedResult ); } else { $this ->assertTrue( $reservedResult ); } } } /** * @return array */ public function eventsDataProvider() { $eventId = 1; $eventName = '活动1' ; $eventStartDate = '2016-11-01 12:00:00' ; $eventEndDate = '2016-11-01 13:00:00' ; $eventAttendeeLimitNotFull = 5; $eventAttendeeFull = 10; $eventsData = array ( array ( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendeeLimitNotFull ), array ( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendeeFull ) ); return $eventsData ; } /** * @expectedException \PHPUnitEventDemo\EventException * @expectedExceptionMessage Duplicated reservation * @expectedExceptionCode 1 */ public function testDuplicatedReservationWithException() { // 测试重复报名,预期丢出异常 // 同一个使用者报名两次 $this ->event->reserve( $this ->user); $this ->event->reserve( $this ->user); } } |
EventTest会继承phpunit的类PHPUnit_Framework_TestCase。
EventTest内有一个测试用例testReserve()。
testReserve()内主要会建立一个用户及事件,使用者去报名一个活动,所以活动已经有一个人报名了。
接下来的断言,assertEquals()会预期活动报名人数有1个人。
assertContains()预期在活动报名清单内,已经有已报名的使用者。
其中有一个@depends testReserve,这个叫做依赖测试。
依赖测试,如果有两个测试用例,具有依赖关系,就可以使用测试依赖在两个测试用例建立依赖关系。
这里将报名与取消报名分成两个测试用例,让取消报名的测试依赖于报名的测试。
执行测试
1 2 3 4 5 6 7 8 | ➜ how-to-write-a-phpunit-testcase git:(master) ✗ phpunit --bootstrap vendor/autoload.php tests/EventTest.php PHPUnit 5.4.8 by Sebastian Bergmann and contributors. ..... 5 / 5 (100%) Time: 78 ms, Memory: 10.00MB OK (5 tests, 17 assertions) |
Producer 与 Consumer
testUnReserve()在注释内利用@depends testReserve()标记依赖于testReserve()测试,而被依赖的测试可以当作producer,将值传给依赖的测试testUnReserve()为consumer,通过参数接收。
这样就能够报名testReserve()与取消报名testUneserve()测试分开,testUneserve()会接收来自testReserve()的返回值,为一个两个元素的数组,数组的第一个元素为,已经有人报名的对象,第二个元素为用户对象,是已经报名的使用者。
Q:如果testReserve()执行失败,testUnReserve()会执行吗?
A:是不会的,当被依赖的测试案例如果测试失败,那依赖的测试就会忽略执行。
我们可以试着将testReserve()故意测试失败,只需要针对事件物件的getAttendNumber()断言的预期值,从1改成0就可以让testReserve()测试失败,接着再执行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ➜ how-to-write-a-phpunit-testcase git:(master) ✗ phpunit --bootstrap vendor/autoload.php tests/EventTest.php PHPUnit 5.4.8 by Sebastian Bergmann and contributors. FS.. Time: 110 ms, Memory: 10.00MB There was 1 failure: 1) EventTest::testReserve Failed asserting that 1 matches expected 0. /Users/wangzhihao/git/how-to-write-a-phpunit-testcase/tests/EventTest.php:52 FAILURES! Tests: 4, Assertions: 14, Failures: 1, Skipped: 1. |
Data Providers(数据提供者)
数据提供者,能提供多次的测试数据进行多次的测试。
使用数据提供者,能让测试更简洁,因为,可以将测试的断言与测试数据分开写。
在EventTest内增加一个testDuplicatedReservationWithException()测试用例,在注释内标注:
1 2 3 | @expectedException \PHPUnitEventDemo\EventException 预期的异常类。 @expectedExceptionMessage 预期的异常消息。 @expectedExceptionCode 预期的异常代码。 |
也就是,预期在这个测试用例内会接收到EventException的异常类别,异常消息为预留的值,异常代码为1。
数据提供者为:
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 | public function eventsDataProvider() { $eventId = 1; $eventName = '活动1' ; $eventStartDate = '2016-11-01 12:00:00' ; $eventEndDate = '2016-11-01 13:00:00' ; $eventAttendeeLimitNotFull = 5; $eventAttendeeFull = 10; $eventsData = array ( array ( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendeeLimitNotFull ), array ( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendeeFull ) ); return $eventsData ; } |
在数据提供者的基础上进行对报名人数限制的测试:
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 testAttendeeLimitReserve( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendLimit ) { // 测试报名人数限制 $event = new \PHPUnitEventDemo\Event( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendLimit ); $userNumber = 6; // 建立不同使用者报名 for ( $userCount = 1; $userCount < $userNumber ; $userCount ++) { $userId = $userCount ; $userName = 'User ' . $userId ; $userEmail = 'user' . $userId . '@zoco.space' ; $user = new \PHPUnitEventDemo\User( $userId , $userName , $userEmail ); $reservedResult = $event ->reserve( $user ); // 报名人數是否超过 if ( $userCount > $eventAttendLimit ) { // 无法报名 $this ->assertFalse( $reservedResult ); } else { $this ->assertTrue( $reservedResult ); } } } |
Fixtures
Fixture能协助建立测试时需要用到的测试环境,对象的建立,在测试完后,把测试环境,对象析构掉,还原到初始化前的状态。
主要透过setUp()与tearDown()分别来初始化测试与还原到初始化前的状态。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public function setUp() { $eventId = 1; $eventName = '活动1' ; $eventStartDate = '2016-11-01 18:00:00' ; $eventEndDate = '2016-11-01 20:00:00' ; $eventAttendLimit = 10; $this ->event = new \PHPUnitEventDemo\Event( $eventId , $eventName , $eventStartDate , $eventEndDate , $eventAttendLimit ); $userId = 1; $userName = 'User1' ; $userEmail = 'user1@zoco.space' ; $this ->user = new \PHPUnitEventDemo\User( $userId , $userName , $userEmail ); } public function tearDown() { $this ->event = null; $this ->user = null; } |
把$event,$user类修改成全局变量,接着把构造类写在setUp()中,析构类写在tearDown(),testReserve()与testDuplicatedReservationWithException中使用这两个变量。
所以在执行测试的时候,运行顺序会是:
1 | setUp()->testReserve()->tearDown()->...->setUp()->testDuplicatedReservationWithException |
设定phpunit
在前面使用phpunit工具来执行测试时,有用到--bootstrap,在执行测试前先执行vendor/autoload.php来注入自动加载的功能。但是每次执行测试,都要加上参数有点麻烦,phpunit可以使用XML来设定测试。
phpunit.xml的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version= "1.0" encoding= "UTF-8" ?> <phpunit bootstrap= "../vendor/autoload.php" verbose= "true" > <testsuite> <directory suffix= "Test.php" >../tests</directory> </testsuite> <filter> <whitelist processUncoveredFilesFromWhitelist= "true" > <directory suffix= ".php" >../src</directory> </whitelist> </filter> </phpunit> |
加入bootstrap和filter屬性。
执行测试,如果XML名字不是phpunit.xml的话,可以利用--configuration来指定。
直接执行,结果如下:
1 2 3 4 5 6 7 8 9 10 11 | ➜ how-to-write-a-phpunit-testcase git:(master) ✗ phpunit tests/EventTest.php PHPUnit 5.4.8 by Sebastian Bergmann and contributors. Runtime: PHP 7.0.12 with Xdebug 2.4.0 Configuration: /Users/wangzhihao/git/how-to-write-a-phpunit-testcase/phpunit.xml ..... 5 / 5 (100%) Time: 101 ms, Memory: 10.00MB OK (5 tests, 17 assertions) |
还有更多的phpunit.xml在这里https://phpunit.de/manual/current/en/appendixes.configuration.html
Code Coverage 分析
写好单元测试之后,该如何了解到哪些程序还没有经过测试?目标程序被测试百分比有多少?
phpunit利用PHP CodeCoverage來计算程序代码覆盖率(code coverage),需要安裝 Xdebug。
该如何产生Code coverage呢?
先在项目下建立一个reports/目录,存放code coverage分析的结果。
然后执行
1 | phpunit --coverage-html reports/ tests/ |
或者执行
1 | phpunit --bootstrap vendor/autoload.php --coverage-html reports/ tests/ |
当然,也可以使用XML来设定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?xml version= "1.0" encoding= "UTF-8" ?> <phpunit bootstrap= "./vendor/autoload.php" verbose= "true" > <testsuite> <directory suffix= "Test.php" >./tests</directory> </testsuite> <filter> <whitelist processUncoveredFilesFromWhitelist= "true" > <directory suffix= ".php" >./src</directory> </whitelist> </filter> <logging> <log type= "coverage-html" target= "./report" charset= "UTF-8" /> </logging> </phpunit> |
接着执行测试:
1 | phpunit tests/EventTest.php |
就可以在reports/下打开index.html或其他html页面,浏览code coverage分析的结果。
如图所示:


结束。
作者:_叶左左
链接:https://www.jianshu.com/p/ba6829a6f3ec
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!