在本文中, 我们将看到如何使用测试驱动开发(TDD), 本文稍后将深入解释)有助于编写可维护的高质量代码, reusable, 并且在发生错误时易于调试和修复.
PHP和测试
PHP和测试一开始并不是朋友. 我们都知道,其他语言从一开始就更倾向于基于测试套件创建代码, 但是PHP是为快速开发而设计的. 这就是为什么测试不是PHP编码人员日常工作的一部分.
但是时代在变, 现在PHP是一种成熟的语言,可以用来构建伟大的大型应用程序, 如果您想在最后节省时间并构建高质量的代码,那么使用测试驱动的开发方法是一种可行的方法.
What Is TDD?
TDD stands for 测试驱动开发 . 基本上这意味着:先写测试用例,然后再写代码. 首先,您的测试将失败,因为在这种情况下代码将为空,这很好. 您在这里的任务是通过构建代码使测试通过. 这使您可以将您的编码过程集中在传递描述良好的规范上, 而不是在编码的时候把这些都记在脑子里(当你需要在压力下工作或你累了的时候).
更具体地说,TDD背后的模式可以用以下流程图来总结:
所以我总结一下:
编写测试用例
为测试代码编写代码
Test
修复和重构
移动到下一个测试用例
对于那些跟随我的人 agile approach ,这听起来很相似. 是的,因为TDD适合敏捷开发.
Of course, 事情可能会出错, 但是如果您后来发现了一个bug,您就必须编写另一个测试用例, 很明显你的套房不见了. 这样做的好处是,在编写新功能时, 他们还必须通过目前的自动化测试.
Unit Testing
好的,我们知道TDD是什么意思,但是单元测试呢? 单元测试意味着将代码的一部分与其他部分隔离开来进行测试. For that, 您需要改进代码,以便能够单独测试每个组件, 不受其他部件的干扰.
这就是为什么TDD非常适合开发, 因为它迫使开发人员在众所周知的方法中隔离不同的信息片段,这些方法可以单独测试以通过测试.
不难想象,用使用的代码实现TDD会很困难 God objects ,或者在一个函数或方法中包含太多的关注点. 例如,处理XML文件并将数据导入数据库的函数. 你可以把它分成几个函数, 处理XML的程序, 另一个用于获取映射到数据库的结果, 另一个用于保存和检查错误. 如果你用一种方法完成所有的任务,那么就很难测试这种方法是否正确地完成了任务. 作为一般规则,每个方法应该只做一件事,并且做得正确.
这就是你如何进行单元测试, 通过创建小而定义良好的方法,并有明确的任务来实现, test, 然后证明是对还是错.
依赖注入
所有这些听起来都很棒, 但是现在,当您需要为测试创建代码时, 你发现你的函数依赖于不同的类, 这些类被实例化到函数中. 你该怎么办?? 你怎么知道问题不在那些课上? 您是否应该为您正在使用的每个库编写一个测试套件?
不,尽管您现在可能想使用带有测试套件的库,不是吗?
这个的解是 依赖注入 的模式 控制反转 . 所有这些花哨的词语都可以用一个例子来解释,所以让我们以下面的函数为例:
类公司{
公共成员美元;
addMember($name, $position)
{
$member = new member ($name, $position);
$this->members[] = $member;
}
}
在本例中,调用方法 addMember
意味着必须在函数内部创建成员对象. 所以当我们测试这个方法时,我们不仅要测试成员是否被添加到 $members
数组,而且构造函数按预期工作. 不是很单位,对吧??
解决这个问题的方法是在方法调用中注入依赖项:
类公司{
公共成员美元;
addMember(Member $ Member)
{
$this->members[] = $member;
}
}
现在我们只需要测试数组是否在添加成员. Having a valid $member
实例不是我们的问题. 所以我们测试这段单独的代码. 这就是单元测试.
在测试用例中,我们可以模拟 $member
争论并传递下去. 但是,有一些框架,比如 Laravel 通过创建动态解析依赖项的方法解决了这个问题, 而不需要在参数中传递它. 当然,这在注入服务提供者时比我们上面的例子更有用, 但是想想这个例子:
类公司{
sendNotificationEmail()
{
//创建$html发送
$mail = new Mailer();
$mail->send($html);
}
}
这里有一个全新的问题,即在方法内部创建mailer函数. 你应该做的是:
类公司{
sendNotificationEmail(Mailer $mail)
{
//创建$html发送
$mail->send($html);
}
}
使用服务提供商自动解析自己,你只需要做这个调用:
$company =新公司;
$company->sendNotificationEmail();
这个系统就能做到这一点. 顺便说一下,很好看的语义代码?
引入PHPUnit)
PHPUnit 正如您可以通过名称巧妙地推断出来的那样,它是一个用于为我们的代码创建单元测试的库. 这已经包含在一些框架中, 到目前为止,它是PHP中最常用的测试库.
本文的目的是提供改进PHP代码的技巧, 所以我不会详细介绍如何使用PHPUnit. 你可以去他们的详细资料 documentation .
如果你看完这篇文章, 您知道应该编写独立的代码, 这些代码被称为可测试的.
让我们以上面定义的Company示例为例,并为它编写一个测试. 为此,您必须在我们的 phpunit.xml
在我们的项目的根目录中,在我们安装它之后. 我建议使用Composer安装,但您可以下载代码并根据需要包含它. 因此,为了测试add成员函数,我们创建了以下测试:
类CompanyTest扩展PHPUnit_Framework_TestCase
{
/**
* @dataProvider memberProvider
*/
公共函数testAddMember($company, $member, $expected)
{
// Act
$company->add($member);
// Assert
$this->assertEquals(count($company->members), $expected);
}
函数memberProvider()
{
$company =新公司;
return [
[$company,新成员,1],
[$company,新成员,2],
[$company,新成员,3],
];
}
}
In this case, 我们正在生成带有数据提供者的测试用例,该数据提供者将返回一些信息和预期的结果. 在我们的测试用例中,我们使用这个函数 assertEquals
测试代码是否按预期工作. We have a 有很多断言可供我们使用 .
使用PHPUnit,我们可以做很多事情:我们可以模拟对象, databases, 创建数据提供者, 创建测试依赖项, 测试异常, output, and a lot more. 我们还可以通过调用端点来测试REST api. Check the 完整的文档 to learn more.
最后的建议
我对使用TDD的建议是尽可能地使用它. 大多数从头开始的项目都适合TDD方法, 因为你可以随心所欲地创建它们.
However, 代码混乱的项目,或者没有考虑我们在本文中讨论的概念而构建的项目,或者需要在生产环境中快速纠正的项目,并不总是适合的. 在这种情况下,我的建议是使用TDD来重构代码片段,并按部分改进代码. 在大多数情况下,获取所有代码并重新构建所有内容并不是一种选择.
TDD迫使您思考如何构建每个功能,这很好. 随着时间的推移,你会发现这项任务更容易完成, 你的代码将非常容易更新和重构.