如何为主要面向服务的应用程序编写有用的单元测试?

时间:2009-10-14 17:53:51

标签: unit-testing mocking integration-testing

我已成功使用单元测试一段时间,但我开始认为它们只适用于实际执行大量逻辑的类/方法 - 解析器,数学运算,复杂的业务逻辑 - 一切都很好候选人进行测试,毫无疑问。我真的很难弄清楚如何使用另一类对象的测试:主要通过委托操作的那些。

例证:我当前的项目协调了很多数据库和服务。大多数类只是服务方法的集合,大多数方法执行一些基本的条件逻辑,可能是for-each循环,然后调用其他服务。

对于这样的对象,模拟实际上是唯一可行的测试策略,所以我尽职尽责地为其中几个设计了模拟。我真的,真的不喜欢它,原因如下:

  1. 使用模拟来指定对行为的期望会在我更改类实现时使事情中断,即使这不是应该对单元测试产生影响的那种变化。在我看来,单元测试应该测试功能,而不是指定“方法需要做A,然后是B,然后是C,而不是按顺序。”我喜欢测试,因为我可以自信地改变事物,我知道如果有什么东西坏了 - 但是嘲笑只会让屁股变得很难改变。
  2. 如果预期的行为很简单,编写模拟通常比编写类本身更有用。
  3. 因为我在测试中使用了完全不同的所有服务和组件对象的实现,最后,我所有的测试都真正验证了行为的最基本框架:“if”和“for”语句还在工作。无聊。我并不担心这些。
  4. 我的应用程序的核心是所有部分如何协同工作,所以我正在考虑 开沟单元完全测试(除了他们显然是合适的地方)并转而进行外部集成测试 - 更难设置,覆盖更少的可能情况,但实际上是运行系统,因为它意味着运行。

    我没有看到任何使用模拟实际上有用的情况。

    思想?

4 个答案:

答案 0 :(得分:3)

如果你可以编写快速可靠的集成测试,那么我会说它去吧。 仅在必要时使用模拟和/或存根来保持测试。

请注意,使用模拟并不一定像您所描述的那样痛苦:

  1. 模拟API允许您使用松散/非严格模拟,这将允许从被测单元到其协作者的所有调用。因此,您不需要记录所有调用,只需要记录需要为测试生成一些所需结果的调用,例如方法调用的特定返回值。
  2. 使用一个好的模拟API,你将不得不编写一些测试代码来指定模拟。在某些情况下,您可能会使用单个字段声明或应用于测试类的单个注释。
  3. 您可以使用部分模拟,以便实际上只为给定的测试模拟服务/组件类的必要方法。这可以在不指定字符串中的所述方法的情况下完成。

答案 1 :(得分:2)

  

在我看来,单元测试应该进行测试   功能,而不是指定“   方法需要做A,然后B,然后C,   没有别的,按顺序。“

我同意。正如您所发现的,使用模拟进行行为测试可能会导致脆弱的测试。使用存根进行基于状态的测试可以减少该问题。福勒在Mocks Aren't Stubs中权衡了这一点。

  

编写模拟通常更有效   而不是自己写课程

对于模拟或存根,请考虑使用隔离(模拟)框架。

  

最后,我的所有测试都真的验证了   是最基本的骨架   行为:“如果”和“为”   陈述仍然有效

分支和循环是逻辑;我建议测试它们。在我看来,没有必要测试getter和setter,单行纯委托方法等等。

集成测试对于像您这样的复合系统非常有价值。除了单元测试之外,我会推荐它们,而不是代替它们。

您肯定希望测试低级或组合服务的基础类;那就是你会看到最大的收获。

编辑:福勒不会像我想象的那样使用“经典”术语(这可能意味着我错了)。当我谈到基于状态的测试时,我的意思是将存根注入到被测试的类中,用于任何依赖项,作用于被测试的类,然后针对被测试的类进行断言。在纯粹的情况下,我不会验证存根上的任何内容。

答案 2 :(得分:0)

Integration Tests是一个可行的选择,但不应取代单元测试。但是既然你自己说过你的写作模拟,我建议使用一个隔离框架(又称模拟框架),我相信它也可以用于你的环境。

答案 3 :(得分:0)

因为你已经在一个问题上发了几个问题,我将逐一回答。

  

如何为主要面向服务的应用程序编写有用的单元测试?

不要依赖于“主要面向服务的应用程序”的单元测试!是的我在一句话中说过。这些类型的应用程序旨在做一件事:集成服务。因此,编写集成测试而不是单元测试非常紧迫,因为集成工作正常。

  

我没有看到任何使用模拟实际上有用的情况。

模拟可能非常有用,但我不会在控制器上使用它们。控制器应该由集成测试覆盖。服务可以由单元测试覆盖,但如果测试量减慢了您的项目,将它们作为单独的模块可能是明智的。

  

思想?

对我来说,我倾向于考虑一些事情:

  1. 我的申请在做什么?
  2. 执行系统级别/集成测试的成本是多少?
  3. 我可以将我的应用程序拆分为可以单独测试的模块吗?
  4. 在您提供的方案中,我会说您的应用程序 是许多服务的集成。因此,我非常依赖单元测试的集成测试。我敢打赌你写的大部分模拟都是用于http相关课程等。

    由于以下原因,我在可能的情况下更喜欢集成/系统级测试:

    1. 在这个“快速行动”的时代,对昨天设计的重新分解发生在不断增加的速度。集成测试根本不关心实现细节,因此这有助于快速更改。动态语言正在全面展开,使得模拟更加危险/脆弱。使用静态lang,mocks更安全,因为如果他们试图删除不存在或拼写错误的方法名称,那么测试将无法编译。
    2. 在集成测试中编写的代码量通常比单元测试中编写的代码量少60%,以达到相同的覆盖率,因此开发时间更短。 “是的,但是运行集成测试需要更长的时间......”这就是你需要务实的地方,直到它实际上减慢你运行集成测试的速度。
    3. 集成测试可以捕获更多错误。模拟通常是人为的,并且将开发人员从他们的更改将对整个应用程序的实际情况中删除。我已经允许在100%单元测试覆盖率的“安全网”下生产更多的错误,而不是集成测试。
    4. 如果我的应用程序的集成测试速度很慢,那么我没有把它拆分成单独的模块。这通常是早期的指标,我需要做一些提取分离。
    5. 集成测试对您而言远远超过覆盖代码覆盖率,它们也是性能问题或网络问题等的指标。