【问题标题】:Testing unfriendly code测试不友好的代码
【发布时间】:2011-05-28 11:17:30
【问题描述】:

假设我有以下代码,无法修改:

class A {
    public doSomething() {
        // lots of business logic
        data = obtainData();
        // lots of other business logic which depends on data
    }

    private obtainData() {
        // connect to the database
        // send web requests
        // send manned missions to the moon
        // process the obtained data
        return data;
    }
}

测试此类代码的最佳做法是什么?我想确保doSomething() 做它应该做的事情,但我想为它提供已知数据,而不是在obtainData() 中运行代码。

【问题讨论】:

  • 私人而不是受保护...哎哟。 :-(
  • @Denis:私人有什么问题?
  • @zerkms:如果它受到保护,他可以声明一个扩展 A 的类 B 并重新定义 gainData()。根据他使用的语言,他可能无法使用私有方法来做到这一点。
  • 私有没问题,很多变量最好设置为不可触碰。
  • 我是亲私人的。只要有依赖注入。

标签: unit-testing testing


【解决方案1】:

查看Moles 以帮助您进行棕地测试。 C# 中的 Moles 允许您为私有代码和其他人的代码提供自己的实现。

当你有一套很好的测试来解决这个烂摊子时,请re-factor 拯救人类。

如果意大利面条密码的始作俑者触手可及,请替我打他,你为什么不呢?

如需最佳实践,请阅读鲍勃叔叔的清洁代码http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882

总结一下:如果被测代码具有外部依赖项,则使它们显式且可外部配置。

【讨论】:

  • “棕色”听起来恰到好处!不过,我要问的是一般的最佳实践。如何使用常规单元测试来测试这样的东西。
  • @rdineiu:原始问题不包括特定的语言或技术。我提供了一个通用的答案。
  • @GregC,没关系。通用是我所要求的。所以答案是,如果可能的话,最好的做法是去掉私有方法并用其他方法替换它,或者,如果不可能,不要测试。对吗?
  • @rdineiu:(我以为你站在我这边)测试,测试,再测试。如果无法进行测试,则说明您的工具很弱。
  • @GregC,(不是我投了反对票……)如果工具很弱(因为语言不支持这种深奥的魔法),请自己挖一个洞在空间/时间,或语言的工作方式,提取该死的方法?
【解决方案2】:

您没有发布有问题的代码,通常罪魁祸首是在构造函数中创建新对象、静态调用、常量的使用以及其他一些问题。

这个想法是将您的类与其依赖项分离。这样做的常用方法是依赖注入。

还可以考虑让较小的班级完成非常具体的任务,如果您在句子中用“和”来描述班级任务,那么它会做得太多并且更难测试。

请参阅this post 及其上的所有相关链接,了解有关测试静态和硬编码依赖项的信息。

【讨论】:

  • 我在看那个,我正在考虑通过最新的 PHP 5.3 提供的黑色巫术魔法来访问私有方法(我的代码恰好是 PHP),但这是正确的方法吗?处理这个?不知何故,它看起来......非常丑陋......你将如何使用一种不提供公开该私有方法所需的魔力的语言来做到这一点?或者你不会?
  • 我自己在这个确切的主题上发表了一些帖子。您永远不必测试您的私有方法和变量。有例外,但即使是那些也反映了糟糕的设计。您应该测试公共方法,这些将验证私有方法是否按预期工作。您将 1. 节省时间,2. 获得更有意义的代码覆盖率报告 3. 更多地享受单元测试方式。就像我说的那样,要在一个类上测试太多东西意味着它需要分成具有不同职责的更小的类,从而更容易模拟并减少该类的预期结果,从而减少测试。
  • 你不必说服我,我是依赖注入和解耦的粉丝。但不幸的是,我们并不生活在一个完美的世界中,有时我们需要继承糟糕的代码(当我说 bad 时,我很老练)。但它仍然需要测试......
【解决方案3】:

设计工作

  1. 确定这个类的职责。
  2. 接下来,提取该类的依赖项。将所有与项目符号#1 不一致的方法移动到依赖项中。

例如如果此类不负责处理数据库或网络 IO,则将它们提取为此类的依赖项。将它们作为 ctor args 或方法 args 注入(如果只有一个公共方法需要它)。

public A(DataRepository repository, WebService service, SpaceStation spaceStation)
{ // cache them as internal fields;}

现在没有必要为测试而存根或子类化并覆盖或增加成员可见性。

您的单元测试将创建一个实例

_testSubject = new A(new Mock<DataRepository>.object, new Mock<WebService>.object, new Mock<SpaceStation>.object);

而您的生产代码将使用上述角色的真实实现。

【讨论】:

  • 这个想法是将不友好的位/尴尬的依赖项移到可以在测试中模拟/替换的角色后面。
【解决方案4】:

你看过 Mocking 吗?它可能由您的单元测试框架提供。

【讨论】:

  • 我知道嘲笑。但是我将如何创建一个模拟来测试这个类?我应该模拟什么?
  • 部分模拟并非在所有测试固件中都可用。在phpunit中没有。
猜你喜欢
  • 2016-01-12
  • 2012-10-18
  • 2022-10-23
  • 2015-04-21
  • 2011-12-21
  • 2013-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多