使用嵌套依赖项和Factory类执行单元测试

时间:2012-04-12 17:34:09

标签: php unit-testing design-patterns phpunit

我是单元测试和PHPUnit的新手,但我最近读了很多关于设计模式和隔离测试的内容,我决定重构一个我正在努力摆脱静态类,单例的应用程序,硬编码的依赖关系以及在全局范围内定义的任何其他内容,希望使其“可测试”,并且不会在将来保留麻烦,因为它本来是一个长期项目。

到目前为止,我相信我理解单元测试背后的理论,但我想知道,在一个委托处理对象的嵌套依赖关系到一个工厂的场景中,应该如何进行单元测试所述工厂,或者它只是冗余测试一下?什么是测试依赖关系“链”同步工作的最佳方法?

让我来说明问题。假设您有以下“遗留”代码:

class House {
    protected $material;
    protected $door;
    protected $knob;

    public function __construct() {
        $this->door = new Door();
        $this->knob = $this->door->getKnob();
        $this->material = "stone";

        echo "House material: ".$this->material . PHP_EOL . "<br/>";
        echo "Door material: ".$this->door->getMaterial() . PHP_EOL . "<br/>";
        echo "Knob material: ".$this->knob->getMaterial() . PHP_EOL . "<br/>";
    }
}

class Door {
    protected $material;
    protected $knob;

    public function __construct() {
        $this->knob = new Knob();
        $this->material = "wood";
    }

    public function getKnob() {
        return $this->knob;
    }

    public function getMaterial () {
        return $this->material;
    }

}

class Knob {
    protected $material;

    public function __construct() {
        $this->material = "metal";
    }

    public function getMaterial () {
        return $this->material;
    }
}

$house = new House();

这是(据我的理解)对单元测试不利,所以我们用DI + Factory类替换硬编码的依赖项:

class House {
    protected $material;
    protected $door;
    protected $knob;

    public function __construct($door) {
        $this->door = $door;
        $this->knob = $this->door->getKnob();
        $this->material = "stone";

        echo "House material: ".$this->material . PHP_EOL . "<br/>";
        echo "Door material: ".$this->door->getMaterial() . PHP_EOL . "<br/>";
        echo "Knob material: ".$this->knob->getMaterial() . PHP_EOL . "<br/>";
    }
}

class Door {
    protected $material;
    protected $knob;

    public function __construct($knob) {
        $this->knob = $knob;
        $this->material = "wood";
    }

    public function getKnob() {
        return $this->knob;
    }

    public function getMaterial () {
        return $this->material;
    }

}

class Knob {
    protected $material;

    public function __construct() {
        $this->material = "metal";
    }

    public function getMaterial () {
        return $this->material;
    }
}

class HouseFactory {
    public function create() {
        $knob = new Knob();
        $door = new Door($knob);
        $house = new House($door);

        return $house;
    }
}

$houseFactory = new HouseFactory();
$house = $houseFactory->create();

现在(并且据我所知),House,Door和Knob可以通过模拟依赖关系进行单元测试。但是:

1)HouseFactory现在会发生什么?

应该只是:

  • 没有测试它,因为它没有任何值得测试的应用程序逻辑,而且工厂通常保持这种方式。假设如果独立测试House,Door&amp;旋钮通过工厂应该没问题。
  • 以某种方式重构工厂,即使用类中的函数以这样的方式获取每个实例,即可以通过PHPUnit覆盖这些函数以返回模拟对象,以防万一在类中有一些额外的逻辑可以使用将来会进行一些测试。

2)设置依赖于多个(非模拟)依赖项的测试是否可行?我知道这在技术上不是单元测试(也许是集成测试?)但是我想它仍然可以使用PHPUnit完美地完成?鉴于上面的例子,我希望能够设置一个测试,不仅可以隔离测试House,Door,Knob和HouseFactory,还可以测试真实物体之间相互作用的结果,也许还有一些模拟的函数,例如处理数据的函数。 PHPUnit对于这种测试是不是一个好的选择吗?

提前感谢您的时间。我意识到我所做的一些假设可能不正确,因为我显然不是这方面的专家;我们欢迎并赞赏更正。

2 个答案:

答案 0 :(得分:4)

工厂就像new关键字一样。您是否测试了new关键字?不,你测试你是否可以构建一个类。但这是独立于工厂本身和部分单元的,因此已经是单元测试的一部分。

2)称为集成测试。你也可以用PHPUnit做到这一点。


  

修改 - 由于评论中有一些讨论:

就单元测试而言,您可以对工厂进行单元测试,以确定它的用途:返回具体类型,类型或任何类型。

没有任何问题,但是通常没有必要,因为返回类型的构造函数已经在单元测试中,并且测试实际上是微不足道的,只是数据检查有点像集成测试。此外,那些在工厂中具有该类型作为依赖关系的类型(以及在单元测试下也是如此)将使编译/执行失败,如果无法提供依赖关系。所以工厂的一切都已经过测试,即使是双方都已经过测试。如果工厂没有消耗掉,那么你就不需要进行测试了。

我建议您创建一个纯粹TDD样式的工厂,以便预先制定使用,然后您会对此感觉到。您可能想要测试工厂类的其他方面,但这可能更多地属于集成而不是单元测试。

而且我不想让人觉得其他单位实际上应该对工厂创建方法进行硬编码调用,而不是注入依赖项。由于您不应在单位内使用new,因此您也不应在其中使用Factory::create。与new类似,类名(Factory)是硬编码的,不是注入的。那是一种隐藏的依赖。但是不应该隐藏依赖关系;但是可见。

答案 1 :(得分:0)

你可以用继承来测试它。

只需用FakeHouse扩展House进行测试,然后检查$ material,$ door和$ knob等,测试后它们是否已经改变。