icecave/isolator

全局函数的依赖注入。

3.0.3 2015-03-27 05:04 UTC

This package is auto-updated.

Last update: 2024-08-29 04:04:00 UTC


README

Build Status Code Coverage Latest Version

Isolator 通过将所有全局函数视为“隔离器”对象上的方法,简化了使用全局函数的类的测试。

composer require icecave/isolator

理由

许多 PHP 扩展(以及 PHP 核心本身)都将其功能实现为全局函数。由于无法用测试替身替换这些函数,因此测试使用这些函数的类很快就会变得困难。

Isolator 通过充当您的类和全局函数之间的代理来尝试解决这个问题。将隔离器实例作为依赖项传递到您的对象中,并在测试时替换任何您希望替换的全局函数调用。

示例

以下类使用 file_get_contents() 来读取文件内容。

class MyDocument
{
    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    public function getContents()
    {
        return file_get_contents($this->filename);
    }

    protected $filename;
}

尽管示例很简单,但由于该类依赖于文件系统,因此立即变得难以测试。为了测试此类,您可能倾向于在磁盘上设置一些静态的固定值,在设置测试套件时创建一个临时目录,或者甚至使用虚拟文件系统包装器

Isolator 提供了第四种选择。下面是使用 Isolator 实例重写的相同示例。

use Icecave\Isolator\Isolator;

class MyDocument
{
    public function __construct($filename, Isolator $isolator = null)
    {
        $this->filename = $filename;
        $this->isolator = Isolator::get($isolator);
    }

    public function getContents()
    {
        return $this->isolator->file_get_contents($this->filename);
    }

    protected $filename;
    protected $isolator;
}

MyDocument 现在接受一个 Isolator 实例作为其构造函数的一部分。在您的生产代码中每次创建对象时都创建一个新的 Isolator 实例会是一件痛苦且不必要的事情,因此使用 Isolator::get() 方法使共享实例可用。如果将非空值传递给 Isolator::get(),则返回值不变,允许您在必要时替换隔离器。

MyDocument::getContents() 也已更新为使用隔离器实例而不是直接调用全局函数。MyDocument 的行为保持不变,但测试该类变得容易,如下面的示例测试套件所示。

注意:以下测试是为 PHPUnit 测试框架编写的,使用 Phake 进行模拟。Phake 提供了比 PHPUnit 内置模拟对象更灵活的替代方案。

class MyDocumentTest extends PHPUnit\Framework\TestCase
{
    public function setUp()
    {
        // First a mocked isolator instance is created ...
        $this->isolator = Phake::mock('Icecave\Isolator\Isolator');

        // That isolator instance is given to the MyDocument instance
        // that is to be tested ...
        $this->myDocument = new MyDocument('foo.txt', $this->isolator);
    }

    public function testGetContents()
    {
        // Phake is used to configure the mocked isolator to return a known
        // string when file_get_contents() is called with a parameter equal
        // to 'foo.txt' ...
        Phake::when($this->isolator)
          ->file_get_contents('foo.txt')
          ->thenReturn('This is the file contents.');

        // MyDocument::getContents() is called, and it's result checked ...
        $contents = $this->myDocument->getContents();
        $this->assertEquals($contents, 'This is the file contents.');

        // Finally Phake is used to verify that a call to file_get_contents()
        // was made as expected ...
        Phake::verify($this->isolator)
          ->file_get_contents('foo.txt');
    }
}

该测试验证了 MyDocument 类的行为,而无需任何磁盘访问。

在测试使用保持全局状态或利用数据库、文件系统等外部资源的函数的代码时,使用隔离器最有帮助。通常不需要模拟像 strlen() 这样的确定性函数。

Isolator 特性

在 PHP 5.4 及更高版本中,您还可以使用 IsolatorTrait 将隔离器引入到您的类中。通过 $this->isolator() 访问隔离器实例,并通过 $this->setIsolator() 设置。

use Icecave\Isolator\IsolatorTrait;

class MyDocument
{
    use IsolatorTrait;

    public function __construct($filename)
    {
        $this->filename = $filename;
    }

    public function getContents()
    {
        return $this->isolator()->file_get_contents($this->filename);
    }

    protected $filename;
}

语言构造

Isolator 还可以用于调用以下类似于函数的语言构造

  • includeinclude_oncerequirerequire_once
  • exitdie
  • echo
  • eval
  • new

特殊性

PHP的一些核心全局函数在定义方式上存在一些特殊性和不一致性。《隔离器》尝试在可能的情况下适应这种不一致性,但可能与一些参数反射信息非标准或不正确的本地C函数存在问题。这些问题似乎在PHP 5.6版本中得到大量修正。