arron/testit
PHPUnit 的插件,用于轻松模拟依赖关系并测试它们。
Requires
- php: >=7.0
- phpunit/phpunit: >=6.5 <8.0.0
Requires (Dev)
README
TestIt 是一个针对 PHPUnit 的插件库,它扩展了模拟引擎,并允许舒适地测试类与周围环境之间的交互。你厌倦了使用 $this->getMock()->expects()->method()->with()->will() 构造吗?TestIt 就是为了你而生的 :-)
常见的单元测试通常包含以下几个步骤
- 数据准备 - 准备测试期间所需的所有数据。输入数据、部分结果(从模拟依赖关系中返回)、预期结果等。
- 模拟依赖关系 - 创建“假”依赖关系,以便在测试期间不与真实环境交互
- 创建要测试的对象的新实例 - 对于每个测试,都需要一个新的(干净)实例
- 预期会发生什么 - 定义依赖关系调用的预期和行为(返回特定值、抛出异常等)
- 将测试实例置于定义的状态 - 起始条件对于测试的确定性很重要
- 在测试实例上调用方法(方法)
- 断言最终条件 - 断言所有预期都得到满足,所有结果都符合预期,并且测试实例处于预期的最终状态
TestIt 可以帮助你完成大多数步骤。
兼容性
- 版本 1.* 与 PHPUnit 3.7.* 兼容,需要 PHP 版本 5.3、5.4、5.5、5.6、7 或 HHVM。
- 版本 2.* 与 PHPUnit 4.* 兼容,需要 PHP 版本 5.3、5.4、5.5、5.6、7 或 HHVM。
- 版本 3.* 与 PHPUnit 5.* 兼容,需要 PHP 版本 5.6、7 或 HHVM。
- 版本 4.* 与 PHPUnit <6.5 兼容,需要 PHP 版本 7.0 或更高。
- 版本 5.* 与 PHPUnit >=6.5 和 <8.0.0 兼容,需要 PHP 版本 7.0 或更高。在 HHVM 上,存在 PHPUnit 6.5 的问题,因为它无法安装,因为 HHVM 无法满足 PHP 7.0 的要求。
在最新版本中,我们正在针对 PHP 版本 7.0、7.1、7.2 运行我们的单元测试。
请将任何错误报告到 GitHub。谢谢。
许可证
您可以在 MIT 许可证下使用 TestIt 库。
安装
使用 composer 安装 TestIt。只需将 arron/testit 项目添加为您的依赖项。
使用 TestIt 进行测试
TestIt 提供了自己的 TestCase 类,它继承自 \PHPUnit_Framework_TestCase 并增强了它的一些功能。它旨在成为您测试的基类。它帮助您创建类依赖项的模拟并验证这些依赖项是否按预期调用。要使用这些功能,只需从 \Arron\TestIt\TestCase 类继承您的测试基类即可。
从该库中受益的最佳方式是将它集成到您的测试中,这样您就可以更容易地使用所有这些功能。
有关示例,请参阅示例目录。
创建要测试的对象
在 \Arron\TestIt\TestCase 命名空间中定义了一个抽象方法 createTestObject()。它应该返回一个已创建的实例,用于测试。因此,createTestObject() 是进行任何初始化、模拟注入等的正确位置。它将在 setUp 调用期间被调用。因此,只需关注新对象的创建,其余的都由 TestIt 掌管吧 :-)。如果您需要在测试对象创建期间对任何依赖项调用任何期望,请覆盖 initializationExpectations 方法并在那里放置您的期望。此方法将在 setUp 方法中对象创建之前被调用。
依赖项模拟
TestIt 帮助您创建模拟类。它包含类似对象定位器的东西,因此它不会每次都创建新的模拟。但您可以通过将 TestIt 功能与您的依赖注入(DI)集成来获得最佳效果。
创建模拟类
您只需在测试类中调用简单方法即可创建模拟。
$mock = $this->getMockedClass($className, $mockName);
请注意,所有创建的模拟都必须具有唯一的名称。使用此名称,您在期望中引用它们。这也允许您有更多相同类(不同名称)的模拟。模拟创建引擎使用本地的 PHPUnit 模拟,自动模拟所有公共方法,并添加调用跟踪功能。您所需要做的就是将模拟传递到您要测试的类,以便从中调用。
创建全局函数的模拟
TestIt 允许您模拟全局函数。通过调用 mockGlobalFunction 函数来完成。
protected function mockGlobalFunction($name, $namespace = NULL) //just call it before using global function $this->mockGlobalFunction('time', 'YourNamespace'); //and then expect the call $this->expectDependencyCall('global', 'time', array(), 123456789);
命名空间参数是您的 time() 函数调用的命名空间。如果没有提供,它将被设置为 mockGlobalFunction 调用的命名空间。
这是通过命名空间技巧实现的。如果您在命名空间中调用函数,它将首先在当前命名空间中搜索,然后是在全局空间中。因此,如果没有在命名空间中定义,它将回退到全局函数。mockGlobalFunction 将在特定命名空间中定义函数的模拟,因此调用将不再回退到全局空间,并且模拟的函数将被调用。请注意,一旦函数在某个命名空间中定义,它将在这个命名空间中的任何地方被调用。尽管如此,TestIt 将进行检查,而不会再次定义它。但从代码的可读性角度来看,请在需要模拟的地方调用 mockGlobalFunction,以便明确您正在模拟某些内容。
因此,这里有限制。您只能模拟以非限定名称调用的函数。
$timestamp = time();//can be mocked $timestamp = \time();//can NOT be mocked
有一件事目前不起作用。带有输出参数的函数可以被模拟,但输出参数将不起作用。主要问题是,如何检测输出参数?有主意吗?:-)
期望发生的情况
在您的测试中,您正在测试输入和输出,但您还必须测试类与其依赖项的交互。这意味着您必须测试,是否有正确的方法、以正确的参数、在正确的顺序中被正确地调用。这些方法通常返回一些数据,因此有必要确保这些数据将被返回。
在 TestIt 中,这样做非常简单。
//declaration of expectation method public function expectDependencyCall($dependencyName, $methodName, $methodArguments = array(), $returnValue = NULL) //practical use $this->expectDependencyCall("articleModel", "get", array(10, 5), array()); //expects that on the "articleModel" dependency "get" method will be called with arguments (offset, limit) 10, 5 and it will return empty array //valid call in tested class would be for example $this->articleModel->get(10,5); //where $this->articleModel is a property where mock named "articleModel" is stored
方法参数将被期望与您传递的(在数组中)完全相同。空数组表示没有参数,NULL 表示您不在乎,因此将接受任何参数。可选参数(具有默认值)可以省略。
至于返回值,此处传递的所有值都将通过模拟调用原样返回,除了 \Exception 类的实例(及其祖先当然)。这些实例将被抛出为异常。这允许您模拟依赖项之一失败的情况。
定义测试对象的状态
在启动测试之前,您必须将测试对象置于定义的状态。您将根据此状态在测试后断言变化。为了拥有真正的单元测试,您不应使用对象的 set 方法等。TestIt 为此提供了两种方法。
setPropertyInTestSubject
protected function setPropertyInTestSubject($name, $value) //example $this->setPropertyInTestSubject('state', 'notReady'); //will set property 'state' to value 'notReady' in test object
getPropertyFromTestSubject
protected function getPropertyFromTestSubject($name) //example $this->getPropertyFromTestSubject('state'); //return value of the 'state' property in test object
您可以从测试对象中设置/获取任何已定义的属性。这是使用反射实现的。我注意到这里偶尔会出现错误。到目前为止,我还没有能够捕捉到具体的情境,但大多数时候,它运行良好 :-)
调用方法进行测试
使用 getTestObject() 方法访问测试对象的实例。如果尚未创建,将使用 createTestObject() 方法创建新的实例。
有时,调用/测试受保护的/私有方法也是有用的。TestIt 提供了一个简单的方法来调用这些方法。
protected function callTestSubjectMethod($name, array $arguments = array()) //example $this->callTestSubjectMethod('setState', array('ready')); //will call 'setState' method with one argument 'ready', even it is protected/private
调用不可访问的方法不是一个好习惯,但有时会很有用 :-)
断言最终条件
TestIt 将自动断言您的期望。对于每个依赖项调用,这些断言将在运行时(按以下顺序)完成
- 在正确的依赖项上调用正确的方法,顺序由期望的顺序定义
- 调用方法的实际参数将与预期的参数进行断言。如果有任何方法参数是可选的(具有默认值)并且被省略,则默认值将被填充到期望中。如果任何断言失败,测试将立即失败。将生成有意义的错误消息,因此可以清楚地了解应该发生什么以及实际发生了什么。
PHP CLI 脚本的远程调试
从版本 1.2.0 开始,TestIt 附带一个 bash 脚本,允许您在本地机器上调试服务器上运行的 PHP CLI 脚本。您可以在 vendor/bin 目录中找到 debugphpscript。您需要做的就是配置您的 IDE 以便能够进行带有 xDebug 的远程调试(请参考您 IDE 的帮助),将您的 IDE 设置为监听传入的 xDebug 会话,然后通过 debugphpscript 启动您想要调试的 CLI 脚本。您必须指定 xDebug 会话的 session_id,在某些情况下(例如 PhpStorm)您必须在 IDE 中指定您服务器配置的名称,需要本地机器的 IP 地址,当然,您必须指定您想要运行的命令。
./debugphpscript -id PhpStorm -s myserver.local -ip 192.168.1.1 -c "my_script.php -f someConfiguration"