CodeIgniter4 测试入门
CodeIgniter的构建旨在使对框架和应用程序的测试尽可能简单。PHPUnit
内置对它的支持,并且该框架提供了许多方便的辅助方法,以使您的应用程序的各个方面的测试尽可能轻松。
系统设置
安装phpUnit
CodeIgniter使用phpUnit作为所有测试的基础。有两种方法可以安装phpUnit以在系统中使用。
Composer
推荐的方法是使用Composer将其安装在项目中。尽管可以在全球范围内安装它,但我们不建议您这样做,因为随着时间的流逝,它可能会导致与系统上其他项目的兼容性问题。
确保系统上已安装Composer。在项目根目录(包含应用程序和系统目录的目录)中,从命令行键入以下内容:
> composer require --dev phpunit/phpunit
这将为您当前的PHP版本安装正确的版本。完成后,您可以通过键入以下内容运行该项目的所有测试:
> ./vendor/bin/phpunit
Phar
另一个选择是从phpUnit网站下载.phar文件。这是一个独立文件,应放置在项目根目录下。
测试您的应用程序
PHPUnit配置
框架phpunit.xml.dist
在项目根目录中有一个文件。这控制了框架本身的单元测试。如果您提供自己的phpunit.xml
,它将超越此。
您phpunit.xml
应该排除的system
文件夹,以及任何vendor
或 ThirdParty
文件夹,如果你是单元测试您的应用程序。
测试类
为了利用所提供的其他工具,您的测试必须扩展CIUnitTestCase
。默认情况下,所有测试均应位于tests / app目录中。
要测试新库Foo,您可以在tests / app / Libraries / FooTest.php中创建一个新文件:
<?php namespace App\Libraries;
use CodeIgniter\Test\CIUnitTestCase;
class FooTest extends CIUnitTestCase
{
public function testFooNotBar()
{
. . .
}
}
要测试您的模型之一,您可能会在以下代码中得到以下结果tests/app/Models/OneOfMyModelsTest.php
:
<?php namespace App\Models;
use CodeIgniter\Test\CIUnitTestCase;
class OneOfMyModelsTest extends CIUnitTestCase
{
public function testFooNotBar()
{
. . .
}
}
您可以创建适合您的测试样式/需求的任何目录结构。为测试类命名时,请记住app目录是App
名称空间的根,因此您使用的任何类都必须具有相对于的正确名称空间App
。
注解
测试类并非严格要求使用名称空间,但是它们有助于确保没有类名冲突。
测试数据库结果时,必须使用CIDatabaseTestClass类。
附加断言
CIUnitTestCase
提供了其他可能有用的单元测试断言。
assertLogged($ level,$ expectedMessage)
确保您希望实际记录的内容是:
$config = new LoggerConfig();
$logger = new Logger($config);
... do something that you expect a log entry from
$logger->log('error', "That's no moon");
$this->assertLogged('error', "That's no moon");
assertEventTriggered($ eventName)
确保您希望触发的事件实际上是:
Events::on('foo', function($arg) use(&$result) {
$result = $arg;
});
Events::trigger('foo', 'bar');
$this->assertEventTriggered('foo');
assertHeaderEmitted($ header,$ ignoreCase = false)
确保实际上发出了标头或cookie:
$response->setCookie('foo', 'bar');
ob_start();
$this->response->send();
$output = ob_get_clean(); // in case you want to check the actual body
$this->assertHeaderEmitted("Set-Cookie: foo=bar");
注意:与此相关的测试案例应在PHPunit中作为单独的进程运行。
assertHeaderNotEmitted($ header,$ ignoreCase = false)
确保没有发出标题或cookie:
$response->setCookie('foo', 'bar');
ob_start();
$this->response->send();
$output = ob_get_clean(); // in case you want to check the actual body
$this->assertHeaderNotEmitted("Set-Cookie: banana");
注意:与此相关的测试案例应在PHPunit中作为单独的进程运行。
assertCloseEnough($ expected,$ actual,$ message ='',$ tolerance = 1)
对于延长的执行时间测试,请测试预期时间与实际时间之间的绝对差是否在规定的公差内:
$timer = new Timer();
$timer->start('longjohn', strtotime('-11 minutes'));
$this->assertCloseEnough(11 * 60, $timer->getElapsedTime('longjohn'));
上述测试将允许实际时间为660或661秒。
assertCloseEnoughString($ expected,$ actual,$ message ='',$ tolerance = 1)
对于延长的执行时间测试,测试预期时间与实际时间之间的绝对差(格式为字符串)是否在规定的公差范围内:
$timer = new Timer();
$timer->start('longjohn', strtotime('-11 minutes'));
$this->assertCloseEnoughString(11 * 60, $timer->getElapsedTime('longjohn'));
上述测试将允许实际时间为660或661秒。
访问受保护的/私有的属性
测试时,可以使用以下setter和getter方法访问要测试的类中的受保护的方法和私有方法以及属性。
getPrivateMethodInvoker($ instance,$ method)
使您可以从类外部调用私有方法。这将返回一个可以调用的函数。第一个参数是要测试的类的实例。第二个参数是您要调用的方法的名称。
// Create an instance of the class to test
$obj = new Foo();
// Get the invoker for the 'privateMethod' method.
$method = $this->getPrivateMethodInvoker($obj, 'privateMethod');
// Test the results
$this->assertEquals('bar', $method('param1', 'param2'));
getPrivateProperty($ instance,$ property)
从类的实例中检索私有/受保护的类属性的值。第一个参数是要测试的类的实例。第二个参数是属性的名称。
// Create an instance of the class to test
$obj = new Foo();
// Test the value
$this->assertEquals('bar', $this->getPrivateProperty($obj, 'baz'));
setPrivateProperty($ instance,$ property,$ value)
在类实例中设置一个受保护的值。第一个参数是要测试的类的实例。第二个参数是要为其设置值的属性的名称。第三个参数是将其设置为的值:
// Create an instance of the class to test
$obj = new Foo();
// Set the value
$this->setPrivateProperty($obj, 'baz', 'oops!');
// Do normal testing...
模拟服务
您通常会发现您需要模拟app / Config / Services.php中定义的服务之一,以将测试限制为仅涉及代码,同时模拟服务的各种响应。在测试控制器和其他集成测试时尤其如此。该服务类提供了两个方法,使这个简单的:injectMock()
和reset()
。
injectMock()
此方法允许您定义Services类将返回的确切实例。您可以使用它来设置服务的属性,使其以某种方式运行,或者将服务替换为模拟类。
public function testSomething()
{
$curlrequest = $this->getMockBuilder('CodeIgniter\HTTP\CURLRequest')
->setMethods(['request'])
->getMock();
Services::injectMock('curlrequest', $curlrequest);
// Do normal testing here....
}
第一个参数是您要替换的服务。该名称必须与Services类中的函数名称完全匹配。第二个参数是要替换为的实例。
reset()
从Services类中删除所有模拟的类,使其恢复到原始状态。
流过滤器
CITestStreamFilter提供了这些辅助方法的替代方法。
您可能需要测试难以测试的事物。有时,捕获流(例如PHP自己的STDOUT或STDERR)可能会有所帮助。将CITestStreamFilter
帮助您从您所选择的流捕获输出。
在您的一个测试用例中演示此示例:
public function setUp()
{
CITestStreamFilter::$buffer = '';
$this->stream_filter = stream_filter_append(STDOUT, 'CITestStreamFilter');
}
public function tearDown()
{
stream_filter_remove($this->stream_filter);
}
public function testSomeOutput()
{
CLI::write('first.');
$expected = "first.\n";
$this->assertEquals($expected, CITestStreamFilter::$buffer);
}