phpunit 的官方文档对如何使用 phpunit 进行了详细的说明。本人在通读文档后进行了一些概要提升,
同时摘录了一些示例 phpunit-demo,便于以后
理解和查阅。

文档较为简洁,但是也涵盖了平时使用的基本用法,适合入门使用。

安装 phpunit

项目安装

composer require --dev phpunit/phpunit

使用 ./vendor/bin/phpunit

全局安装

composer global require --dev phpunit/phpunit

使用 phpunit

快速入门

基本格式

  • 测试类命名: 类名 + Test , eg FooClassTest
  • 测试方法命名: test + 方法名, eg testFoo

也可以使用注释 @test 来标注需要测试的方法

use PHPUnit\Framework\TestCase;class SampleTest extends TestCase
{public function testSomething(){$this->assertTrue(true, 'This should already work.');}/*** @test*/public function something(){$this->assertTrue(true, 'This should already work.');}
}

测试依赖(@depends)

有一些测试方法需要依赖于另一个测试方法的返回值,此时需要使用测试依赖。测试依赖
通过注释 @depends 来标记。

下列中, depends 方法的 return 值作为 testConsumer 的参数传入

use PHPUnit\Framework\TestCase;class MultipleDependenciesTest extends TestCase
{public function testProducerFirst(){$this->assertTrue(true);return 'first';}public function testProducerSecond(){$this->assertTrue(true);return 'second';}/*** @depends testProducerFirst* @depends testProducerSecond*/public function testConsumer($a, $b){$this->assertSame('first', $a);$this->assertSame('second', $b);}
}

数据提供器(@dataProvider)

在依赖中,所依赖函数的返回值作为参数传入测试函数。除此之外,我们也可以用数据提供器
来定义传入的数据。

