尊重/测试

在尊重,我们对测试非常认真,这就是严肃的测试项目。

dev-master 2013-02-22 02:06 UTC

This package is not auto-updated.

Last update: 2024-09-14 12:37:48 UTC


README

Latest Stable Version Total Downloads Latest Unstable Version License

简介

正如其名,这是我们将单元测试做得令人惊叹的地方。通常情况下,我们需要特定的平台或系统,我们的任务不是孤立的。通常,编写模拟、猴子补丁、polyfills 和 shims 的纯任务比它们旨在测试的工作更麻烦。

为了简化我们的测试流程,这些工具应运而生,现在你也可以从这些劳动中受益,并深入了解为什么我们认为测试如此酷。

随着更多项目的加入,本文档将逐步成形,最终这段话将与空白一起消失。请自由地成为尊重的另一个出色项目的一部分。

反思

无需麻烦即可访问类或对象属性,Reflect 使您能够清楚地了解您是在访问或更改类或对象的属性,或者这些属性是静态的还是实例变量,它们是公共的、私有的还是受保护的。对我们来说,这一切都是一样的。

示例类和对象实例

    use Respect\Test\Reflect;

    class HappyPanda
    {
        private $p = 'private';
        protected $pr = 'protected';
        public $pu = 'public';
    }

    $hp = new HappyPanda();

Reflect::on

要获取 Respect\Test\Reflect 助手的一个实例,请调用静态 on 方法,并提供一个对象或类名字符串。

    $reflect = Reflect::on($hp);

    /** or */

    $reflect = Reflect::on('HappyPanda');

getProperty($name)

getProperty 方法将返回命名属性的值。

    echo $reflect->getProperty('pu');

    // public

但鉴于流畅的接口设计,要使用实例对象从我们的 HappyPanda 获取属性,只需写一行代码即可。

    echo Reflect::on($hp)->getProperty('p');

    // private

我们可以用同样的方式只使用类名。

    echo Reflect::on('HappyPanda')->getProperty('pr');

    // protected

setProperty($name, $value)

所以你想更改属性,对吧?这就是测试的全部意义所在。

    $reflect->setProperty('pu', 1234);
    echo $reflect->getProperty('pu');

    // 1234

或者通过链式调用,让您再次可以在一行中组合所有内容。

    echo Reflect::on($hp)->setProperty('p', 'owned')->getProperty('p');

    // owned

我们可以用同样的方式只使用类名,我告诉过你什么。

    echo Reflect::on('HappyPanda')->setProperty('pu', 'easy')->getProperty('pr');

    // easy

getInstance()

正如你可能知道的,我们需要一个实例(对象)来修改这些实例属性,因此您可能最终想要获取该实例本身。无论您的类是抽象的、没有构造函数、带有必需参数的构造函数,还是标记为私有或受保护的,Reflect 都会确保您无论付出什么代价都能获取一个实例。

让我们看看一个新的类定义,它有一个需要 2 个非可选参数的私有构造函数。我们还加载了一些静态属性,但对我们来说,现在这些都一样。

    class Panda
    {
        private $p = 'private';
        protected $pr;
        public $pu;
        private static $ps;
        protected static $prs;
        public static $pus;

        private function __construct($a, $b)
        {
        }
    }

最大限度地利用流畅的接口!

    $object = Reflect::on('Panda')
        ->setProperty('p', 1)
        ->setProperty('pr', 2)
        ->setProperty('pu', 3)
        ->setProperty('ps', 4)
        ->setProperty('prs',5)
        ->setProperty('pus',6)
        ->getInstance();

这将为您提供一个具有每个属性都已更改的新 Panda 类实例,并将其分配给变量 $object。看起来像这样

    class Panda
    {
        private $p = 1;
        protected $pr = 2;
        public $pu = 3;
        private static $ps = 4;
        protected static $prs = 5;
        public static $pus = 6;

        private function __construct($a, $b)
        {
        }
    }

