浅谈如何提高PHP代码质量之单元测试


Posted in PHP onMay 28, 2021

1、单元测试

通过实现单一责任原则(我们的代码应该只关注功能的单个部分),我们将确保在测试期间,我们只会同时关注项目的一小部分

通过使用 Liskov 替换原则和依赖倒置原则,我们的代码不会关心我们是否注入模拟依赖关系,只要它们实现了适当的接口

在单元测试中,我们确实希望用模拟对象替换所有依赖的服务,因此我们一次只测试一个类。但模拟是什么?它们是实现与其他对象相同的接口的对象,但它们的行为是受控的。例如,假设我们在创建一个价格比较服务,我们利用另一个服务来获取当前的汇率。在测试我们的比较器时,我们可以使用一个模拟对象来为特定的货币返回特定的汇率,因此我们的测试既不依赖也不调用真正的服务。

2、应该使用哪个框架?

有几个好的框架可以达到这个目的。最常见的可能是 PHPUnit。在我的工作中,我发现使用行为方法来编写测试会带来更好的结果,并使我更急切地编写测试。对于我们的项目,我们选择 phpspec。

安装过程相当简单 - 只需使用:

$ php composer.phar require --dev phpspec/phpspec

然后,如果你在本文的第一部分中配置了 PHing,那么你可以在 build.xml 中添加构建目标:

<target name="phpspec">
	<exec executable="bin/phpspec" passthru="true" checkreturn="true">
		<arg line="run --format=pretty" />
	</exec>
</target>...
<target name="run" depends="phpcs,phpcpd,phan,phpspec" />

然后,你必须为你想要测试的每个服务类创建一个测试类。让 PHPSpec 非常容易使用的是模型创建。你只需使用严格的输入,就可以将模拟对象声明为测试函数的参数。PHPSpec 会自动为你创建模拟。让我们看一下代码示例:

//spec/Domain/PriceComparatorSpec.php
<?php
namespace spec\Domain;
use Domain\Price;use Domain\PriceConverter;
use PhpSpec\ObjectBehavior;
class PriceComparatorSpec extends ObjectBehavior{
	public function let(PriceConverter $converter)    {       
		$this->beConstructedWith($converter);   
	} 
	public function it_should_return_equal()    {   
		$price1 = new Price(100, 'EUR');     
		$price2 = new Price(100, 'EUR');   
		$this->compare($price1, $price2)->shouldReturn(0);
	}    
	public function it_should_convert_first(PriceConverter $converter) {   
		$price1 = new Price(100, 'EUR');    
		$price2 = new Price(100, 'PLN');    
		$priceConverted = new Price(25, 'EUR');    
		$converter->convert($price2, 'EUR')->willReturn($priceConverted);  
		$this->compare($price1, $price2)->shouldReturn(1); 
	}
}

这里有三个函数:

  • let( ) - 它允许使用依赖来初始化服务
  • 两个 it_* 函数实现测试。其中一种方法是使用模拟 $priceConverter 的方法实现 priceConverter 接口,该接口被注入到测试对象的创建中。

你可以看到创建模拟非常容易。你所需要做的就是将它定义为测试函数的参数,并通过指定在执行代码时应该运行哪些函数来配置 mock。如果需要,你还可以设置返回值。

所有测试的方法都是从 $this 上下文中运行的,你可以使用与模拟相同的语法来轻松地检查它们的结果。

3、如何设置测试?

Phpspec 有一个很好的文档,但是我将尝试向你展示一些在日常实践中有用的基本用例。

构建测试对象

一般来说,设置测试对象的最简单方法是调用 $this->beConstructedWith(…) 方法,该方法将所有应该传递给对象构造函数的 params 作为参数。

如果你的对象应该使用工厂方法来创建,那么你可以使用

