lastzero/test-tools

通过添加服务容器和自动初始化的假对象到PHPUnit,提高测试效率

v5.0.1 2022-07-05 07:17 UTC

README

License Latest Stable Version Total Downloads Test Coverage Build Status

这个库通过添加可配置的服务容器和自动初始化的假对象到PHPUnit,提高了测试效率。

  • UnitTestCase 将 Symfony 的 服务容器 添加到 PHPUnit\Framework\TestCase 中(通过 Tests 目录中的 config.yml 文件进行配置)
  • WebTestCaseCommandTestCase 扩展 UnitTestCase,以对 Symfony Web 和 CLI 应用程序进行功能测试
  • FileFixture 从/到文件系统读取和写入序列化数据
  • SelfInitializingFixtureTraitBlackBox 为几乎任何数据库或服务客户端添加了测试支持(记录和回放),以提供作为测试替身的使用自初始化的假对象
  • 默认支持 Doctrine DBAL

服务容器

以下是一个使用 TestTools\TestCase\UnitTestCase 构建的测试用例示例 - 注意 setUp() 方法,它从依赖注入容器中获取可用的对象

use TestTools\TestCase\UnitTestCase;

class FooTest extends UnitTestCase
{
    protected $foo;

    public function setUp()
    {
        $this->foo = $this->get('foo');
    }

    public function testBar()
    {
        $result = $this->foo->bar('Pi', 2);
        $this->assertEquals(3.14, $result);
    }
}

要定义服务,只需在您的基测试目录中创建一个 config.yml(可选 config.local.yml 用于本地修改),例如

Bundle/Example/Tests/config.yml

YAML 文件必须包含 "parameters" 和 "services" 部分。如果您还不熟悉依赖注入或配置文件格式,请阅读 symfony.com 上的文档(它真的很简单)

https://symfony.com.cn/doc/current/components/dependency_injection/introduction.html

选择 Symfony 服务容器,是因为它的 YAML 容器配置易于理解。

由于在执行测试时必须避免全局状态,服务实例在测试之间不会被缓存。但是,容器中的服务定义会被重用。这比每次测试之前完全重新初始化容器(大约快 5 到 10 倍)显著提高了测试性能。

TestTools 可以用于测试任何应用程序、框架或库,就像 PHPUnit\Framework\TestCase 一样。

经典与模拟主义风格的单元测试

这些工具通过依赖注入简化了使用真实对象和测试替身编写单元测试,因此一些开发人员可能会担心,由此产生的测试不是 真正 的单元测试,因为 类依赖默认不会模拟。模拟是创建模拟真实对象行为的对象。这些表面上相互冲突的方法被称为 经典和模拟主义单元测试风格

经典 TDD 风格 是尽可能使用 真实对象,如果使用真实对象不方便,则使用双倍。因此,经典的 TDDer 会使用真正的仓库和一个双倍用于邮件服务。这种双倍并不真的那么重要。

然而,一个 模拟主义 TDD 实践者会始终为任何具有有趣行为的对象使用模拟。在这种情况下,对于仓库和邮件服务。” -- Martin Fowler

有时需要使用模拟对象(mocks)和测试替身(test doubles)来进行测试,但创建和维护模拟对象可能是一项枯燥且耗时的工作。因此,你应该考虑避免其广泛使用,并优先使用真实对象。根据我的经验,它们不会造成伤害——相反:你可以立即看到真实对象是如何相互交互的,而不是等待功能测试。实际上,过度使用模拟是糟糕软件设计的指示。

从理论上讲,模拟风格在查找有问题的代码行时可能更精确,因为所有类都是在完全隔离的情况下进行测试。在实践中,经典的单元测试也会为你提供堆栈跟踪,指向正确的代码行。

"我们发现追踪实际故障并不困难,即使它导致相邻的测试失败。因此,我们认为在实际情况中隔离不是一个问题。" —— 马丁·福勒

在最坏的情况下,如果仅有一个类或函数出错,就会导致多个测试用例失败——这将为你提供更多关于问题的信息,并允许你轻松找到并修复受影响的代码。

即使是依赖于数据库或Web服务的代码,也可以使用自动初始化的固定值(self-initializing fixtures)而不是手写的模拟进行测试。它们无法正确模拟的是状态,但健壮的单元测试不应依赖于状态。如果你想测试状态,请使用用户界面或API的功能测试

自动初始化的虚构对象

作为测试替身的自动初始化的虚构对象的概念可以应用于所有类型的存储(数据库)和服务,如SOAP或REST API。

SelfInitializingFixtureTrait使现有类能够与基于文件的固定值(记录和回放)一起工作。

use TestTools\Fixture\SelfInitializingFixtureTrait;

class Foo extends SomeBaseClass
{
    use SelfInitializingFixtureTrait;

    public function bar($name, $type, array $baz = array())
    {
        return $this->callWithFixtures('bar', func_get_args());
    }
}

Doctrine连接类(TestTools\Doctrine\DBAL\Connection)可以作为现成的示例。它作为标准连接类的包装器(白盒继承)。可以使用黑盒继承(TestTools\Fixture\BlackBox)来封装任何客户端接口。

TestTools\TestCase\WebTestCase.php可以用于基于应用程序常规服务配置的Symfony控制器的功能测试。

use TestTools\TestCase\WebTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;

class FooControllerTest extends WebTestCase
{
    protected function configureFixtures(ContainerInterface $container)
    {
        // Service instance must provide a useFixtures() method for this to work
        $container->get('db')->useFixtures($this->getFixturePath());
    }

    public function testGetBar()
    {
        $response = $this->getRequest('/foo/bar/Pi', array('precision' => 2));
        $this->assertEquals(3.14, $response->getContent());
    }
}

自动初始化的虚构对象的服务容器配置

配置参数“fixture.path”(用于存储基于文件的固定值作为虚构对象)将根据测试类文件名和路径自动设置,以避免不同测试之间的冲突/依赖关系,并强制执行一致的命名方案。目录将自动创建。也可以使用“base.path”参数(指向“Tests”的父目录)。

示例容器配置(TestTools/Tests/config.yml

parameters:
    dbal.params:
        driver:         mysqli
        host:           localhost
        port:           3306
        dbname:         testtools
        charset:        utf8
        user:           testtools
        password:       testtools
        
services:
    dbal.driver:
        class: Doctrine\DBAL\Driver\Mysqli\Driver

    dbal.connection:
        class: TestTools\Doctrine\DBAL\Connection
        arguments:
            - %dbal.params%
            - "@dbal.driver"
        calls:
            - [setFixturePrefix, ['sql']]
            - [useFixtures, ["%fixture.path%"]]

当与固定值一起使用服务容器时,你不必关心不同环境(如开发和生产):配置详细信息(例如,登录凭证)仅对开发环境有效,因为服务/数据库请求应在第一次运行相应的测试之后由文件系统中的固定值替换。如果在Jenkins或Travis CI上测试失败,因为config.yml中的URL或凭证无效,你必须确保所有访问外部资源的代码都使用固定值(或模拟对象),并且所有固定值都已正确检查入。

Composer

如果你使用Composer,只需运行

composer require --dev lastzero/test-tools