【问题标题】:How to unit test code that is highly complex behind the public interface如何对公共接口后面高度复杂的代码进行单元测试
【发布时间】:2010-01-06 16:54:45
【问题描述】:

我想知道我应该如何通过 NUnit 测试这种功能。

Public void HighlyComplexCalculationOnAListOfHairyObjects()
{
    // calls 19 private methods totalling ~1000 lines code + comments + whitespace
}

从阅读中我看到 NUnit 不是为了测试私有方法而设计的,因为哲学原因是关于单元测试应该是什么;但是尝试创建一组完全执行计算中涉及的所有功能的测试数据几乎是不可能的。同时,计算被分解为一些相当离散的较小方法。然而,它们不是合乎逻辑的事情,可以彼此独立完成,因此它们都被设置为私有。

【问题讨论】:

  • 正如其他人所指出的:如果一个类很难测试,这通常表明该类试图做太多事情。如果计算可以分解为更小的方法,是否可以将其进一步分解为单独的阶段,每个阶段由其自己的类/接口表示?或者,如果计算主要由一堆复杂的公式组成,你可以有一个专门的 Math 类,其中有一堆静态方法来实现每个公式。
  • 在当前范围/预算/时间之外进行一定程度的重构。
  • 丹 - 你最后做了什么?我们遇到了同样的问题。
  • 我最终链接到 Visual Studio 测试框架并添加“使用 PrivateObject = Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject;”到我的测试项目,这样我就可以访问私有成员,而无需编写自己的反射类。然后,我在调用树的底部添加了一些私有方法的低级测试,以补充运行顶级公共方法的高级测试。目前没有明确涵盖中间层方法的测试。稍后我可能会添加它们,但创建输入所需的设置量令人望而却步。
  • 如下所述,这不是理想的选择,但不存在用于重大重新设计的资金,所以我正在尽我所能。当有额外资金可用时,或针对发现的任何具体问题,将考虑对测试进行未来改进。

标签: c# unit-testing nunit


【解决方案1】:

你把两件事混为一谈了。接口(可能暴露的很少)和这个特定的实现类,可能会暴露更多。

  1. 定义尽可能窄的接口。

  2. 使用可测试(非私有)方法和属性定义实现类。如果课程有“额外”的东西也没关系。

  3. 所有应用程序都应该使用接口,因此 - 对类的公开功能没有类型安全的访问权限。

如果“某人”绕过接口直接使用类怎么办?他们是反社会者——你可以放心地忽略他们。不要为他们提供电话支持,因为他们违反了使用接口而不是实现的基本规则。

【讨论】:

  • 为了方便测试而公开方法感觉不对。
  • @Dan Neely:(1) 这就是为什么它是测试驱动设计。你几乎必须这样做。更重要的是。 (2) 这就是为什么接口和实现是分开的。接口什么也不暴露。并且 (3) 在 Python 中,我们不太关心“私人”,我们非常高兴和成功。从长远来看,“曝光”并不重要。
  • @S. Lott - 一个问题,如果您以前的私有方法调用其他私有方法,这有什么帮助?即使您将所有这些公开,您也没有它们的接口,因此您无法模拟它们(除非您也将它们设为虚拟,这似乎有点奇怪)。有什么想法吗?
  • 实现(不是接口)很少使用私有方法。此外,该实现还具有“官方”API(可测试部分)的方法,与更私有的实现细节不同。您不必测试 every 方法。您只需测试“官方”接口的方法。如果您无法区分,请取消 private 并使用界面提供您真正需要的所有隐私。
【解决方案2】:

要解决您当前的问题,您可能需要查看Pex,这是来自 Microsoft Research 的工具,它通过查找所有相关边界值来解决此类问题,以便可以执行所有代码路径。

也就是说,如果您使用了测试驱动开发 (TDD),您将永远不会遇到这种情况,因为编写驱动这种 API 的单元测试几乎是不可能的。

像您描述的那种方法听起来像是试图一次做太多事情。 TDD 的主要优势之一是它促使您从小的、可组合的对象而不是具有不灵活接口的大类来实现您的代码。

