lucinda/unit-testing

轻量级的PHP8.1单元测试库,零依赖

v2.0.3 2022-06-05 06:43 UTC

README

目录

关于

这个库部分是在使用PHPUnit时感到沮丧而创建的,PHPUnit是超过99%的具有单元测试功能的PHP应用程序的标准解决方案。这个软件的目标是构建PHPUnit不具备的东西:一个干净编码、零依赖的API!

diagram

开发人员只需遵循以下步骤

  • 配置:设置一个XML文件,其中配置了单元测试
  • 初始化:为测试的目标API自动创建单元测试架构(类和方法)
  • 开发:为上面创建的每个类方法开发一个或多个单元测试
  • 执行:在上述基础上自动执行单元测试,并在控制台或JSON中显示单元测试结果

API完全符合PSR-4规范,仅需要PHP8.1+解释器,SimpleXML + cURL + PDO扩展(后者用于URI和SQL测试)和Console Table API(用于显示单元测试结果)。要快速了解其工作原理,请查看

  • 安装:描述了如何在计算机上安装API,考虑到上述步骤
  • 断言:描述了如何使用此API进行断言
  • 示例:显示了OAuth2客户端API的真实单元测试

为什么不使用PHPUnit

关于PHPUnit的所有事情都让人联想到过去的时代,那时开发者构建了庞大的类,这些类可以做“一切”,除了“extends”关键字外,对封装一无所知(怀疑者应该检查https://github.com/sebastianbergmann/phpunit/blob/master/src/Framework/TestCase.php所有PHPUnit测试都必须扩展!)。

是否可以做更好的事情?单元测试API是否应遵循面向对象编程的良好原则,或者只是被测试的代码?我认为,只要开发者感觉在混乱中工作很舒服,那么在以后构建类似的东西就会成为不良先例。必须做更好的事情!

配置

类似于PHPUnit,单元测试的配置是通过一个XML文件完成的,其语法如下

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<xml>
    <unit_tests>
        <unit_test>
            <sources path="PATH" namespace="NAMESPACE"/>
            <tests path="PATH" namespace="NAMESPACE"/>
        </unit_test>
        ...
    </unit_tests>
    <servers>
        <sql>
            <ENVIRONMENT>
                <server driver="DRIVER" host="HOSTNAME" port="PORT" username="USERNAME" password="PASSWORD" schema="SCHEMA" charset="CHARSET"/>
            </ENVIRONMENT>
            ...
        </sql>
    </servers>
</xml>

必选标签unit_tests存储要执行的单元测试套件。每个测试的API由一个具有以下子标签的unit_test标签标识:

  • sources:通过同名属性配置源的基本路径path(例如:src)和基本APInamespace(例如:Lucinda\Logging)
  • 测试:通过同名属性配置测试的基础路径 path(例如:tests)和基础API 命名空间(例如:Test\Lucinda\Logging)

这些设置将用于在需要时自动加载 test/sources 类,例如在 composer 的情况下,您必须使用完全限定的命名空间,并以反斜杠结尾。

可选标签 服务器 存储单元测试中将要使用的 SQL 服务器连接设置,按 ENVIRONMENT(通过 php getenv("ENVIRONMENT") 的值)分隔。每个服务器必须映射到具有以下属性的 服务器 子标签,以配置连接

  • DRIVER:(必填)PDO 识别的 SQL 供应商名称。例如:mysql
  • HOSTNAME:(必填)当前数据库服务器主机名。例如:127.0.0.1
  • PORT:(可选)当前数据库服务器端口号。例如:3306
  • USERNAME:(必填)数据库服务器用户名。例如:root
  • PASSWORD:(必填)数据库服务器用户密码。例如:my-password
  • SCHEMA:(可选)单元测试将运行的架构名称。例如:test_schema
  • CHARSET:(可选)连接中使用的默认字符集。例如:utf8

示例:在 单元测试 @ OAuth2 客户端 API

实现

初始化

只需运行一个 Lucinda\UnitTest\Controller 实现(参见 安装 部分),sources 文件夹中的类就会根据以下规则镜像到 tests 文件夹

  • 保留原始文件夹结构,只是类名被重命名(见下文)
  • 保留原始类和文件名,只是后面添加了 "Test"。因此,MyClassMyClass.php 镜像到 MyClassTestMyClassTest.php
  • 保留原始命名空间,只是前面添加了 "Test" 命名空间。因此,Foo\Bar 镜像到 Test\Foo\Bar
  • 只镜像源类的公共方法
  • 忽略源方法的参数和返回类型,因此原始 asd(string fgh): int 将镜像到 php asd()
  • 所有创建的方法都将具有空体

这确保了每次执行时都保持 100% 的覆盖率,让程序员自己开发缺失的单元测试

开发

为了被覆盖,每个 tests 类的公共方法必须返回一个 Lucinda\UnitTest\Result 实例或一个实例列表,具体取决于您是否希望进行一个或多个测试。每个测试都有一个状态(通过或不通过)和一个可选的消息(包含标识测试的细节)。

示例

namespace Test\Foo; // mirrors source namespace: Foo

class BarTest { // mirrors class: Bar
    public function asd(): Lucinda\UnitTest\Result // mirrors method: asd @ Bar 
    {
        $object = new \Foo\Bar(...);
        $data = $object->asd(...);
        // makes a single numeric assertion
        return (new Lucinda\UnitTest\Validator\Integers($data))->assertEquals(12);
    }

    public function fgh(): array // mirrors method: fgh @ Bar 
    {
        $results = [];
        $object = new \Foo\Bar(...);
        $data = $object->fgh(...);
        // makes multiple assertions on same value
        $test = new Lucinda\UnitTest\Validator\Arrays($data);
        $results[] = $test->assertNotEmpty("is it empty");
        $results[] = $test->assertContainsValue("qwerty");
        return $results;
    }
}

执行

只需运行一个 Lucinda\UnitTest\Controller(参见 安装 部分),tests 文件夹中的类就会被实例化,它们的公共方法会按设置的顺序执行,并收集 Lucinda\UnitTest\Result 实例。逻辑如下:

  • 如果类 @ src 没有镜像类 @ tests,则对相应类的单元测试标记为失败!
  • 如果类 @ src 中有公共方法不在镜像类 @ tests 中,则对相应方法的单元测试标记为失败!
  • 如果镜像类的任何方法不返回Lucinda\UnitTest\Result 或其列表,单元测试将标记为失败,并显示消息表示该方法未覆盖
  • 单元测试的结果被收集到Lucinda\UnitTest\Result 的列表中

这个抽象类包含以下感兴趣的方法

API 已经包含两个Lucinda\UnitTest\Controller 实现

开发者可以构建自己的扩展,并将结果保存到某个位置...

安装

在您的 API 所在的文件夹中,请在控制台运行以下命令

composer require lucinda/unit-testing

然后创建一个包含配置设置(参见上文的配置)的 unit-tests.xml 文件和一个包含以下代码的 test.php 文件

require(__DIR__."/vendor/autoload.php");
try {
	new Lucinda\UnitTest\ConsoleController("unit-tests.xml", "local");
} catch (Exception $e) {
	// handle exceptions
}

要查看使用示例,请查看单元测试,这些测试针对OAuth2 客户端 API

断言

原始值断言

API 允许您对所有的 PHP 原始数据类型进行断言

这些类中的每一个都有一个构造函数,其中注入了相应类型的值,然后是一系列对该值进行断言的方法。在现实生活中,您将只使用这些类进行单个断言。

断言示例

$test = new Lucinda\UnitTest\Validator\Arrays($data);
return $test->assertNotEmpty("is it empty");

SQL 查询结果断言

有时需要测试数据库中的信息。为此,您可以使用 API 提供的 Lucinda\UnitTest\Validator\SQL 类,该类有四个公共方法

断言示例

$test = new Lucinda\UnitTest\Validator\SQL($dataSource);
$test->assertStatement("SELECT COUNT(id) AS nr FROM users", new class extends Lucinda\UnitTest\Validator\SQL\ResultValidator() {
    public function validate(\PDOStatement $statementResults): Result {
        $test = new Lucinda\UnitTest\Validator\Integer((integer) $statementResults->fetchColumn());
        return $test->assertEquals(8);
    }
});

上述机制允许您对单个 Lucinda\UnitTest\Validator\SQL 实例开发多个断言,这又对应于单个 SQL 连接。

URL 执行结果断言

有时需要测试 URL 执行的结果。为此,您可以使用 API 提供的 Lucinda\UnitTest\Validator\URL 类,该类有两个公共方法

断言示例

$test = new Lucinda\UnitTest\Validator\URL(new Lucinda\UnitTest\Validator\URL\DataSource("https://www.google.com"));
$test->assert(new class extends Lucinda\UnitTest\Validator\URL\ResultValidator() {
    public function validate(Lucinda\UnitTest\Validator\URL\Response $response): Result {
        $test = new Lucinda\UnitTest\Validator\Strings($response->getBody());
        return $test->assertContains("google");
    }
});

上述机制允许您通过单个 Lucinda\UnitTest\Validator\URL 实例对相同的 URL 执行结果开发多个断言。

文件断言

可以通过使用Lucinda\UnitTest\Validator\Files类对文件进行断言,该类提供了以下公共方法:

断言示例

$test = new Lucinda\UnitTest\Validator\Files("foo/bar.php");
return $test->assertExists();

示例

OAuth2 客户端 API是使用此API进行单元测试的API之一,因此请检查: