先发下简书的干货:

教你一步一步写一个phpunit testcase:https://www.jianshu.com/p/ba6829a6f3ec

程序地址

https://github.com/yezuozuo/how-to-write-a-phpunit-testcase

使用方法

composer install
phpunit tests/EventTest.php

  

背景

https://phpunit.de/manual/current/zh_cn/index.html 这个是phpunit的文档,但是看完了我完全不知道怎么入手好吗,从0到1这个过程就在下面。

综述

目录结构

.
|-- 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的主要功能,用来验证单元的执行结果是不是预期值。

例子:

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

<?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

<?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的单元测试结果是不是符合预期。

<?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,这个叫做依赖测试。

依赖测试,如果有两个测试用例,具有依赖关系,就可以使用测试依赖在两个测试用例建立依赖关系。

这里将报名与取消报名分成两个测试用例,让取消报名的测试依赖于报名的测试。

执行测试

➜  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()测试失败,接着再执行测试:

➜  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()测试用例,在注释内标注:

@expectedException \PHPUnitEventDemo\EventException 预期的异常类。
@expectedExceptionMessage 预期的异常消息。
@expectedExceptionCode 预期的异常代码。

  

也就是,预期在这个测试用例内会接收到EventException的异常类别,异常消息为预留的值,异常代码为1。

数据提供者为:

 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;
}

  

在数据提供者的基础上进行对报名人数限制的测试:

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()分别来初始化测试与还原到初始化前的状态。

代码如下:

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中使用这两个变量。

所以在执行测试的时候,运行顺序会是:

setUp()->testReserve()->tearDown()->...->setUp()->testDuplicatedReservationWithException

  

设定phpunit

在前面使用phpunit工具来执行测试时,有用到--bootstrap,在执行测试前先执行vendor/autoload.php来注入自动加载的功能。但是每次执行测试,都要加上参数有点麻烦,phpunit可以使用XML来设定测试。

phpunit.xml的内容如下:

<?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来指定。

直接执行,结果如下:

➜  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分析的结果。

然后执行

phpunit --coverage-html reports/ tests/

  

或者执行

phpunit --bootstrap vendor/autoload.php --coverage-html reports/ tests/

  

当然,也可以使用XML来设定。

<?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>

  

接着执行测试:

phpunit tests/EventTest.php

  

就可以在reports/下打开index.html或其他html页面,浏览code coverage分析的结果。

如图所示:

 
1.jpeg
 
2.jpeg

结束。


作者:_叶左左
链接:https://www.jianshu.com/p/ba6829a6f3ec
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

PHPUnit_Framework_Assert单元测试的更多相关文章

  1. 单元测试 2 & 初识模块3

    单元测试 - 创建测试用例 单元测试是什么? (老鸟可以无视下面这段话.) hi,新同学们,咱们的PHP代码里满布着好多函数和类,经常互相调用,你改的一个函数/方法可能是"比较底层" ...

  2. Intellij idea添加单元测试工具

    1.idea 版本是14.0.0 ,默认带有Junit,但是不能自动生成单元测试,需要下载JunitGererator2.0插件 2.Settings -Plugins,下载 JunitGenerat ...

  3. Python的单元测试(二)

    title: Python的单元测试(二) date: 2015-03-04 19:08:20 categories: Python tags: [Python,单元测试] --- 在Python的单 ...

  4. Python的单元测试(一)

    title: Python的单元测试(一) author: 青南 date: 2015-02-27 22:50:47 categories: Python tags: [Python,单元测试] -- ...

  5. javascript单元测试框架mochajs详解

    关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...

  6. 使用NUnit为游戏项目编写高质量单元测试的思考

    0x00 单元测试Pro & Con 最近尝试在我参与的游戏项目中引入TDD(测试驱动开发)的开发模式,因此单元测试便变得十分必要.这篇博客就来聊一聊这段时间的感悟和想法.由于游戏开发和传统软 ...

  7. 我这么玩Web Api(二):数据验证,全局数据验证与单元测试

    目录 一.模型状态 - ModelState 二.数据注解 - Data Annotations 三.自定义数据注解 四.全局数据验证 五.单元测试   一.模型状态 - ModelState 我理解 ...

  8. ABAP单元测试最佳实践

    本文包含了我在开发项目中经历过的实用的ABAP单元测试指导方针.我把它们安排成为问答的风格,欢迎任何人添加更多的Q&A's,以完成这个列表. 在我的项目中,只使用传统的ABAP report. ...

  9. python_单元测试unittest

    Python自带一个单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法和一些用例执行前的初始化操作. 步骤1:首先引入unittest模块--import un ...

随机推荐

  1. kaptcha Java验证码

    原文:http://www.cnblogs.com/chizizhixin/p/5311619.html 在项目中经常会使用验证码,kaptcha 就一个非常不错的开源框架,分享下自己在项目中的使用: ...

  2. Assembly.Load动态加载程序集而不占用文件 z

    方式一:占用文件的加载 Assembly assembly = Assembly.Load(path); 用上面的方法可以动态的加载到dll,但是用这种方法加载到的dll一直到程序运行结束都是占用的d ...

  3. mac mysql命令行

    https://www.cnblogs.com/lonecloud/p/5841522.html mac下使用mysql控制台命令行   命令行中输入 open .bash_profile 然后将 a ...

  4. zabbix日志监控

    一般情况下,日志最先反映出应用当前的问题,在海量日志里面找到我们异常记录,例如监控系统日志.nginx.Apache.业务日志,然后记录下来,并且根据情况报警. 1.日志监控项介绍 最主要的是监控日志 ...

  5. zabbix分组报警

    生产上需要在出现报警情况下,不同的主机发送报警给不同的用户 下面实例为分组为dev(开发组)和ops(运维组) 1.把主机进行分组 创建主机群组 配置-->主机群组-->创建主机群组,创建 ...

  6. 使navicat可以通过SSH连接MySQL数据库

    1.编辑/etc/ssh/sshd_config,在最下面添加如下语句 KexAlgorithms diffie-hellman-group1-sha1,curve25519-sha256@libss ...

  7. JAVA反射机制--静态加载与动态加载

    Java反射是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如pu ...

  8. Angular 学习笔记——ng-disable

    <!DOCTYPE html> <html lang="en" ng-app="myApp"> <head> <met ...

  9. JAVA Eclipse如何设置点击按钮切换图片

    右击图片文件夹,新建一个Android XML文件   设置文件的名称,注意这个新建的xml文件就是会被用作按钮的background属性的,所以名字不要太奇怪,设置Root Element为sele ...

  10. Laravel之命令

    一.创建命令 php artisan make:console SendEmails 上述命令将会生成一个类app/Console/Commands/SendEmails.php,当创建命令时,--c ...