use PHPUnit\Framework\TestCase;class DataTest extends TestCase
{/*** @dataProvider additionProvider*/public function testAdd($a, $b, $expected){$this->assertSame($expected, $a + $b);}public function additionProvider(){return ['adding zeros' => [0, 0, 0], // 0 + 0 = 0 pass'zero plus one' => [0, 1, 1], // 0 + 1 = 1 pass'one plus zero' => [1, 0, 1], // 1 + 0 = 1 pass'one plus one' => [1, 1, 2], // 1 + 1 = 2 pass];}
}

测试异常(expectException)

需要在测试方法的开始处声明断言,然后执行语句。而不是调用后再声明

也可以通过注释来声明 @expectedException, @expectedExceptionCode,
@expectedExceptionMessage, @expectedExceptionMessageRegExp

use PHPUnit\Framework\TestCase;class ExceptionTest extends TestCase
{public function testException(){$this->expectException(\Exception::class);throw new \Exception('test');}/*** @throws \Exception* @test* @expectedException \Exception*/public function exceptionExpect(){throw new \Exception('test');}
}

测试 PHP 错误

通过提前添加期望,来使测试正常进行,而不会报出 PHP 错误

use PHPUnit\Framework\TestCase;class ExpectedErrorTest extends TestCase
{/*** @expectedException PHPUnit\Framework\Error\Error*/public function testFailingInclude(){include 'not_existing_file.php';}
}

测试输出

直接添加期望输出,然后执行相关函数。和测试异常类似,需要先添加期望,再执行代码。

use PHPUnit\Framework\TestCase;class OutputTest extends TestCase
{public function testExpectFooActualFoo(){$this->expectOutputString('foo');print 'foo';}public function testExpectBarActualBaz(){$this->expectOutputString('bar');print 'baz';}
}

命令行测试

此章节主要说明了命令行的一些格式和可用参数。可以参考官方文档
获取细节。The Command-Line Test Runner

基境

基境就是在测试前需要准备的一系列东西。

比如有的测试需要依赖数据库的数据,那么在测试类运作前需要进行数据的准备。

主要有两个函数 setUptearDown

那为什么不直接用构造函数和析构函数呢?是因为这两个有他用,当然你可可以直接用构造函数,然后
再执行 parent::__construct,但不是麻烦嘛;

组织你的测试代码

可以通过命令行的 --bootstrap 参数来指定启动文件,用于文件加载。正常情况下,可以指向 composer 的 autoload 文件。
也可以在配置文件中配置(推荐)。

$ phpunit --bootstrap src/autoload.php tests
PHPUnit |version|.0 by Sebastian Bergmann and contributors..................................Time: 636 ms, Memory: 3.50MbOK (33 tests, 52 assertions)
<phpunit bootstrap="src/autoload.php"><testsuites><testsuite name="money"><directory>tests</directory></testsuite></testsuites>
</phpunit>

有风险的测试(Risky Tests)

无用测试(Useless Tests)

默认情况下,如果你的测试函数没有添加预期或者断言,就会被认为是无用测试。

通过设置 --dont-report-useless-tests 命令行参数,或者在 xml 配置
文件中配置 beStrictAboutTestsThatDoNotTestAnything="false" 来更改
这一默认行为。

意外的代码覆盖(Unintentionally Covered Code)

当打开这个配置后,如果使用 @covers 注释来包含一些文件的覆盖报告,就会被
判定为有风险的测试。

通过设置 --strict-coverage 命令行参数,或者在 xml 配置
文件中配置 beStrictAboutCoversAnnotation="true" 来更改
这一默认行为。

测试过程中有输出(Output During Test Execution)

如果在测试过程中输出文本,则会被认定为有风险的测试。

通过设置 --disallow-test-output 命令行参数,或者在 xml 配置
文件中配置 beStrictAboutOutputDuringTests="true" 来更改
这一默认行为。

测试超时(Test Execution Timeout)

通过注释来限制某些测试不能超过一定时间:

  • @large 60秒
  • @medium 10秒
  • @small 1秒

通过设置 --enforce-time-limit 命令行参数,或者在 xml 配置
文件中配置 enforceTimeLimit="true" 来更改
这一默认行为。

操作全局状态(Global State Manipulation)

phpunit 可以对全局状态进行检测。

通过设置 --strict-global-state 命令行参数,或者在 xml 配置
文件中配置 beStrictAboutChangesToGlobalState="true" 来更改
这一默认行为。

待完善的测试和跳过的测试

处于一些原因,我们希望跳过或者对某些测试方法标记未待完善

待完善的测试

使用 $this->markTestIncomplete 标记待完善的测试

use PHPUnit\Framework\TestCase;class SampleTest extends TestCase
{public function testSomething(){$this->assertTrue(true, 'This should already work.');$this->markTestIncomplete('This test has not been implemented yet.');}

跳过的测试

使用 markTestSkipped 来标记跳过的测试。

use PHPUnit\Framework\TestCase;class DatabaseTest extends TestCase
{protected function setUp(){if (!extension_loaded('mysqli')) {$this->markTestSkipped('The MySQLi extension is not available.');}}public function testConnection(){// ...}
}

结合 @require 来跳过测试

以下的示例中,使用 require 来限定测试需要依赖 mysqli 拓展和 5.3 以上
的 PHP 版本,否则跳过测试

use PHPUnit\Framework\TestCase;/*** @requires extension mysqli*/
class DatabaseTest extends TestCase
{/*** @requires PHP 5.3*/public function testConnection(){// Test requires the mysqli extension and PHP >= 5.3}// ... All other tests require the mysqli extension
}

数据库测试

使用数据库测试之前先安装拓展 composer require --dev phpunit/dbunit

总的来说,我们在好多的测试场景中都会用到数据库,我们可以结合 PHPUnit 的基境
章节中提到的 setUp 来进行测试。

我们来看一个示例

use PHPUnit\DbUnit\DataSet\ArrayDataSet;
use PHPUnit\DbUnit\TestCaseTrait;
use PHPUnit\Framework\TestCase;/*** 测试之前,需要在 MySQL 中新建数据库 phpunit,并且新建表 guestbook* CREATE TABLE `guestbook` (* `id` bigint(20) NOT NULL,* `content` varchar(255) COLLATE utf8mb4_bin NOT NULL,* `user` varchar(255) COLLATE utf8mb4_bin NOT NULL,* `created` datetime NOT NULL* ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;* Class ConnectionTest.** @requires extension pdo_mysql*/
class ConnectionTest extends TestCase
{use TestCaseTrait;/*** @return \PHPUnit\DbUnit\Database\DefaultConnection*/public function getConnection(){$pdo = new \PDO('mysql:host=127.0.0.1:33060;dbname=phpunit;charset=utf8mb4','root','112233');return $this->createDefaultDBConnection($pdo);}/*** 请注意,phpunit每次会先 TRUNCATE 数据库,然后把下面数组的数据插入进去.** @return ArrayDataSet*/public function getDataSet(){return new ArrayDataSet(['guestbook' => [['id' => 1,'content' => 'Hello buddy!','user' => 'joe','created' => '2010-04-24 17:15:23',],['id' => 2,'content' => 'I like it!','user' => 'mike','created' => '2010-04-26 12:14:20',],],]);}public function testCreateDataSet(){$this->markTestSkipped('just an example, skipped');$tableNames = ['guestbook'];$dataSet = $this->getConnection()->createDataSet();}public function testCreateQueryTable(){$this->markTestSkipped('just an example, skipped');$tableNames = ['guestbook'];$queryTable = $this->getConnection()->createQueryTable('guestbook', 'SELECT * FROM guestbook');}public function testGetRowCount(){$this->assertSame(2, $this->getConnection()->getRowCount('guestbook'));}/*** 测试表的内容和给定的数据集相等.*/public function testAddEntry(){$queryTable = $this->getConnection()->createQueryTable('guestbook', 'SELECT * FROM guestbook');$expectedTable = $this->createFlatXmlDataSet(__DIR__.'/expectedBook.xml')->getTable('guestbook');$this->assertTablesEqual($expectedTable, $queryTable);}
}

其中 getConnectiongetDataSet 都是 TestCaseTrait 中提供的方法,
我们在 getConnection 中设定数据库的链接动作,同时在 getDataSet 中设定
需要往数据库中写入的数据,注意,每次执行这个测试类时,都会执行

  1. 清空数据库 TRUNCATE
  2. 填充数据

如何验证呢?加一个字段为 datetime 类型,设置位数据库自动更新时间,即可看到每次执行测试时,时间都在变化

测试桩

所谓的桩测试,其实就是对一些类的方法进行一番 mock,强制其返回一些数据。因为在开发中,有一些类
依赖于第三方服务,而第三方服务又属于“不可控”因素,所以这个时候就需要使用“桩”了。

use PHPUnit\Framework\TestCase;class StubTest extends TestCase
{public function testStub(){// Create a stub for the SomeClass class.$stub = $this->createMock(SomeClass::class);// Configure the stub.$stub->method('doSomething')->willReturn('foo');// Calling $stub->doSomething() will now return// 'foo'.$this->assertSame('foo', $stub->doSomething());}public function testStub2(){// Create a stub for the SomeClass class.$stub = $this->getMockBuilder(SomeClass::class)->disableOriginalConstructor()->disableOriginalClone()->disableArgumentCloning()->disallowMockingUnknownTypes()->getMock();// Configure the stub.$stub->method('doSomething')->willReturn('foo');// Calling $stub->doSomething() will now return// 'foo'.$this->assertSame('foo', $stub->doSomething());}public function testReturnArgumentStub(){// Create a stub for the SomeClass class.$stub = $this->createMock(SomeClass::class);// Configure the stub.$stub->method('doSomething')->will($this->returnArgument(0));// $stub->doSomething('foo') returns 'foo'$this->assertSame('foo', $stub->doSomething('foo'));// $stub->doSomething('bar') returns 'bar'$this->assertSame('bar', $stub->doSomething('bar'));}public function testReturnSelf(){// Create a stub for the SomeClass class.$stub = $this->createMock(SomeClass::class);// Configure the stub.$stub->method('doSomething')->will($this->returnSelf());// $stub->doSomething() returns $stub$this->assertSame($stub, $stub->doSomething());}public function testReturnValueMapStub(){// Create a stub for the SomeClass class.$stub = $this->createMock(SomeClass::class);// Create a map of arguments to return values.$map = [['a', 'b', 'c', 'd'],['e', 'f', 'g', 'h'],];// Configure the stub.$stub->method('doSomething')->will($this->returnValueMap($map));// $stub->doSomething() returns different values depending on// the provided arguments.$this->assertSame('d', $stub->doSomething('a', 'b', 'c'));$this->assertSame('h', $stub->doSomething('e', 'f', 'g'));}public function testReturnCallbackStub(){// Create a stub for the SomeClass class.$stub = $this->createMock(SomeClass::class);// Configure the stub.$stub->method('doSomething')->will($this->returnCallback('str_rot13'));// $stub->doSomething($argument) returns str_rot13($argument)$this->assertSame('fbzrguvat', $stub->doSomething('something'));}/*** 按照指定顺序返回列表中的值*/public function testOnConsecutiveCallsStub(){// 为 SomeClass 类创建桩件。$stub = $this->createMock(SomeClass::class);// 配置桩件。$stub->method('doSomething')->will($this->onConsecutiveCalls(2, 3, 5, 7));// $stub->doSomething() 每次返回值都不同$this->assertSame(2, $stub->doSomething());$this->assertSame(3, $stub->doSomething());$this->assertSame(5, $stub->doSomething());}public function testThrowExceptionStub(){$this->expectException(\Exception::class);// 为 SomeClass 类创建桩件$stub = $this->createMock(SomeClass::class);// 配置桩件。$stub->method('doSomething')->will($this->throwException(new \Exception()));// $stub->doSomething() 抛出异常$stub->doSomething();}
}class SomeClass
{public function doSomething(){// Do something.}
}

我们看到, SomeClassdoSomething() 本身没有返回数据,我们通过桩动作,来完成了测试。

这个在测试依赖于第三方服务的相关代码时很管用。

代码覆盖度

白名单文件(Whitelisting Files)

添加到白名单文件的文件或者文件夹,会进行代码覆盖度统计的工作。具体的参数可以看帮助文档

### 忽略代码块(Ignoring Code Blocks)

我们可以添加部分代码不进行覆盖度统计。通过一些注释来标记即可

use PHPUnit\Framework\TestCase;/*** @codeCoverageIgnore*/
class Foo
{public function bar(){}
}class Bar
{/*** @codeCoverageIgnore*/public function foo(){}
}if (false) {// @codeCoverageIgnoreStartprint '*';// @codeCoverageIgnoreEnd
}exit; // @codeCoverageIgnore

执行方法进行统计(Specifying Covered Methods)

同样通过添加注释标记的方法来执行需要覆盖的方法。

use PHPUnit\Framework\TestCase;class BankAccountTest extends TestCase
{protected $ba;protected function setUp(){$this->ba = new BankAccount;}/*** @covers BankAccount::getBalance*/public function testBalanceIsInitiallyZero(){$this->assertSame(0, $this->ba->getBalance());}/*** @covers BankAccount::withdrawMoney*/public function testBalanceCannotBecomeNegative(){try {$this->ba->withdrawMoney(1);}catch (BankAccountException $e) {$this->assertSame(0, $this->ba->getBalance());return;}$this->fail();}/*** @covers BankAccount::depositMoney*/public function testBalanceCannotBecomeNegative2(){try {$this->ba->depositMoney(-1);}catch (BankAccountException $e) {$this->assertSame(0, $this->ba->getBalance());return;}$this->fail();}/*** @covers BankAccount::getBalance* @covers BankAccount::depositMoney* @covers BankAccount::withdrawMoney*/public function testDepositWithdrawMoney(){$this->assertSame(0, $this->ba->getBalance());$this->ba->depositMoney(1);$this->assertSame(1, $this->ba->getBalance());$this->ba->withdrawMoney(1);$this->assertSame(0, $this->ba->getBalance());}
}

phpunit光速入门相关推荐

  1. [MySQL光速入门]012 作业解答

    实训内容二 使用算数运算符-, 查询最低借阅量与最高借阅量的差值 select max(borrowsum)-min(borrowsum) as 差值 from book; 复制代码 使用比较运算符( ...

  2. Spring - Bean注解配置光速入门

    Bean注解配置光速入门 步骤一: 创建 web 项目,引入 Spring 的开发包 在 Spring 的注解的 AOP 中需要引入 spring-aop 的 jar 包 步骤二: 引入相关配置文件 ...

  3. 光速入门MyBatis-Plus

    光速入门MyBatis-Plus 0.特性 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 强大的 CRU ...

  4. [MySQL光速入门]020 事务

    TCL 事务控制语言 SQL的四种语言 DDL(Data Definition Language)数据库定义语言 建表, 建库, 修改表结构 DML(Data Manipulation Languag ...

  5. 光速入门Docker 和 Kubernetes,一起学~

    只需要每天晚上花三两个小时,在一周业余的时间里,你就能快速入门Docker和Kubernetes!! 第一期云原生在线技术工坊已经圆满结束,好评如潮,下面是部分参与者打卡截图: 第二期技术工坊活动再度 ...

  6. 阿里云 icp 备案流程(光速入门篇)

    文章标题 阿里云icp备案光速入门篇 文档目标 帮助自媒体老师光速完成icp备案 预计阅读时长 10min~15min 首次编写日期 2020-10-21 最近更新日期 2020-10-23 编撰作者 ...

  7. p5.js 光速入门中文教程

    本文简介 点赞 + 关注 + 收藏 = 学会了 本文的目标是和各位工友一起有序的快速上手 p5.js ,会讲解 p5.js 的基础用法. 本文会涉及到的内容包括: 项目搭建 p5.js 基础2D图形 ...

  8. numpy 光速入门示例

    基于notebook 文章目录 光速入门示例 矩阵创建 矩阵输出 运算 通用函数 索引,切片及循环 改变矩阵形状 堆叠 拆分 副本和视图 函数和方法概述 高级索引技巧详见我另一篇blog 光速入门示例 ...

  9. 光速入门消息队列Kafka

    文章目录 光速入门消息队列Kafka 消息队列 知识要点 2.1 背景.问题的产生 2.2 消息队列应运而生 2.3 消息队列的特点 认识kafka 知识要点 2.1 认识kafka 2.2 kafk ...

  10. typescript光速入门

    文章目录 运行环境 数据类型 函数 面向对象 TypeScript(后称ts)是微软开发的开源编程语言,是JavaScript(后称js)的超集并可以转译成纯的js,对React.Angular.Vu ...

最新文章

  1. ATOM中MARKDOWN的使用小结
  2. 2款不同样式的CSS3 Loading加载动画 附源码
  3. MVC 源码系列之路由(一)
  4. Kafka为什么速度那么快?
  5. windows下解决pip安装出错问题
  6. apache 配置 中英
  7. 2019.1.15 作业
  8. linux windows下重启oracle
  9. Java神鬼莫测之MyBatis多表操作延迟加载(四)
  10. 邻接矩阵(图的存储)
  11. 《卡巴斯基全系列》大客户key31个,有了它不用到处找卡巴斯基的KEY了!
  12. xgboost的plot_importance绘图时出现的f0、f1、f2、f3、f4、f5等改为对应特征的字段名
  13. 社会网络分析——Social Network Analysis
  14. 从团队协作与主题解读ios主流思维导图软件
  15. AI写小说!ChatGPT创作福尔摩斯小说,3分钟狂写856字,就问人类慌不慌?
  16. 游戏输入控制利器:DirectInput
  17. C++编程——常函数与常对象
  18. CPCI-S检索(原ISTP)、CPCI-SSH源期刊检索简介
  19. 计算机系统的性能评价
  20. 七个高度有效的媒体查询技巧

热门文章

  1. WTEM-1Q/GPS瞬变电磁仪操作步骤
  2. 如何建立NVivo的人际关系?
  3. 敬业签手机便签App怎么解绑微信互联设置?
  4. 工作感受月记 201906月
  5. 如何从官网直接下载iTunes?
  6. 【深度学习笔记(九)】之物体的分类与定位
  7. 微信屏蔽网站域名,提示“已停止访问该网页”该怎么办?
  8. 企业微信群:机器人定时提醒功能数据库配置化
  9. 计算机保研QA——by chx
  10. 华为云服务器最新信息,查询云主机信息