phpunit光速入门
phpunit 的官方文档对如何使用 phpunit 进行了详细的说明。本人在通读文档后进行了一些概要提升,
同时摘录了一些示例 phpunit-demo,便于以后
理解和查阅。
文档较为简洁,但是也涵盖了平时使用的基本用法,适合入门使用。
安装 phpunit
项目安装
composer require --dev phpunit/phpunit
使用 ./vendor/bin/phpunit
全局安装
composer global require --dev phpunit/phpunit
使用 phpunit
快速入门
基本格式
- 测试类命名:
类名 + Test
, egFooClassTest
- 测试方法命名:
test + 方法名
, egtestFoo
也可以使用注释
@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
基境
基境就是在测试前需要准备的一系列东西。
比如有的测试需要依赖数据库的数据,那么在测试类运作前需要进行数据的准备。
主要有两个函数 setUp
和 tearDown
。
那为什么不直接用构造函数和析构函数呢?是因为这两个有他用,当然你可可以直接用构造函数,然后
再执行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);}
}
其中 getConnection
和 getDataSet
都是 TestCaseTrait
中提供的方法,
我们在 getConnection
中设定数据库的链接动作,同时在 getDataSet
中设定
需要往数据库中写入的数据,注意,每次执行这个测试类时,都会执行
- 清空数据库 TRUNCATE
- 填充数据
如何验证呢?加一个字段为 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.}
}
我们看到, SomeClass
的 doSomething()
本身没有返回数据,我们通过桩动作,来完成了测试。
这个在测试依赖于第三方服务的相关代码时很管用。
代码覆盖度
白名单文件(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光速入门相关推荐
- [MySQL光速入门]012 作业解答
实训内容二 使用算数运算符-, 查询最低借阅量与最高借阅量的差值 select max(borrowsum)-min(borrowsum) as 差值 from book; 复制代码 使用比较运算符( ...
- Spring - Bean注解配置光速入门
Bean注解配置光速入门 步骤一: 创建 web 项目,引入 Spring 的开发包 在 Spring 的注解的 AOP 中需要引入 spring-aop 的 jar 包 步骤二: 引入相关配置文件 ...
- 光速入门MyBatis-Plus
光速入门MyBatis-Plus 0.特性 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 强大的 CRU ...
- [MySQL光速入门]020 事务
TCL 事务控制语言 SQL的四种语言 DDL(Data Definition Language)数据库定义语言 建表, 建库, 修改表结构 DML(Data Manipulation Languag ...
- 光速入门Docker 和 Kubernetes,一起学~
只需要每天晚上花三两个小时,在一周业余的时间里,你就能快速入门Docker和Kubernetes!! 第一期云原生在线技术工坊已经圆满结束,好评如潮,下面是部分参与者打卡截图: 第二期技术工坊活动再度 ...
- 阿里云 icp 备案流程(光速入门篇)
文章标题 阿里云icp备案光速入门篇 文档目标 帮助自媒体老师光速完成icp备案 预计阅读时长 10min~15min 首次编写日期 2020-10-21 最近更新日期 2020-10-23 编撰作者 ...
- p5.js 光速入门中文教程
本文简介 点赞 + 关注 + 收藏 = 学会了 本文的目标是和各位工友一起有序的快速上手 p5.js ,会讲解 p5.js 的基础用法. 本文会涉及到的内容包括: 项目搭建 p5.js 基础2D图形 ...
- numpy 光速入门示例
基于notebook 文章目录 光速入门示例 矩阵创建 矩阵输出 运算 通用函数 索引,切片及循环 改变矩阵形状 堆叠 拆分 副本和视图 函数和方法概述 高级索引技巧详见我另一篇blog 光速入门示例 ...
- 光速入门消息队列Kafka
文章目录 光速入门消息队列Kafka 消息队列 知识要点 2.1 背景.问题的产生 2.2 消息队列应运而生 2.3 消息队列的特点 认识kafka 知识要点 2.1 认识kafka 2.2 kafk ...
- typescript光速入门
文章目录 运行环境 数据类型 函数 面向对象 TypeScript(后称ts)是微软开发的开源编程语言,是JavaScript(后称js)的超集并可以转译成纯的js,对React.Angular.Vu ...
最新文章
- ATOM中MARKDOWN的使用小结
- 2款不同样式的CSS3 Loading加载动画 附源码
- MVC 源码系列之路由(一)
- Kafka为什么速度那么快?
- windows下解决pip安装出错问题
- apache 配置 中英
- 2019.1.15 作业
- linux windows下重启oracle
- Java神鬼莫测之MyBatis多表操作延迟加载(四)
- 邻接矩阵(图的存储)
- 《卡巴斯基全系列》大客户key31个,有了它不用到处找卡巴斯基的KEY了!
- xgboost的plot_importance绘图时出现的f0、f1、f2、f3、f4、f5等改为对应特征的字段名
- 社会网络分析——Social Network Analysis
- 从团队协作与主题解读ios主流思维导图软件
- AI写小说!ChatGPT创作福尔摩斯小说,3分钟狂写856字,就问人类慌不慌?
- 游戏输入控制利器:DirectInput
- C++编程——常函数与常对象
- CPCI-S检索(原ISTP)、CPCI-SSH源期刊检索简介
- 计算机系统的性能评价
- 七个高度有效的媒体查询技巧