如何测试工厂类?

时间:2013-11-30 15:47:06

标签: php unit-testing design-patterns testing

鉴于此课程:

class MyBuilder {
    public function build($param1, $param2) {

        // build dependencies ...

        return new MyClass($dep1, $dep2, $dep3);
    }
}

我如何对这个班级进行单元测试?

单元测试意味着我想测试它的行为,所以我想测试它用正确的依赖项构建我的对象。但是,new指令是硬编码的,我不能嘲笑它。

现在,我已经将类的名称添加为参数(因此我可以提供模拟类的类名),但它很难看:

class MyBuilder {
    public function build($classname, $param1, $param2) {

        // build dependencies ...

        return new $classname($dep1, $dep2, $dep3);
    }
}

是否有清洁的解决方案或设计模式使我的工厂可测试?

3 个答案:

答案 0 :(得分:3)

工厂本质上是可测试的,你只是试图对实现过于严格控制。

您将检查是否通过$this->assertInstanceOf()获得了课程的实例。然后使用生成的对象,确保正确设置属性。为此,您可以使用任何公共访问器方法或使用PHPUnit中可用的$this->assertAttribute*方法。

http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions.assertEquals

许多常见断言还能够检查受保护属性和私有属性的属性。

我不会在参数列表中指定类名,因为您的用法是工厂只返回一种类型,而且只是更改的依赖项。使其返回模拟对象类型是不必要的,并使您的测试更复杂。

测试最终看起来像这样:

public function testBuild() {
    $factory = new MyBuilder();

    //I would likely put the following into a data provider
    $param1 = 'foo';
    $param2 = 'bar';

    $depen1 = 'boo';
    $depen2 = 'baz';
    $depen3 = 'boz';

    $object = $factory->build($param1, $param2);

    $this->assertInstanceOf('MyClass', $object);

    //Check the object definition
    //This would change depending on your actual implementation of your class
    $this->assertAttributeEquals($depen1, 'attr1', $object);
    $this->assertAttributeEquals($depen2, 'attr2', $object);
    $this->assertAttributeEquals($depen3, 'attr3', $object);
}

您现在确保工厂返回正确的对象。首先要确保它是正确的类型。然后确保它已正确初始化。

你依靠MyClass的存在来通过测试,但这不是一件坏事。您的工厂旨在创建MyClass个对象,因此如果该类未定义,那么您的测试肯定会失败。

在开发过程中进行测试失败也不是一件坏事。

答案 1 :(得分:1)

那么你想测试什么?

  

所以我想测试它使用正确的依赖项构建我的对象。

我确实看到了这个问题。你有可能创建一个具有不正确依赖关系的对象(首先不应该是这种情况,或者在其他测试中测试,而不是在工厂中测试),或者你想要测试你不应该测试的工厂细节所有

否则 - 如果它不是在嘲笑你正在寻找的工厂 - 我认为没有理由为什么简单

$actual = $subject->build($param1, $param2);
$this->assertInstanceOf('MyClass', $actual);

不会成功。它测试工厂构建方法的行为,它返回正确的类型。

另见Open-Close-Principle


对于测试,您可以创建从您的Builder扩展的MockBuilder:

class MyMockBuilder extends MyBuilder {
    public function build($param1, $param2) {

        // build dependencies ...

        return new MyMockClass($dep1, $dep2, $dep3);
    }
}

让classname成为参数1:1似乎对我来说不实用,因为它将工厂变成了不同的东西。创造是工厂的细节,没有你外化。所以它应该被封装。因此MockBuilder用于测试。你切换工厂。

答案 2 :(得分:0)

在我看来,你需要为该构建器验证两件事:

  • 返回正确的实例
  • 注入的
  • 值是正确的值。

检查实例很容易。验证值需要一些技巧。

  

执行此操作的简单方法是更改​​自动加载器。您需要确保在请求自动加载器MyClass时,而不是/src/app/myclass.php文件加载/test/app/myclass.php,它实际上包含一个“透明”模拟(您可以使用简单的getter验证值。)

     

坏主意

<强>更新

此外,如果您不想使用自动加载器,您可以在myBuilderTest.php文件的顶部包含模拟类文件,其中包含MyClass的定义。

......这实际上似乎是一种更清洁的方式。

namespace Foo\Bar;
use PHPUnit_Framework_TestCase;

require TEST_ROOT . '/mocks/myclass.php'

class MyBuilderTest extends PHPUnit_Framework_TestCase
{
    public function MyBuilder_verify_injected_params_test()
    {
         $target = new MyBuilder;
         $instance = $target->build('a', 'b');

         $this->assertEquals('a', $instance->getFirstConstructorParam(); 
    }
}