【讨论】:

  • +1 表示 TDD。正如 Mark 所说,先写测试,你不会到此为止。
  • 桥下的水。有问题的代码是在团队对任何类型的自动化测试产生任何官方兴趣之前很久就编写的。
  • Pex 看起来很有趣,但商业评估版需要 VS 的团队版版本,我团队中的每个人都只有 2k8Pro。
【解决方案3】:

如前所述,InternalsVisibleTo("AssemblyName") 是测试遗留代码的好起点。

Internal 方法仍然是私有的,因为当前程序集之外的程序集无法看到这些方法。更多信息请查看MSDN

另一件事是将大方法重构为更小、更明确的类。检查这个问题,我问了一个类似的问题,testing large methods

【讨论】:

    【解决方案4】:

    就我个人而言,我会在内部制作组成方法,应用 InternalsVisibleTo 并测试不同的位。

    白盒单元测试当然仍然有效 - 尽管它通常比黑盒测试更脆弱(即,如果您更改实现,您更有可能不得不更改测试)。

    【讨论】:

    • 代码已经存在并投入生产,除非发现任何错误,否则目前不太可能发生更改。在挥手级别,该算法正在执行类似于有损压缩的操作,因为根据实现的不同,对于给定的输入,有多个可能的输出是“正确的”并且任何重大变化。因此,任何重大更改都可能会破坏黑盒测试。
    【解决方案5】:

    HighlyComplexCalculationOnAListOfHairyObjects() 是一种代码异味,表明包含它的类可能做的太多,应该通过Extract Class 重构。这个新类的方法是公开的,因此可以作为单元进行测试。

    这种重构的一个问题是原始类拥有很多新类需要的状态。这是另一种代码异味,表明应将状态移动到值对象中。

    【讨论】:

      【解决方案6】:

      我见过(并且可能写过)很多这样的头发物品。如果它很难测试,它通常是重构的好选择。当然,这样做的一个问题是重构的第一步是确保它首先通过所有测试。

      不过,老实说,我想看看是否有什么方法可以将代码分解成更易于管理的部分。

      【讨论】:

        【解决方案7】:

        获取 Michael Feathers 的书 Working Effectively with Legacy Code。我已经完成了大约三分之一,它有多种技术可以处理这些类型的问题。

        【讨论】:

        • 第二个。它是从未经测试和看似不可测试的代码中构建测试代码孤岛的绝佳资源。一旦你有足够的测试,你将能够修复和重构现有的代码库。
        【解决方案8】:

        您的问题暗示整个子系统有许多执行路径。脑海中浮现的第一个想法是“重构”。即使您的 API 仍然是单一方法接口,测试也不应该是“不可能的”。

        【讨论】:

          【解决方案9】:

          尝试创建一组测试数据 完全执行了所有 涉及的功能 计算几乎是不可能的

          如果这是真的,请尝试一个不那么雄心勃勃的目标。首先通过代码测试特定的高使用率路径、您怀疑可能脆弱的路径以及您已报告错误的路径。

          将方法重构为单独的子算法将使您的代码更具可测试性(并且可能在其他方面有益),但如果您的问题是这些子算法之间的交互数量荒谬, extract 方法(或提取到策略类)并不能真正解决它:您必须一次构建一套可靠的测试。

          【讨论】:

          • 在某种程度上我们已经拥有了。所有常见情况都包含在一组用于人工验收测试的测试文件中;我的一位同事正在(ab)使用 NUnit 创建一组集成级别测试,用于加载、处理和检查测试文件的输出。由于主推内部发生的事情的复杂性,主要是分别测试内部方法以确保我们没有错过任何边缘情况。对于我的罪过(几年前完成了 VBA->C# 端口),并且对第二部分中发生的事情有最好的了解。
          猜你喜欢
          • 1970-01-01
          • 2012-12-06
          • 2011-03-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-09-26
          • 1970-01-01
          • 2010-12-29
          相关资源
          最近更新 更多