【问题标题】:Performing unit testing with nested dependencies and Factory classes使用嵌套依赖项和工厂类执行单元测试
【发布时间】:2012-04-25 02:33:06
【问题描述】:

我是单元测试和 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 现在会发生什么?

应该只是:

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

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

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

【问题讨论】:

    标签: php unit-testing design-patterns phpunit


    【解决方案1】:

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

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


    编辑 - 因为在 cmets 中有一些讨论:

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

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

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

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

    【讨论】:

    • 工厂不像new关键字。它可以被测试。 HouseFactory::create 应该怎么做?它创建并返回 House。您可以并且应该至少使用单元测试或断言来测试它。 OP 的例子很简单,但是如果你的方法中有任何逻辑,就应该对其进行测试。
    • 另外,你不能在 PHP 中测试 new 关键字(因为它不可重载),但你可以在 C++ 中重载它,如果你这样做的话,你应该测试它。
    • 由于house的构造函数已经过单元测试(按照TDD),所以不需要太多的正常测试工厂方法。你可以测试一切,问题是它是否有意义并且真的有必要。集成测试可以涵盖工厂的使用情况,因此如果工厂没有达到预期目的,则可以退出。
    • 我的意思是你没有测试构造函数或任何东西。您正在测试以确保该方法(在工厂中)返回它应该返回的内容,就像所有单元测试一样。
    • 是的,但是您可以通过简单的测试从源头上捕获该问题,而不是分析可能很长的堆栈跟踪,因为在此过程中某个地方出现了可捕获的致命错误。这就是单元测试的用途,不是吗? :)
    【解决方案2】:

    你可以用继承来测试它。

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

    【讨论】:

      猜你喜欢
      • 2013-05-10
      • 2017-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-23
      • 2015-10-18
      相关资源
      最近更新 更多