5

我正在为以下单元测试用例寻找解决方案/模式。

案子:

假设我们有三个类,A、B、C,每个类都有一个方法。A的方法调用B的方法,B的方法调用C的方法。所以,A->B->C。每种方法都有一个输入(方法 A 的输入 A,输入 B,输入 C)。调用方法 A 的结果输出将是一个树结构,例如:

根(从方法 A 创建) -- 节点 B(从方法 B 创建) -- 节点 C1 -- 节点 C2(都从方法 C 创建)

对我来说,单元测试是关于孤立地测试方法输入的输出。因此,我们将为上述每种方法编写单元测试。因为测试是独立编写的,所以我们在为方法 A 编写单元测试时模拟方法 B,在为方法 B 编写单元测试用例时模拟方法 C。

到目前为止,一切都很好,我们可以在每个方法的输出上写入期望值,以确保结果树结构得到尊重。

问题:

现在让我们添加另一个调用方法 B 的类,这样我们也有以下调用链:D->B->C。生成的根树如下所示:

  • 根 D
    • 节点 B
      • 节点 C1
      • 节点 C2

在开发过程中,有人意识到方法 A 的需求被误解了,树的结果应该是这样的:

  • 根 A
    • 节点 B
      • 节点 C

令人高兴的是,开发人员会更改方法 C,以便输出仅返回一个节点而不是两个。他将更改单元测试,以使其反映这些更改。但是,方法 D 的要求不应该改变,并且该方法的输出应该仍然有节点 C1 和节点 C2。

问题:

您将如何编写您的单元测试,以便第二个开发人员会被提醒他将为方法 D 引入的重大更改?我宁愿避免看起来最适合这里的集成测试。

谢谢。

4

3 回答 3

2

您应该混合使用“纯¹”单元测试、不纯单元测试、集成(外部资源命中)测试和全栈测试(UI 关闭)。“纯”单元测试并不是测试的结束。

因此,您最终将针对您开发的每个功能进行以下一些类型的测试:

  • 单元测试 - 单独命中 A - 如果较低级别很复杂或命中外部资源。
  • 还有单元测试(但有时也称为集成或组件测试) - 在外部资源(例如数据库)之前将 A 降低到(大约)级别 - 如果较低级别很复杂,但经过测试或者较低级别并不复杂.
  • 集成测试 - 命中 A 和数据库。
  • 全栈测试——点击碰巧在某个时候调用 A 的 UI——我的目标是在这个级别(如 Cukes 等)或以下级别至少有几个。

只要您有一些更高级别的测试,即使达到 A 隔离的测试没有,它们也应该失败。

¹ 只是使用“纯单元测试”这个短语,因为有些人认为单元测试的定义是孤立的方法

于 2013-04-25T10:42:06.180 回答
1

理想情况下,您的单元测试应该一直渗透到 C 类。单元测试并不一定意味着ONE METHOD on ONE CLASS。这只是意味着您可以在不需要其他依赖项(如库或数据库等)的情况下运行它。然后您需要进行集成测试。

你的单元测试应该一直渗透到 C,当 A 的需求发生变化时,单元测试就会中断,D 的开发人员会知道有问题。

于 2013-04-25T10:04:21.140 回答
0

如果您想坚持使用孤立的单元测试(我对大多数中心对象都这样做,因为我发现几乎不可能编写具有正确深度并保持快速的正确数量的集成测试),一个有趣的方法是 Joe Rainsberger 的合同和协作测试。他提出了一个观点,即你不应该模拟一个依赖项以在测试中以某种方式表现,除非你有相应的契约测试确保该依赖项的具体实现在现实世界中表现得那样。

在您的示例中,说“方法 C 现在只返回一个节点而不是两个”意味着相应地更改 C 的合同及其合同测试。这引入了模拟 C 与新合同不同步的可能性,因此您应该在 C 被模拟的任何地方查看测试,并在需要时调整模拟。

在仔细检查现有测试之后,您会意识到您并不是真的想更改方法 C 的合同,而是引入适合 A 需求的新方法 C2。

Rainsberger 在这里描述了他用来确保他在测试中获得互惠的过程。您还可以在此视频中观看实际操作中的技术。

于 2013-04-25T17:53:49.217 回答