16

我是单元测试和 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 + 工厂类:

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 现在会发生什么?

应该只是:

  • 不测试它,因为它还没有任何值得测试的应用程序逻辑,而工厂通常保持这种状态。假设如果房屋、门和旋钮的独立测试通过了工厂应该没问题。
  • 以某种方式重构工厂,即使用类中的函数来获取每个实例,这样可以通过 PHPUnit 覆盖这些函数以返回模拟对象,以防万一类中有一些额外的逻辑可以使用一些测试未来。

2)一次设置依赖于多个(非模拟)依赖项的测试是否可行?我知道这在技术上不是单元测试(也许是集成测试?)但我想使用 PHPUnit 仍然完全可行?鉴于上面的示例,我希望能够设置一个测试,不仅可以单独测试 House、Door、Knob 和 HouseFactory,还可以测试真实对象彼此交互的结果,也许还有它们的一些模拟的函数,例如处理数据的函数。PHPUnit 对于这种测试来说是一个糟糕的选择吗?

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

4

2 回答 2

4

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

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


编辑- 由于评论中有一些讨论:

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

这没有什么问题,但是通常没有必要,因为返回类型的构造函数已经在单元测试中,而且测试真的很简单,只是数据检查,闻起来像集成测试。如果无法提供依赖项,那些将工厂中的类型作为依赖项(并且也在单元测试中)的类型也会使编译/执行失败。因此,工厂的一切都已经过测试,甚至来自双方。如果工厂没有被消耗,那么你就不需要测试它。

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

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

于 2012-04-12T17:45:02.653 回答
0

您可以使用继承对其进行测试。

只需用 FakeHouse 扩展 House 进行测试,然后检查 $material、$door 和 $knob 等在测试后是否发生了变化。

于 2016-04-23T16:46:59.630 回答