this−>beConstructedThrough(this−>beConstructedThrough(methodName,$argumentsArray)方法。

在模拟中匹配运行时参数

你会发现 phpspec 使用一种非常类似于人类的语法来配置模拟。例如,如果你想要检查在运行时是否有一个模拟方法 someMethod 与参数“desired value”被调用,你可以在测试中定义它,如下面的例子:

$mockObject->someMethod("desired value")->shouldBeCalled();

如果你想要测试代码的行为,当一些 mock 的函数返回“some value”时,你可以通过调用来轻松地设置它:

$mockObject->someFunction("some input")->willReturn("some value");

有时我们并不真正关心传递给 mock 的确切参数。然后可以写这段代码:

use Prophecy\Argument\Token\AnyValueToken;
$mockObject->someFunction(new AnyValueToken())->willReturn(true);

有时你会关心一些参数,最好是写一个检查函数,它会告诉你是否正确地调用了一些方法,例如:

use Prophecy\Argument\Token\CallbackToken;
$checker = function (Message $message) use ($to, $text) {  
	return $message->to === $to && $message->text === $text;
};
$msgSender->send(new CallbackToken($messageChecker))->shouldBeCalled()

匹配运行时异常

。在某些情况下,异常是代码接口的一部分。你希望它们在特定的场景被抛出。你可以通过编写以下代码来完成这项工作:

$this->shouldThrow(\DomainException::class)->during('execute', [$command, $responder]);

传给 during() 的第一个参数是将要调用的方法的名称,第二个参数是将传递给我们的方法的参数数组。

4、在哪里可以找到更多的例子?

在本文中,我们只介绍了一些基本的用例。请参考 phpspec 的文档,以找到更多的示例,这些示例将使你的测试代码变得漂亮!

代码覆盖率

PHPSpec 附带了扩展子系统,它允许例如创建代码覆盖率报告。如果您想要检查在测试中执行了多少代码,它们是很有帮助的。

你可以通过以下来安装这个扩展:

$ php composer.phar require --dev leanphp/phpspec-code-coverage

然后通过创建 phpspec 来启用它。yml 文件内容:

1 extensions: LeanPHP\PhpSpec\CodeCoverage\CodeCoverageExtension: ~

默认情况下,这个扩展会使用 PHP 的 Xdebug 扩展生成代码覆盖率信息,但是 PHP 的本机调试器 - phpdbg 会更快速一些:

$ phpdbg -qrr phpspec run

现在,你可以在 build 中更改 phpspec 的构建目标。xml:

<target name="phpspec">
	<exec executable="phpdbg" passthru="true" checkreturn="true">
		<arg line="-qrr bin/phpspec run --format=pretty" />
	</exec>
</target>...
<target name="run" depends="phpcs,phpcpd,phan,phpspec" />

报告在覆盖率 / 目录中生成,作为漂亮的 HTML 页面,可以浏览以检查测试覆盖率。

以上就是浅谈如何提高PHP代码质量之单元测试的详细内容,更多关于如何提高PHP代码质量之单元测试的资料请关注三水点靠木其它相关文章!

PHP 相关文章推荐
php学习之简单计算器实现代码
Jun 09 PHP
php中$_REQUEST、$_POST、$_GET的区别和联系小结
Nov 23 PHP
解析php中反射的应用
Jun 18 PHP
PHP随机生成随机个数的字母组合示例
Jan 14 PHP
ThinkPHP3.1新特性之字段合法性检测详解
Jun 19 PHP
php查询mssql出现乱码的解决方法
Dec 29 PHP
php获得客户端浏览器名称及版本的方法(基于ECShop函数)
Dec 23 PHP
PHP生成各种常见验证码和Ajax验证过程
Jan 10 PHP
php文件系统处理方法小结
May 23 PHP
PHP基于mssql扩展远程连接MSSQL的简单实现方法
Oct 08 PHP
详解配置 Apache 服务器支持 PHP 文件的解析
Feb 15 PHP
Laravel框架分页实现方法分析
Jun 12 PHP
浅谈如何提高PHP代码的质量
May 28 #PHP
详解thinkphp的Auth类认证
May 28 #PHP
如何理解PHP核心特性命名空间
May 28 #PHP
如何用Laravel包含你自己的帮助函数
May 27 #PHP
详解Laravel框架的依赖注入功能
May 27 #PHP
详解PHP Swoole与TCP三次握手
May 27 #PHP
如何用PHP实现分布算法之一致性哈希算法
You might like
使用php4加速网络传输
2006/10/09 PHP
php中使用$_REQUEST需要注意的一个问题
2013/05/02 PHP
Codeigniter生成Excel文档的简单方法
2014/06/12 PHP
使用php的HTTP请求的库Requests实现美女图片墙
2015/02/22 PHP
PHP registerXPathNamespace()函数讲解
2019/02/03 PHP
Gambit vs CL BO3 第二场 2.13
2021/03/10 DOTA
JS面向对象编程浅析
2011/08/28 Javascript
jquery星级插件、支持页面中多次使用
2012/03/25 Javascript
jQuery标签替换函数replaceWith()的使用例子
2014/08/28 Javascript
js实现字符串转日期格式的方法
2015/05/20 Javascript
AngularJS中实现动画效果的方法
2016/07/28 Javascript
详解vue.js+UEditor集成 [前后端分离项目]
2017/07/07 Javascript
Angular 组件之间的交互的示例代码
2018/03/24 Javascript
jQuery基于Ajax实现读取XML数据功能示例
2018/05/31 jQuery
利用es6 new.target来对模拟抽象类的方法
2019/05/10 Javascript
深入剖析JavaScript instanceof 运算符
2019/06/14 Javascript
Vue全局loading及错误提示的思路与实现
2019/08/09 Javascript
vue仿淘宝滑动验证码功能(样式模仿)
2019/12/10 Javascript
Vue如何基于es6导入外部js文件
2020/05/15 Javascript
[02:31]2014DOTA2国际邀请赛2009专访:干爹表现出乎意料 看好DK杀回决赛
2014/07/20 DOTA
[08:53]DOTA2-DPC中国联赛 正赛 PSG.LGD vs LBZS 选手采访
2021/03/11 DOTA
实例说明Python中比较运算符的使用
2015/05/13 Python
Django用户身份验证完成示例代码
2020/04/03 Python
解决运行django程序出错问题 'str'object has no attribute'_meta'
2020/07/15 Python
意大利奢侈品购物网站:Deliberti
2019/10/08 全球购物
工商管理本科毕业生求职信范文
2013/10/05 职场文书
结婚邀请函范文
2014/01/14 职场文书
热爱祖国演讲稿
2014/05/04 职场文书
护士找工作求职信
2014/07/02 职场文书
2014员工聘用协议书(最新版)
2014/11/24 职场文书
2015年班组建设工作总结
2015/05/13 职场文书
我是特种兵观后感
2015/06/11 职场文书
创业计划书之韩国烧烤店
2019/09/19 职场文书
根德5570型九灯四波段立体声收音机是电子管收音机的楷模 ? 再论5570
2022/04/05 无线电
Python爬虫 简单介绍一下Xpath及使用
2022/04/26 Python
MySQL索引失效十种场景与优化方案
2023/05/08 MySQL