让我们把事情做得更复杂一点,如何关于一个抽象类?当你对抽象类进行反射时,事情会有所不同。我们无法拥有一个抽象类的实例,这是不可能的。让我们看看会发生什么。

让我们使 Happy Panda 成为抽象的

    abstract class Panda
    {
        private $val = 'private';

        abstract protected function eatBamboo(array $sticks=array());

        private function __construct(&$a, $b)
        {
            $a++;
            $this->val = $b;
        }
    }

我们对抽象类进行反射,并检索实例。

    $object = Reflect::on('Panda')->getInstance();

Reflect 将为您生成一个 Mock 类,这样您就可以有一个有效的抽象类实例进行测试。Mock 类将位于相同的命名空间中(如果适用),并在类名前加上 "Mock"。

    class MockPanda extends Panda
    {
        public function eatBamboo($sticks=array ())
        {
        }
    }

如您所见,我们可以从私有构造函数中获取抽象类的实例,但如果我们想执行该构造函数呢?没问题,只需传递一个参数数组给 getInstance(),Reflect 将为您调用私有构造函数。 注意:示例使用 PHP 5.4 中引入的新数组简写,而 PHP 5.3 使用 array() 而不是 [ ]

    echo Reflect::on('Panda')->getProperty('val');

    // private

    $object = Reflect::on('Panda')->getInstance([&$a, 'New Value']);

    echo Reflect::on('Panda')->getProperty('val');

    // New Value

    echo $a;

    // 1

    $object = Reflect::on('Panda')->getInstance([&$a, 'New Value']);

    echo $a;

    // 2

    $object = Reflect::on('Panda')->getInstance([&$a, 'New Value']);

    echo $a;

    // 3

StreamWrapper

PHP 手册中提到,关于 StreamWrapper,请注意。

这并不是一个真正的类,而是一个定义自己协议的类的原型。

如果您也同意,这很糟糕。难道不更简单直接有一个这个 真正的 类吗?好吧,我们也是这样想的,这就是它,StreamWrapper,别无他名。

与难以驾驭的接口斗争,文档稀少,实现特定且紧密地嵌入到 PHP 解释器的每个方面。那些日子终于结束了。

