我正在尝试使用 PHPUnit 为 php 应用程序实现类似 Django 的测试实用程序。类似 Django,我的意思是在运行第一个测试之前从主数据库创建一个单独的测试数据库,并在运行最后一个测试后将其删除。即使一次运行许多测试用例,测试数据库也只需要创建一次。
为此,我采取了以下方法 -
我定义了一个自定义测试套件类,以便我可以在其设置和拆卸方法中编写用于创建和删除数据库的代码,然后使用此类来运行测试,如下所示
$ phpunit MyTestSuite
MyTestSuite 定义了一个名为
suite
的静态方法,我只使用 glob
并将测试添加到测试套件中,如下所示
public static function suite() {
$suite = new MyTestSuite();
foreach (glob('./tests/*Test.php') as $tc) {
require_once $tc;
$suite->addTestSuite(basename($tc, '.php'));
}
return $suite;
}
所有测试用例类都从
PHPUnit_Framework_TestCase
的子类扩展,该类的 setup 和teardown 方法负责从 json 夹具文件加载和清除初始数据。
现在作为第一。测试不断增加,我一次只需要运行选定的测试。但由于我已经使用测试套件加载测试,因此无法使用 --filter 选项。 这让我觉得这种做法可能不是正确的。
所以我的问题是,无论 PHPUnit 如何找到它们,在运行第一个测试之前和运行最后一个测试之后执行某些操作的正确方法是什么?
PS:我没有使用 PHPUnit_Extensions_Database_TestCase,而是使用我自己的创建、填充和删除数据库的实现。
我最近遇到了一些需要解决同样问题的事情。我使用自定义类的 __destruct
方法尝试了
Edorian 的答案,但它似乎是在每个测试结束时运行,而不是在 所有 测试结束时运行。
我没有在 bootstrap.php 文件中使用特殊的类,而是使用 PHP 的
register_shutdown_function
函数在所有测试结束后处理数据库清理,而且它似乎工作得很好。
这是我的 bootstrap.php 文件中的示例
register_shutdown_function(function(){
some_db_cleanup_methods();
});
我的两个不使用
"Test Suites"
的自发想法。这样做的一个位于底部。
###测试监听器
PHPUnits test listeners
你可以做一个
public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
{
if($suite->getName() == "yourDBTests") { // set up db
}
public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
{
if($suite->getName() == "yourDBTests") { // tear down db
}
您可以在 xml 配置文件的测试套件中定义所有数据库测试,如下所示
in the docs
<phpunit>
<testsuites>
<testsuite name="db">
<dir>/tests/db/</dir>
</testsuite>
<testsuite name="unit">
<dir>/tests/unit/</dir>
</testsuite>
</testsuites>
</phpunit>
###引导程序
使用 phpunits 引导文件,您可以创建一个类,该类创建数据库并在进程结束时用自己的
__destruct
方法将其拆除。
将对对象的引用放在某个全局范围内将确保对象仅在所有测试结束时才被解构。 (正如 @beanland 指出的:使用 register_shutdown_function() 更有意义!)
##使用测试套件:
http://www.phpunit.de/manual/3.2/en/organizing-test-suites.html显示:
<?php
class MySuite extends PHPUnit_Framework_TestSuite
{
public static function suite()
{
return new MySuite('MyTest');
}
protected function setUp()
{
print "\nMySuite::setUp()";
}
protected function tearDown()
{
print "\nMySuite::tearDown()";
}
}
class MyTest extends PHPUnit_Framework_TestCase
{
public function testWorks() {
$this->assertTrue(true);
}
}
这在 PHPUnit 3.6 中运行良好,并且将在 3.7 中运行。它不在当前的文档中,因为“测试套件类”在某种程度上已被弃用/不鼓励,但它们将存在相当长一段时间。
请注意,为每个测试用例拆除并设置整个数据库对于对抗测试间依赖性非常有用,但如果您不在内存中(如 sqlite 内存)运行测试,那么速度可能不值得。
在 2020 年,@edorian 的方式已经被弃用了:
/**
* @throws Exception
*
* @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039
*/
public function testSuiteLoaderClass(): string
{
///
}
现在我们需要通过扩展使用TestRunner。在
phpunit.xml
中添加此代码:
<extensions>
<extension class="Tests\Extensions\Boot"/>
</extensions>
<testsuites>
...
</testsuites>
Tests/Extensions/Boot.php
:
<?php
namespace Tests\Extensions;
use PHPUnit\Runner\AfterLastTestHook;
use PHPUnit\Runner\BeforeFirstTestHook;
class Boot implements BeforeFirstTestHook, AfterLastTestHook
{
public function executeBeforeFirstTest(): void
{
// phpunit --testsuite Unit
echo sprintf("testsuite: %s\n", $this->getPhpUnitParam("testsuite"));
// phpunit --filter CreateCompanyTest
echo sprintf("filter: %s\n", $this->getPhpUnitParam("filter"));
echo "TODO: Implement executeBeforeFirstTest() method.";
}
public function executeAfterLastTest(): void
{
// TODO: Implement executeAfterLastTest() method.
}
/**
* @return string|null
*/
protected function getPhpUnitParam(string $paramName): ?string
{
global $argv;
$k = array_search("--$paramName", $argv);
if (!$k) return null;
return $argv[$k + 1];
}
}
纯PHP:
phpunit --testsuite Unit --filter CreateCompanyTest
拉拉维尔:
php artisan test --testsuite Unit --filter CreateCompanyTest
输出:
PHPUnit 9.3.10 by Sebastian Bergmann and contributors.
testsuite: Unit
filter: CreateCompanyTest
TODO: Implement executeBeforeFirstTest() method.