koncnyjakub / mytester
适用于PHP的灵活和可扩展的测试框架
Requires
- php: >=8.3.0
- composer-runtime-api: ^2.0
- ayesh/php-timer: ^2.2
- konecnyjakub/event-dispatcher: ^1.1
- nette/command-line: ^1.7
- nette/utils: ^4.0.5
- symfony/polyfill-php84: ^1.30
Requires (Dev)
- nette/application: ^3.2
- nette/bootstrap: ^3.2.4
- nette/robot-loader: ^4.0.2
- php-parallel-lint/php-console-highlighter: ^0.5
- php-parallel-lint/php-parallel-lint: ^1.4
- phpstan/phpstan: ^1.12
- squizlabs/php_codesniffer: ^3.10
Suggests
- ext-dom: For generating Cobertura code coverage reports and/or JUnit reports
- ext-pcov: For generating code coverage reports
Conflicts
- nette/application: <3.2
- nette/di: <3.2
- nette/robot-loader: <4.0.2
README
My Tester 是一个灵活且可扩展的PHP测试框架。它需要PHP 8.3或更高版本和Composer 2。
安装
通过Composer安装My Tester是最好的方式。只需将konecnyjakub/mytester添加到您的(开发)依赖中。
使用
测试用例
My Tester使用面向对象风格来定义测试。包含测试的类必须扩展MyTester\TestCase。所有以"test"开头的方法都将自动在调用"run"方法时启动。在它们内部调用assertSomething方法。示例
<?php
declare(strict_types=1);
class Tests extends MyTester\TestCase
{
public function testA(): void
{
$actual = someCall();
$text = anotherCall();
$this->assertSame("abc", $actual);
$this->assertSame("def", $text);
}
}
测试方法参数
TestCase后代的测试方法可以接受一个参数。您可以从类中提供一个公共方法的名称,该方法返回一个具有DataProvider属性的数组。它可以是值的列表,在这种情况下,方法将多次运行,每次使用列表中的一个值。示例
<?php
declare(strict_types=1);
use MyTester\Attributes\DataProvider;
class Tests extends MyTester\TestCase
{
#[DataProvider("dataProvider")]
public function testParams(string $text): void
{
$this->assertContains("a", $text);
}
public function dataProvider(): array
{
return [
["abc", "def"],
];
}
}
自定义测试名称
您可以给测试方法和整个测试套件自定义名称,这些名称将在输出中显示,而不是标准的NameOfClass::nameOfMethod。这是通过Test/TestSuite属性完成的。示例
<?php
declare(strict_types=1);
use MyTester\Attributes\Test;
use MyTester\Attributes\TestSuite;
#[TestSuite("MyTests")]
class Tests extends MyTester\TestCase
{
#[Test("Custom name")]
public function testTestName(): void
{
$this->assertTrue(true);
}
}
跳过测试
可以无条件地跳过一个测试。只需使用Skip属性即可。示例
<?php
declare(strict_types=1);
use MyTester\Attributes\Skip;
class Tests extends MyTester\TestCase
{
#[Skip()]
public function testTestName(): void
{
$this->assertTrue(false);
}
}
。您还可以添加测试应该跳过的条件。简单的值,如数字、字符串和布尔值,将直接评估。如果您提供一个数组,则检查所有键及其值。一个支持的关键是"php"。如果您的PHP版本低于其值,则测试将被跳过。您还可以使用"extension"键,当该扩展未加载时,测试将被跳过。跳过的测试将显示在输出中。示例
<?php
declare(strict_types=1);
use MyTester\Attributes\Skip;
class Tests extends MyTester\TestCase
{
#[Skip(1)]
#[Skip(true)]
#[Skip("abc")]
#[Skip(["php" => "5.4.1"])]
#[Skip(["extension" => "abc"])]
#[Skip(["sapi" => "cgi"])]
public function testTestName(): void
{
$this->assertTrue(false);
}
}
如果条件太复杂(或者您出于任何原因不想使用属性),可以在测试方法中调用markTestSkipped方法。它可选地接受一个说明为什么跳过的消息。
<?php
declare(strict_types=1);
use MyTester\Attributes\Skip;
class Tests extends MyTester\TestCase
{
public function testTestName(): void
{
$this->markTestSkipped("Optional message");
$this->assertTrue(false);
}
}
不完整的测试
如果测试尚未完全编写,您可以将其标记为不完整,它将带有警告通过。只需调用markTestIncomplete方法。您可以选择传递一个解释为什么它不完整的消息。一旦调用该方法,则不会在调用该方法的方法中执行其他断言。
<?php
declare(strict_types=1);
class Tests extends MyTester\TestCase
{
public function testIncomplete(): void
{
$this->assertTrue(true);
$this->markTestIncomplete("Optional message");
}
}
设置和清理
如果您需要在TestCase中在每个测试之前/之后执行某些操作,您可以定义setUp/tearDown方法。如果您定义了startUp/shutDown方法,它们将在套件开始/结束时自动调用。
运行测试
运行测试用例最简单的方法是使用提供的脚本vendor/bin/mytester。它扫描your_project_root/tests文件夹(默认情况下)中的*Test.php文件,并运行其中的TestCases。您可以指定不同的文件夹,作为脚本的第一个参数。
./vendor/bin/mytester tests/unit
如果您已正确配置Composer来自动加载您的测试套件并使用优化后的自动加载器,您就设置好了。如果Composer找不到它们,请安装包nette/robot-loader,它将用于查找和加载它们。
彩色输出
自动测试运行器可以以彩色打印结果,但默认情况下是禁用的。要使用颜色,只需将--colors参数传递给脚本。
./vendor/bin/mytester tests/unit --colors
结果格式
可以将测试结果以不同的格式显示,对于某些格式甚至可以将它们放入一个可以被您的持续集成系统处理的文件中,只需将参数 --resultsFormat 传递给脚本。目前支持的格式有 JUnit、TAP 和 TextDox(值为格式的名称,小写)。
JUnit 将结果打印到文件 junit.xml 中,TAP 和 TestBox 默认在控制台/终端显示结果。TestDox 使用由属性 TestSuite/Test 设置的自定义名称,如果未设置,则仅显示类名和方法名。
./vendor/bin/mytester tests/unit ----resultsFormat junit
./vendor/bin/mytester tests/unit ----resultsFormat tap
./vendor/bin/mytester tests/unit ----resultsFormat testdox
如果您想更改 JUnit 格式的输出文件或想以 TAP 和 TestBox 格式将结果打印到文件中,也可以使用参数 --resultsFile。
./vendor/bin/mytester tests/unit ----resultsFormat junit --resultsFile custom_name.xml
代码覆盖率
当可能时,My Tester 会自动生成代码覆盖率报告的百分比。这是在类 MyTester\Tester 中完成的,因此它可以在提供的脚本 vendor/bin/mytester 和我们的 Nette DI 容器扩展(见下文)中使用。您只需要在启用 pcov 或 xdebug 扩展的情况下运行脚本。
它还能够生成完整的代码覆盖率报告。支持的格式有 Cobertura 和文本。只需将参数 --coverageFormat 传递给脚本,值通常是格式的名称,小写。两者都将报告放入文件中,对于 Cobertura 是 coverage.xml,对于文本是 coverage.txt。
./vendor/bin/mytester tests/unit --coverageFormat cobertura
./vendor/bin/mytester tests/unit --coverageFormat text
可以更改 Cobertura 和文本格式的输出名称,只需添加参数 --coverageFile。
./vendor/bin/mytester tests/unit --coverageFormat cobertura --coverageFile cobertura.xml
自动化测试运行器扩展
自动化测试运行器的功能可以通过扩展来扩展。它们可以为特定事件添加回调。扩展必须实现 MyTester\ITesterExtension 接口。目前,在使用脚本 vendor/bin/mytester 时,无法注册自定义扩展。
方法 getEventsPreRun 返回在所有测试运行之前调用的回调(当我们知道应该运行哪些测试用例时),它将 MyTester\Events\TestsStartedEvent 作为第一个参数接收。
方法 getEventsAfterRun 返回在所有测试运行之后调用的回调,它将 MyTester\Events\TestsFinishedEvent 作为第一个参数接收。
方法 getEventsBeforeTestCase 返回在运行测试用例之前调用的回调,它将 MyTester\Events\TestCaseStarted 作为第一个参数接收。
方法 getEventsAfterTestCase 返回在所有测试用例运行之后调用的回调,它将 MyTester\Events\TestCaseFinished 作为第一个参数接收。
Nette 应用程序
如果您正在开发 Nette 应用程序,您可能想使用我们的 Nette DI 容器扩展。它结合了自动化测试运行器和依赖注入的力量。换句话说,它会自动运行您的测试用例,并从 DI 容器中传递依赖项。它的用法很简单,只需将这些行添加到您的配置文件中
extensions:
mytester: MyTester\Bridges\NetteDI\MyTesterExtension
然后您可以从容器中获取名为 mytester.runner 的服务(类型为 MyTester\Tester),并运行其方法 execute。它将根据所有测试是否通过自动结束脚本,以 0/1 结束。
<?php
declare(strict_types=1);
$result = $container->getService("mytester.runner")->execute(); //or
$container->getByType(MyTester\Tester::class)->execute();
扩展期望您的测试用例位于 your_project_root/tests。如果它们位于不同的文件夹中,您必须向扩展添加文件夹参数。
mytester:
folder: %wwwDir%/tests
。并且如果您需要在测试前后执行一些任务,您可以使用自动化测试运行器扩展。只需使用选项 extensions 注册它们。
mytester:
extensions:
- MyExtensionClass
可以通过设置选项 colors 为 true 启用输出中的颜色。
mytester:
colors: true
使用扩展也可以生成代码覆盖率报告,只需使用设置 coverageFormat。有关支持的格式和值,请参阅代码覆盖率部分。
mytester:
coverageFormat: cobertura
如果没有设置格式,则只会报告代码覆盖率的总百分比。
My Tester 包含一些实用程序,使测试 Nette 应用程序变得更容易。您可以在测试用例中使用特性 MyTester\Bridges\NetteDI\TCompiledContainer 从 DIC 容器或其中获取任何服务。
<?php
declare(strict_types=1);
class Tests extends MyTester\TestCase
{
use \MyTester\Bridges\NetteDI\TCompiledContainer;
public function testService(): void
{
$service = $this->getService(\App\Model\MyClass::class);
$this->assertTrue($service->someMethod());
}
}
您还可以使用新配置重新创建容器。
<?php
declare(strict_types=1);
class Tests extends MyTester\TestCase
{
use \MyTester\Bridges\NetteDI\TCompiledContainer;
public function testService(): void
{
$config = [...];
$this->refreshContainer($config);
}
}
您还可以测试组件的输出(无论是针对字符串还是文件内容)或仅验证它是否可以附加到容器。
<?php
declare(strict_types=1);
class Tests extends MyTester\TestCase
{
use \MyTester\Bridges\NetteApplication\TComponent;
public function testService(): void
{
$component = new \Nette\Application\UI\Component();
$this->attachToPresenter($component);
$this->assertRenderOutput($component, "<div>test</div>");
$this->assertRenderOutputFile($component, __DIR__ . "/component_output.txt");
}
}
使用 TCompiledContainer 和 TComponent 特性需要设置容器,请参考 tests/NetteDI.php 中的示例。
更多示例
有关使用示例的更多内容,请参阅包含在 tests 目录中的 My Tester 的测试。