无需努力,无缝集成到内置默认流包装器 (file://),用作测试中的文件系统模拟,不再流泪。创建、修改、移动、删除、链接到,从默认数据协议的便利性中进行所需的一切,与物理对应物完全协同,您将无法分辨它们。

如果还不够酷,那么怎样呢?

  • 可配置的虚拟文件,数据来自 PHP 字符串变量。
  • 读写查找虚拟文件,与真实文件无法区分。
  • 无路径限制,我们将为您填写目录。
  • 准确使用标准 stat 功能,如验证存在、查询类型、打开资源进行读取、写入、修改,甚至将虚拟文件添加到现有文件夹中。
  • 零配置自我管理,无需您做任何事情。
  • 零维护,因为它会在自己清理后结束。
  • 最小开销,因为它只在预期的地方干预。
  • 使用起来如此简单,您会忘记它甚至在那里。

清单可以一直继续下去,但您最好亲自看看。

如何使用 StreamWrapper

只需将库添加到您的包含路径中,配置几个文件(或根本不添加文件)以注入到虚拟文件系统中,我们就完成了。

其余的将由您负责。

我们追求简单的设计,结果是 两个方法 的接口。

StreamWrapper::setStreamOverrides($virtual_fs)

第一个可用方法 setStreamOverrides 允许您通过将 path 映射到 contents 来配置启动文件系统。

文件内容可以是简单的字符串,也可以是映射整个资源作为内容提供者的复杂结构。

    StreamWrapper::setStreamOverrides(array(
      'virtual/foo-bar-baz.ini' => $my_foo_here_doclet,
      'virtual/happy-panda.ini' => "panda=happy\nhappy=panda",

      'virtual/custom-stream.ini'=> fopen('data:text/plain;base64,'.
               urlencode('Sweet like a lemon'), 'wb'),
      'virtual/custom-stream-base64.ini'=> fopen('data:text/plain;base64,'.
               base64_encode('Sweet like a lemon'), 'wb'),
    ));

StreamWrapper 会处理自己的事情,所以您不需要。

一旦 PHP 脚本运行结束,StreamWrapper 将释放其资源,并在退出时优雅地消失。

您不需要做任何事情。

要启用无启动文件的裸骨虚拟文件系统,只需使用空数组。

    StreamWrapper::setStreamOverrides(array());

您可以重复此过程,每次 StreamWrapper 都将清除当前状态,并展示新的配置文件系统,同时确保我们之前使用的资源得到适当的释放。

在我们讨论下一个和最终的 接口方法 # 2 之前,让我们快速看看我们有什么。

如常的 I/O

可以使用您最喜欢的 PHP 文件系统 函数创建额外的虚拟项目,例如使用 mkdir 创建新目录或使用 file_put_contents 填充新文件。

要创建一个包含字符串 "文件将在任何位置创建并可用" 的文本文件,并将其内容保存到当前工作目录相对路径的 it/doesnt/matter/if/path/not/exist.txt

    file_put_contents('it/doesnt/matter/if/path/not/exist.txt',
       'The file will be created and accessible at the location');

    print_r(file('it/doesnt/matter/if/path/not/exist.txt'));

    Array
    (
        [0] => The file will be created and accessible at the location
    )

但您还可以访问所有中间目录,它们是完全可遍历的。

让我们尝试这个简单的递归。

    function traverse($path) {
        echo "$path \$\n":
        foreach (new DirectoryIterator($path) as $i) {
            echo $i->getBasename(), PHP_EOL;
            if ($i->isDir())
                traverse($i->getPathname());
            else
                break;
        }
    }

    traverse('it');

看看我们得到了什么?


    it $
    .
    ..
    doesnt
    it/doesnt $
    .
    ..
    matter
    it/doesnt/matter $
    .
    ..
    if
    it/doesnt/matter/if $
    .
    ..
    path
    it/doesnt/matter/if/path $
    .
    ..
    not
    it/doesnt/matter/if/path/not $
    .
    ..
    exist.txt

这听起来太完美了,当然,让我们看看外壳在PHP之外会说什么。


$ ls it
    ls: it: No such file or directory

用法与之前没有变化。

对于任何新内容,StreamWrapper将创建虚拟资源,并使它们透明地可用,就像它们是真实的一样。

否则,一切照旧,回退到标准 file:// 流包装协议的内置功能,允许像以前一样访问物理资源。

    var_export(scandir('tests'));
    array (
      0 => '.',
      1 => '..',
      2 => 'bootstrap.php',
      3 => 'library',
      4 => 'phpunit.xml',
    )
    echo file_get_contents('tests/phpunit.xml');

    <!-- a Courtesy of Respect/Foundation -->
    <phpunit backupGlobals="false"
             backupStaticAttributes="false"
             bootstrap="bootstrap.php"
    ....

StreamWrapper::releaseOverrides()

在虚拟世界中会话结束后,您可能想恢复正常,这就是 releaseOverrides 方法的作用。

    StreamWrapper::releaseOverrides();

我们简单地释放引用句柄,并允许StreamWrapper以自己的速度开始清理过程。

如果您发现它太长时间才允许您再次使用默认文件系统,您可以坚持立即通过调用PHP函数来恢复标准流包装器

    stream_wrapper_restore('file');

但这不是必需的,所有东西最终都会恢复正常。

免责声明

这个源代码是最新发布的,尽管它已经工作,但积极开发可能会再次引发一些火花。我们当然没有探索所有边缘情况,您可能还会发现尚未发现的错误。

如果您读到这儿,您就知道您找到了测试的圣杯,这是真的。

请帮助我们,通过报告任何问题或提出建议,如果它还没有做到您希望的那样精确。这是提出这些想法并看到它们实现的最佳时机。

现在正在接受问题和拉取请求...