PHPUnit踩坑实录

版本信息 说明:现有项目使用php7.2, 只能支持phpunit8.5, 不支持部分新特性

php 7.4.30 phpunit 9.5.21 mockery 1.5.0 日常开发 测试类继承 PHPUnit\Framework\TestCase use PHPUnit\Framework\TestCase;

class BackBlackServiceTest extends TestCase{}

测试方法 public function testBackBlackService($data, $expect), 需是public, 以test开头

多用例可使用注解 @dataProvider dataProvider dataProvider 方法需为public, 参数个数,类型可按需自定义

1
建议$data按表格式组装数据,  mock方法,指向预期值 [’myFunc‘ => 'myFuncExpectData']

public function serviceProvider() {

1
2
3
4
5
6
    return [
      '测试用例名1'=>[$data,$expect],

      '测试用例名2'=>[$data,$expect]

    ];

}

断言时可增加用例名作为报错message

p u b l i c f u n c t i o n t e s t B a c k B l a c k S e r v i c e ( ) {

this−>assertEquals(this->assertEquals(this−>assertEquals(expected, $actual, $this->getName());

}

常用技巧 对象mock(原则上需要mock的类为操作db,调用第三方服务的类) 可注入对象使用一般方法 \Mockery::mock(MyClass::class)

1
2
3
4
5
6
   无需注入(基于代码现状, 不改动现有代码)
           \Mockery::mock("overload:" . MyClass::class);

           \Mockery::mock("alias:" . MyClass::class);         静态方法

   使用overload:  alias:  需配合以下注解使用
  • @runInSeparateProcess
  • @runTestsInSeparateProcesses
  • @preserveGlobalState disabled

使用shouldReceive定义mock方法,返回值 \Mockery::mock(“alias:” . MyClass::class)->shouldReceive(‘myFunc’)->andReturn($data[‘myFunc’] ?? []);

replyValidation−>shouldReceive(′valiate′)−>andThrow(newValidException(replyValidation->shouldReceive(‘valiate’)->andThrow(new ValidException(replyValidation−>shouldReceive(′valiate′)−>andThrow(newValidException(data[‘validate’])); //需要抛异常

多用例时需增加Mockery::close()方法 public function tearDown(): void { parent::tearDown(); Mockery::close(); }

特殊技巧 链式操作 person=ResumeService::module(′base′)−>findbyuserid((int)person = ResumeService::module(‘base’)->find_by_user_id((int)person=ResumeService::module(′base′)−>findb​yu​seri​d((int)userId); $resume_service->shouldReceive(‘module->find_by_user_id’) 同一个方法调用多次 参数区分 withAnyArgs() 任意传参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
      按传参返回 with(...$args)     \Mockery::spy('alias:' . Config::class)->shouldReceive('getConfigItem')->with("a")->andReturn($data1);

次数区分
    atLeast()->times(2) 最少调用次数

    atMost()->times(2)  最多调用次数

    times(3)  明确调用次数,多了, 少了都不行。

   \Mockery::spy('alias:' . Config::class)->shouldReceive('getConfigItem')->atLeast()->times(2)->andReturn($data1, $data2);

静态常量 使用“alias:” mock的类无法访问该类的常量,静态变量

使用自定义类,方式注入

class ConstMap { public const CONTRACT_RESUME = ‘View’; //简 public static $remain_field = ‘SurplusNum’;

}

\Mockery::mock(‘alias:’ . ContractService::class, ConstMap::class);

魔术方法 类里面未定义属性,使用魔术方法__get($name) 访问

\Mockery::mock(‘overload:’ . CompanyComponent::class)->shouldReceive(’__construct’)->andSet(‘company_name’, ‘公司名’);

代码注解使用 通过phpunit.xml 定义,测试用例常量 如 inPhpUnit 跳过测试用例无法覆盖代码, 如register_shutdown_function !defined(‘inPhpUnit’) && register_shutdown_function(function(){});

使用注解跳过相关代码块覆盖 // @codeCoverageIgnoreStart

/ @ c o d e C o v e r a g e I g n o r e E n d

phpunit.xml phpunit.xml (phpunit9) 关注参数 forceCoversAnnotation=“false”

1
<coverage></coverage>   覆盖源文件配置

xml version=“1.0” encoding=“UTF-8”?

src

tests

phpunit.xml (phpunit8.5) 关注参数 forceCoversAnnotation=“false”

1
<whitelist></whitelist>   覆盖源文件配置

xml version=“1.0” encoding=“UTF-8”?

tests

src

IDE支持 点选phpunit.xml, 或者指定用例, 更多运行/调试,使用覆盖率运行

命令行 全部覆盖率

1
2
3
4
5
 php -dxdebug.mode=coverage /Users/******/composer/vendor/phpunit/phpunit/phpunit  --configuration phpunit.xml

 指定测试

 php -dxdebug.mode=coverage /Users/******/composer/vendor/phpunit/phpunit/phpunit  --configuration phpunit.xml tests/unit/Service/Resume/KickFavoriteServiceTest.php

开发建议 db层不封装业务逻辑

1
2
3
对象实体能以对象形式代替数组形式返回, 便于模拟数据。

数据层,第三方服务支持注入,简化mock难度

相关文档 https://phpunit.readthedocs.io/en/9.5/

https://phpunit.readthedocs.io/zh_CN/latest/ (中文文档相对滞后)

http://docs.mockery.io/en/latest/index.html

下一篇

### 阿里云盘 挂载本地挂载