单元测试 phpUnit
phpUnit简介
- 用于检验php的模块,用来测试代码运行结果和预期是否一致,不一致则报错
- phpUnit功能
- 可以利用命令进行测试
- 可以测试性能
- 测试代码覆盖率(行覆盖率,条件覆盖率等)https://www.cnblogs.com/coderzh/archive/2009/03/29/1424344.html
- 自动化更新测试使用的参数
- 各种格式的日志
phpUnit的安装
- PHPUnit6.5需要php7+
- 需要json和xml扩展,pcre、refletion、spl扩展
- 代码覆盖率报告还需要xdebug2.5以上和tokenizer扩展,如果要生成xml格式的报告,需要xmlwriter扩展
- composer require --dev phpunit/phpunit ^7.0 //安装7.0以上的phpunit
- phpstrom配置
- settings test framework中测试 添加一个remote,为服务器的 ,右侧选择cli interpreter,然后phpUnit library选择phpunit.phar, 选择正确会显示出phpunit的版本
编写phpUnit测试
- 测试类名为xxTest, 例如测试Cat类,则测试类为CatTest
- 测试类一般继承自PHPUnit\Framework\TestCase
- 测试都是命名为test开头的公共方法,也可以在注释中使用@test标记其为测试方法
- 在测试方法内,类似于assertEquals这样的断言
- 断言:用于判断期望值与实际值的
断言
- 断言用于调试,表示为一些布尔表达
- 断言两种形式
- assert Expression
- assert expression1 expression2
- expression1 为buol表达式
- expression2 为失败时返回的错误信息
- 底层使用静态方法实现,调用的时候可以使用$this->assertTrue() 或者 self::assertTrue()都可以
- 测试方法里面一定要有断言
//assertCount 统计数据数量 assertNotCount
$this->assertCount(0,[1],'数量错误'); //参数一为期待得到的数量。参数二为要判断的数据,参数三为出错的信息
// assertEquals() 断言两个值相等
http://www.phpunit.cn/manual/current/zh_cn/phpunit-book.html
//assertEmpty
public function testEmpty(){
$this->assertEmpty(['a']);
}
测试依赖关系
- 依赖关系显示声明,允许在生产者返回一个测试基镜的实例,并将这个实例传递给消费者
- 生产者:生成被测单元并返回的测试方法
- 消费者,依赖一个或者多个生产者的测试方法
使用@depends来表达测试方法之间的依赖关系,消费者依赖生产者,并用参数形式接收到生产者的值
/**
* 生產者
*/
public function testGen()
{
$a = [];
$this->assertEmpty($a);
return $a;
}
/**
* 消费者 依赖生产者,通过参数传递
* @depends testGen
* @param $gen
*/
public function testCu($gen)
{
array_push($gen,1);
$this->assertNotEmpty($gen);
}
- 如果需要多个依赖,就加多个@depends标注
数据供给器
- 使用@dataProvider来标识使用哪个数据攻供给器
- 供给器方法必须声明为public,返回值应为数组,子元素也为数组,或者iterator接口对象 对她进行迭代时每步生成一个数组,数组的每一列对应参数列表,每一行每次调用取一行
- 测试同时包含dataprovider方法和depends接收数据时,供给器参数优先于依赖
/**
* @test
* @return string
*/
public function dep()
{
$this->assertTrue(true);
return 'first';
}
public function depData(){
return [
['pro1'],
['pro2'],
];
}
/**
* 数据供给器和依赖
* @dataProvider depData
* @depends dep
*/
public function con1(){
$this->assertEquals(['pro1','first'],func_get_args()); // 会出错
}
异常测试
- 使用@expectException标注测试代码是否抛出异常
- expectException
- expectExceptionCode
- expectExceptionMessage
- expectExceptionMessageRegExp
- 9中@已废弃,使用$this->expectException();
对错误进行测试
- phpunit会将触发的php错误,警告,通知转换位异常
对输出进行测试
- expectOutputString来设定预期的输出,echo等输出内容测试
- expectOutputRegex
- expectCallback(callback) //实际输出规范化,回调函数
- getActualOutput() //获取实际输出
错误信息输出
- phpstrom中会出现click to see diff
命令行测试执行器
- 将phpUnit/bin/phpunit放入到环境变量的系统变量的path中
- ln -s 绝对路径 /bin/usr/phpunit
- phpunit --version 查看版本
- phpunit xxx.php 可以直接执行test类
- phpunit命令行会输出一个字符来进行展示
- .测试成功时输出
- F 一个断言失败时输出
- E 产生一个错误时输出
- R 被标记为风险时输出
- S 测试跳过时输出
- I 测试不完整或未实现输出
基镜
- 编写代码来讲整个场景设置成某个已知的状态,测试结束后将其复原到出事状态,这种已知状态被称为基镜
- 在运行某个测试方法钱,会调用一个setUp方法,用来创建测试所用的对象,在测试方法运行结束后,不管成功还是失败,都会调用一个名叫tearDown的模板方法清理所有的测试对象
共享基镜
- 多个测试都依赖某一个基镜,例如,数据库连接
- setUpBeforeClass()和tearDownAfterClass() 方法来分别在测试用例类的第一个测试之前和最后一个测试之后连接和断开数据库
<?php
use PHPUnit\Framework\TestCase;
/**
* 共享基镜测试 ,整体的执行流程
* Class TempTest
*/
class TempTest extends TestCase
{
/**
* 第一次执行测试方法之前执行
*/
public static function setUpBeforeClass(): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
}
/**
* 每次执行测试方法时会执行
*/
protected function setUp(): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
}
/**
* 用于做测试前的处理
*/
protected function assertPreConditions(): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
}
//测试方法
public function testOne()
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
$this->assertTrue(true);
}
public function testTwo()
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
$this->assertTrue(true);
}
/**
* 测试后执行的方法
*/
protected function assertPostConditions(): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
}
/**
* 每次执行测试方法结束都调用
*/
protected function tearDown(): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
}
/**
* 最后一个测试方法执行后执行
*/
public static function tearDownAfterClass(): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
}
/**
* 没有测试成功执行的方法
* @param Throwable $t
* @throws Throwable
*/
protected function onNotSuccessfulTest(Throwable $t): void
{
fwrite(STDOUT,__METHOD__.PHP_EOL);
//throw $t;
}
}
测试组合
- 对几个测试类里的方法进行随机的排列组合
利用文件系统来编排测试套件
// 例如controller目录
Currency.php
IntlFormatter.php
Money.php
autoload.php
//tests目录
CurrencyTest.php
IntlFormatterTest.php
MoneyTest.php
//利用composer进行加载
{
"require": {
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"app\\": "app" // 命名空间对应的目录配置好 然后执行composer dump-autoload
}
}
}
利用xml进行测试配置
phpstrom setting language && framework test frameworks default configuration file 选择配置的xml文件
// phpunit.xml
<?xml version="1.0" encoding="UTF-8" ?>
<phpunit bootstrap="vendor/autoload.php"> <!--自动加载路径-->
<testsuites>
<testsuite name="money">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
phpunit --bootstrap vendor/autoload.php Test 对目录下所有的测试文件执行
未完成测试与跳过测试
- 标识测试未完成
- $this->markTestIncomplete('此测试未完成');
- extension_loaded('mysql') 判断是否有扩展
- 标记跳过 $this->markTestSkipped()
- 使用@require 标记跳过
- @requires php7 版本
- @requires phpunit7
- @requires OS WIN32
- @requires extension redis 1.1
- @requires function ReflectionMethod::setAccessible
protected function setUp() {
if(!extension_loaded("mysql")) {
$this->markTestSkipped('xxx'); // 如果没有扩展就跳过测试,并输出xxx
}
}
测试替身
实际测试时无法使用实际依赖的组件,可以用测试替身来代替
- createMock($type)
- getMockBuilder($type)
- 实现测试替身的方式叫做上桩,测试替身又称为桩件
<?php
use PHPUnit\Framework\TestCase;
class stubTest extends TestCase {
public function testStub() {
//创建桩件
$stub = $this->createMock(\app\src\src::class);
//调用桩件的方法,willReturn代表do方法的返回值, 相当于利用123代替了原有的do方法
$stub->method('do')->willReturn('123'); // 构建替身
// 如果原有方法里有method方法 $stub->expects($this->>any())->method('do')->willReturn('123');
//$stub->do() 调用替身的do
$this->assertEquals($stub->do(),'123');
}
/**
* 桩件的返回时参数之一
*/
public function testStub1(){
$stub = $this->createMock(\app\src\src::class);
//调用桩件的方法,willReturn代表do方法的返回值, 相当于利用123代替了原有的do方法
$stub->method('do')->will($this->returnArgument(0));
//$stub->do() 调用替身的do
$this->assertEquals($stub->do('123'),'123');
}
/**
* 对某个方法调用上庄
*/
public function testStub2(){
$stub = $this->createMock(\app\src\src::class);
//调用桩件的方法,willReturn代表do方法的返回值, 相当于利用123代替了原有的do方法
$stub->method('do')->will($this->returnSelf()); // 调用do,返回自身
//$stub->do() 调用替身的do
$this->assertEquals($stub->do(),$stub);
}
/**
* 按照映射返回值
*/
public function testStub3(){
$stub = $this->createMock(\app\src\src::class);
//创建从参数到返回值的映射
$arr = [
['a','b','c'],
['x','x']
];
//调用桩件的方法,willReturn代表do方法的返回值, 相当于利用123代替了原有的do方法
$stub->method('do')->will($this->returnValueMap($arr)); // 调用do,返回自身
//$stub->do() 调用替身的do
$this->assertEquals($stub->do('a','b'),'c'); //只能映射返回最后一个值
$this->assertEquals($stub->do('a','c'),'b'); //cuowu
}
}
仿件对象
将对象替换为能验证预期行为(例如断言某个方法必会被调用)的测试替身称为模仿
- 仿件对象包含桩件对象的内容
- 匹配器
//创建subject 和 observe类当做被测试对象
<?php
use PHPUnit\Framework\TestCase;
use app\src\Observer;
/**
*
* Class SubjectTest
*/
class SubjectTest extends TestCase {
// 测试某个方法以某个参数调用,测试参数一定
public function testObserverAreUpdated() {
//$this->createMock() 桩件创建
// 访件对象, 模仿update方法
$o = $this->getMockBuilder(Observer::class)->setMethods(['update'])->getMock();
$o->expects($this->once()) // 调用一次update方法
->method('update')
->with($this->equalTo('something')); // 用something 作为参数,subject类里调用notify本身就使用的something作为参数,这里为断言参数是否一致
// 创建subject对象
$subject = new \app\src\Subject('sub');
// 这里是为了测试notify方法,notify方法依赖于attack创建observer,notify最后嗲用的是update方法,然后仿造了一个update方法
$subject->attach($o); // $o 即为自己创建的一个observer对象
$subject->doSomething();
}
// 测试参数数量, 并且对参数的进行各种限制 利用Observer的reportError方法测试
public function testArgNum(){
$o = $this->getMockBuilder(Observer::class)->setMethods(['reportError'])->getMock();
$o->expects($this->once())
->method('reportError')
->with($this->greaterThan(0),$this->stringContains('something'),$this->anything()); // with用于对参数惊醒校验 anything,除了something还有其余的内容
$subject = new \app\src\Subject('sub');
// 这里是为了测试notify方法,notify方法依赖于attack创建observer,notify最后嗲用的是update方法,然后仿造了一个update方法
$subject->attach($o); // $o 即为自己创建的一个observer对象
$subject->doSomethingBad();
}
}
对trait和抽象对象测试
- getMockForTrait() 模仿Trait类
- 返回一个使用了trait定义的类的对象
- getMockForAbstractClass()
- 返回抽象类的对象
<?php
use PHPUnit\Framework\TestCase;
use app\src\AbstractTrait;
class TraitTest extends TestCase
{
/* $this->getMockForTrait(string $traitName, array $arguments = [], string $mockClassName = '', bool $callOriginalConstructor = true, bool $callOriginalClone = true, bool $callAutoload = true, array $mockedMethods = [], bool $cloneArguments = false): \PHPUnit\Framework\MockObject\MockObject
{
return parent::getMockForTrait($traitName, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); // TODO: Change the autogenerated stub
}*/
public function testConMethod()
{
$mock = $this->getMockForTrait(AbstractTrait::class);
$mock->expects($this->any())
->method('abstractMethod') // 实现abstractMethod 方法
->will($this->returnValue(true)); // 给抽象方法abstractMethod设置返回值为true
$this->assertTrue($mock->conMethod());
}
}
网友评论