【问题标题】:Test factory method测试工厂方法
【发布时间】:2015-09-14 20:34:53
【问题描述】:

编写测试以覆盖 100% 的代码是我们应该尝试实现的目标。但是我想出了我不知道如何测试方法(工厂方法)的情况:

public function getDocument(){
    $document = new Document();
    $document->settings(new Settings());
    $document->filesystem(new Filesystem('e:'));
    return $document;
}

该方法的目的是创建文档的快捷方式,而不是每次都写3行。

如何测试这个方法?

或者这可能是我们有@codeCoverageIgnoreStart 块的情况?正是因为这个原因,PHPUnit 才提供这种注解。


编辑: 这种方法背后的主要思想是让客户生活更轻松。仅此而已,无需配置等(但该方法将是这样做的好地方)。

//I don't want bother client with Settings() and Filesystem('e:')
$document = new Document(new Settings(), new Filesystem()); //NO
$document = Files.getDocument() //much easier and shorter.

//Changing API to getDocument($var, $var) make no sense, the same thing I could have normally.
$document = new Document(new Settings(),new Filesystem('e:'));

也许我应该考虑是否真的应该提供该方法,想要使用文档的用户应该知道依赖关系,它不应该被隐藏。

【问题讨论】:

  • 有什么问题?对于这种方法,需要测试文档是否有明确的设置和文件系统。
  • 当然,我现在就是这样。但是在单元测试中我们不应该依赖依赖,所以即使尚未定义 Settings 或 Filesystem 类,测试也应该通过。
  • @tne 说正确的答案。阅读依赖注入in this article

标签: php unit-testing phpunit


【解决方案1】:

这个方法有什么作用?返回初始化的Document 对象。因此,您只需验证返回的值是 Document 实例,并且它具有 SettingsFilesystem 对象集。如果你有这些的 getter 很容易,否则你必须访问相应的属性。

这个测试听起来很基础,但它确实测试了它需要做的事情。当您以注入设置和文件系统的方式重构代码时,测试仍会告诉您文档是否设置了这些属性。

之所以称为unit testing,是因为您正在测试unit,而不是对象或方法。如果您的单位有多个班级,那就顺其自然吧。不需要注入所有东西,也不需要模拟所有东西——这些东西可以简化测试,但在某些情况下最好不要模拟它们

【讨论】:

  • 我跳过测试这个方法。但可能我会使用 assertInstanceOf。我没有设置文件系统的 getter,因为它们不应该可以从 Document 访问。为了检查它,我将使用 ReflectionClass。到第二部分,我不知道......在我对单元测试的思考中(可能是错误的),我们不应该使用依赖项的原始实现(这就是我想摆脱的),都应该被嘲笑(存根,伪造)。我们测试“单元”,但必须在单独的环境中进行,我们可以轻松配置。测试这个特定的“单元”应该通过,而不管测试“单元”之外的任何事情。
  • @SonnyD 我建议你阅读 Steve Freeman 的Test Smell: everything is mocked(或者更好——他的书《Growing object-oriented software,guided by testing》)
  • 这是(真的)很好的建议。我认为是否要模拟的问题和是否反转依赖关系(例如注入)的问题是完全不同的。我建议 OP 评估 API 以决定是否要将其放在适配器后面(如果 API 可以最小化,则建议使用,否则只要接口稳定且最小化,使用模拟库可能更合适)。我同意,如果依赖项确实是没有或非常“静态”ctor 参数的值对象,则不应不必要地注入依赖项。在这种情况下,OP 至少希望注入 FS ctor 参数。
  • 好的,我会试着读你推荐的书。您的链接的示例表单类似于我的问题(设置类)。这个答案对我来说是最发人深省的。但是......最后我只是让这个方法未经测试。
【解决方案2】:

Inject your dependencies (Document, Settings, Filesystem) 通过构造函数,然后酌情使用test doubles

还要重新考虑您的 100% 覆盖政策,这绝对不是一件好事。

【讨论】:

  • 好的,我明白你的意思,这将允许我测试方法。但是每次创建文档时我都需要新实例。
【解决方案3】:

将依赖传递给工厂方法,初始化里面的新对象,并正确配置。在测试中,依赖项将是模拟而不是真实的对象。

方法一

传递允许创建依赖项的工厂:

public function getDocument(SettingsFactory $sf, FilesystemFactory $ff){
    $document = new Document();
    $document->settings($sf->getSettings());
    $document->filesystem($ff->getFilesystem());
    return $document;
}

在测试中,你应该:

  • 创建 Settings 实例或模拟和一个 SettingsFactory 模拟,期望调用一次 getSettings 并返回 Settings 实例
  • 创建Filesystem 实例或模拟和一个FilesytemFactory 模拟,期望调用getFilesystem 并返回Filesystem 实例
  • 调用DocumentFactory 方法,传递工厂。检查是否返回了 Document 对象
  • 检查分配给Document 的对象是否与您配置要返回的模拟对象相同

对此的变体getSettingsgetFilesystem 作为Document 工厂的方法。在这种情况下,您应该创建工厂的部分模拟,并对其设置期望。所以调用了真正的getDocument方法,但是当getSettingsgetFilesystem方法被调用时,你返回的是受控实例。

方法2

传递实际的依赖关系:

public function getDocument(Settings $settings, Filesystem $filesystem) {
    $document = new Document();
    $document->settings($settings);
    $document->filesystem($filesystem);
    return $document;
}

在测试中,你应该:

  • 创建Settings 实例或模拟
  • 创建Filesystem 实例或模拟
  • 调用DocumentFactory方法,传递SettingsFilesystem。检查是否返回了 Document 对象
  • 检查分配给Document 的对象是否与您传递给工厂方法的实例相同

【讨论】:

  • 你的想法不错,我理解。感谢您撰写答案,通常您的解决方案类似于我链接的那些表单 cmets:(再次执行此操作:stackoverflow.com/a/7763207/2490611)。我编辑了我的问题,请查看。
  • 可以使用一些库来管理依赖注入:github.com/ziadoz/awesome-php#dependency-injection
  • @SonnyD 如果您认为答案解决了您的问题,那么出于礼貌,您应该接受最佳答案,同时也是为了帮助该问题的未来观众知道正确的解决方案是什么。
【解决方案4】:

我找到了答案:此代码不可测试。

无论你在被测方法中哪里有 new XXX(...),你都注定要失败。

更多: https://stackoverflow.com/a/7763207/2490611

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-18
    • 2017-04-13
    • 2017-08-12
    • 2021-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多