rawsrc/exacodis

一个非常简约的PHP测试引擎

1.2.4 2022-08-15 21:25 UTC

This package is auto-updated.

Last update: 2024-09-27 09:31:15 UTC


README

2022-08-15 PHP 8.0+ v.1.2.3

PHP 测试引擎

Exacodis 是一个针对 PHP 的非常简约的测试引擎(非常轻量级的 PHP 测试框架)。这个引擎与其他工具相比非常简单易用。没有复杂的架构,甚至不是一个庞大的测试引擎,只是基础(以及一点点)来帮助您验证您的 PHP 代码。

只有 3 个类

  1. 一个用于控制测试(称为 Pilot),
  2. 一个用于封装和执行测试代码(称为 Runner
  3. 一个用于生成报告(称为 Report

以及许多辅助函数来检查返回值与预期类型和/或值的匹配。辅助函数并非详尽无遗,您将能够轻松创建自己的。

安装

composer require rawsrc/exacodis

重要

请注意,您的测试源代码必须完全干净:您不能覆盖测试运行、结果或资源。
如果您这样做,则代码将失败并抛出 Exception,直到您修复它。

变更日志

  1. 添加了测试任何受保护/私有方法的可能
  2. 添加了测试任何受保护/私有静态方法的可能
  3. 与上一个版本兼容

如何使用

我将直接使用 Exacodis 来测试它自己,作为学习支持。

由于 Exacodis 是一个非常轻量级的引擎,您需要自行设置测试环境。显然,这就像

<?php

declare(strict_types=1);

include 'Pilot.php';

use Exacodis\Pilot;

对于拥有许多类的项目,您必须告诉 PHP 如何加载您的类,无论是通过包含它们还是使用自动加载器。

这就足够开始测试您的代码了。

概念

  • Pilot 类做所有事情。您不必使用另外两个类;RunnerReport
  • 所有测试代码都必须封装在一个返回值的 Closure 中。
  • 您可以创建的辅助函数用于引擎的 assert 部分。您可以根据需要创建尽可能多的断言。标准库中已经包含了大量的辅助函数。

开始吧

// create new project
$pilot = new Pilot('Exacodis - A PHP minimalist test framework');
$pilot->injectStandardHelpers();
  • 资源

为了使用所有测试代码所需的功能,引擎能够存储和检索您想要的任何资源(对象、数组、标量值,甚至是单元测试...)。每个资源必须有一个唯一的名称,并且您不能意外地覆盖它。

//region resources
$pilot->addResource('year', 2021);
$pilot->addResource('years', [2020, 2021]);
$pilot->addResource('is_leap', false);
$pilot->addResource('current_month', 'september');

$pilot->addResource('dummy_array_data', [
    $pilot->getResource('year'), 
    $pilot->getResource('is_leap'), 
    $pilot->getResource('current_month')
]);
//endregion
  • 测试

如上所述,测试是一段简单的代码片段,封装在返回值的 Closure

$pilot->run(
    id: '001',
    description: 'Resource extractor (integer) - assertIsInt assertEqual assertNotEqual assertIn assertInStrict',
    test: fn() => $pilot->getResource('year')
);
  • 断言

断言使用标准辅助函数和当然也是您的。

$pilot->assertIsInt();
$pilot->assertEqual(2021);
$pilot->assertNotEqual(2000);
$pilot->assertIn(['2021']);
$pilot->assertInStrict([2021]);

您必须知道,断言(->assertXXX)始终应用于最新的一次运行。如果您想更改当前运行器,则可以请求它

$pilot->setCurrentRunnerTo('001');

然后断言将应用于此(见下文:嵌套运行)

  • 动态断言

这是编写动态测试的方法

$pilot->assert(
    test: fn() => count($pilot->getRunner('select_001')->getResult()) === 2,
    test_name: 'Count the records',
    expected: 2,    
);
  • 复杂的测试代码

您可以将测试代码作为原始代码编写,特别是对于复杂的测试代码。

// manual test
$stats = $pilot->getStats();
unset($stats['milliseconds'], $stats['hms']);
// we encapsulate the result in a closure to use it for testing purpose
$pilot->run(
    id: '007',
    description: 'check the count',
    test: fn() => $stats;
);
// then we lead our assertions
$pilot->assertIsArray();
$pilot->assertEqual([
    'nb_runs' => 6,
    'passed_runs' => 5,
    'failed_runs' => 1,
    'passed_runs_percent' => round(5/6*100, 2),
    'failed_runs_percent' => 100-round(5/6*100, 2),
    'nb_assertions' => 18,
    'passed_assertions' => 17,
    'failed_assertions' => 1,
    'passed_assertions_percent' => round(17/18*100, 2),
    'failed_assertions_percent' => 100-round(17/18*100, 2)
]);
  • 测试类中的受保护/私有和/或静态方法

为了能够测试任何 protectedprivatestatic 或不)方法,您必须使用 $pilot->runClassMethod(...) 而不是 $pilot->run(...)。方法的签名是

public function runClassMethod(
    int|string|null $id,
    object|string $class,
    string $description = '',
    ?string $method = null,
    array $params = [],
)

请注意

  • 如果类有一个复杂的构造函数且需要参数,那么您必须为 $class 提供一个干净的对象实例。
  • 在其他情况下,$class 可以是一个字符串,例如 Foo 或者甚至是 Foo::method。这对于没有构造函数或者构造函数没有必需参数的类同样适用。
  • 数组 $params 必须包含调用方法所需的所有参数。它也支持命名参数。

其他部分与 $pilot->run() 方法类似。

以下是一个来自 PHP 测试文件的例子:这里所有的测试都是等效的。

$foo = new Foo();
$pilot->runClassMethod(
    id: '008',
    description: 'private method unit test using directly an instance of Foo',
    class: $foo, // instance
    method: 'abc',
);
$pilot->assertIsString();
$pilot->assertEqual('abc');

$pilot->runClassMethod(
    id: '009',
    description: 'private method unit test using string notation for the class Foo',
    class: 'Foo', // class name 
    method: 'abc',
);
$pilot->assertIsString();
$pilot->assertEqual('abc');

$pilot->runClassMethod(
    id: '010',
    description: 'private method unit test using short string notation for the class Foo and the method abc',
    class: 'Foo::abc', // short notation
);
$pilot->assertIsString();
$pilot->assertEqual('abc');

看看如何使用两个参数调用私有方法,以及另一个调用 private static function() 的例子。

$pilot->runClassMethod(
    id: '012',
    description: 'private method unit test with two parameters',
    class: 'Foo',
    method: 'hij',
    params: ['p' => 25, 'q' => 50]  // or [25, 50] 
);
$pilot->assertIsInt();
$pilot->assertEqual(250);

$pilot->runClassMethod(
    id: '018',
    description: 'private static method unit test',
    class: 'Foo::tuv',
);
$pilot->assertIsString();
$pilot->assertEqual('tuv');

命名参数必须按照函数原型中定义的顺序排列。

  • 报告

引擎内部计算数据,你可以简单地请求一个 HTML 报告,就像这样。

$pilot->createReport();

在存储库中你可以找到两个报告:一个完全通过,另一个失败。你可以确切地看到引擎是如何跟踪执行过程以及保持了哪些数据。

  • 助手

你可以创建自己的助手来验证任何结果,使用简单的 Closure。请查看

//region equal
$equal = function(mixed $to): void {
    /** @var Pilot $this */
    if ($this->current_runner->getResult() === $to) {
        $this->addSuccess('equal');
    } else {
        $this->addFailure(expected: 'Equal to: '.print_r($to, true));
    }
};
$helpers['assertEqual'] = $equal;
//endregion

这个断言是标准库的一部分,并且在项目开始后立即注入。你也可以使用 $pilot->addHelper($name, $closure); 动态定义一个助手。

  • 嵌套运行

对于真正复杂的测试,你还可以定义嵌套运行。

$pilot->run(
    id: 'abc',
    description: 'complex nested tests',
    test: function() use ($pilot) {
        // nested run
        $pilot->run(
            id: 'def',
            description: 'nested run',
            test: function() {
                // ...
                return $foo;
            }
        )
        // careful: this applies to the (latest) run which is here 'def'
        $pilot->assertIsArray();
        
        return []; // a run MUST always return a value
    }
);
// careful, if you continue the assertions here, by default they will apply to the
// (latest) run which is still 'def'; you must change the current runner to work with the previous one
$pilot->setCurrentRunnerTo('abc');
$pilot->assertIsArray(); // now it applies to the run 'abc'

这是 Exacodis 的唯一难点。这使代码更易于阅读,因为不需要在每个函数调用中设置大量的参数。

祝您玩得开心!

rawsrc