要测试行为(Behaviors),不要测试方法(Methods)

乔梁 | 2021-02-14

在编写一个method()之后,只需编写一个测试,就可以轻松地验证方法的所有功能。但是如果你认为,测试用例与 公开方法(public method)应该是一一对应的关系,那就不对了。我们真正想要测试的是软件的行为,而一个方法( method )可能展现出很多种行为,而且,一种行为有时会跨越多个方法。

让我们来看看现面这个不好的测试用例,它是验证整个方法的:

@Test public void testProcessTransaction() {
  User user = newUserWithBalance(LOW_BALANCE_THRESHOLD.plus(dollars(2));
  transactionProcessor.processTransaction(
      user,
      new Transaction("Pile of Beanie Babies", dollars(3)));
  assertContains("You bought a Pile of Beanie Babies", ui.getText());
  assertEquals(1, user.getEmails().size());
  assertEquals("Your balance is low", user.getEmails().get(0).getSubject());
}

从这个测试用例中,我们可以看出, transactionProcessor.processTransaction()这个方法做了两件事,「显示所购买商品的名称」和「发送一封关于余额不足的电子邮件」。这是两种不同的行为,而这个测试用例正是同时检查了这两种行为,因为它们恰好是在同一个方法中触发的。**类似这样的测试用例,随着时间的推移,通常会变得非常庞大并且很难维护,**这是因为它很容易让人产生一种在其中不断添加额外行为的冲动——最终很难确定输入的哪些部分负责哪些断言。测试用例所用的方法是直接投射了被测方法的方法名,这种命名方式,也会发出一个错误暗示的信号,即:所有对该生产方法的测试用例都写在这个测试方法里。

最好使用单独的测试用例来验证一个独立的生产行为:

@Test public void testProcessTransaction_displaysNotification() {
  transactionProcessor.processTransaction(
      new User(), new Transaction("Pile of Beanie Babies"));
  assertContains("You bought a Pile of Beanie Babies", ui.getText());
}
@Test public void testProcessTransaction_sendsEmailWhenBalanceIsLow() {
  User user = newUserWithBalance(LOW_BALANCE_THRESHOLD.plus(dollars(2)));
  transactionProcessor.processTransaction(
      user,
      new Transaction(dollars(3)));
  assertEquals(1, user.getEmails().size());
  assertEquals("Your balance is low", user.getEmails().get(0).getSubject());
}

当测试用例使用上面这种写法后,当有人向该生产方法添加新的生产行为时,他们就会为这个新行为编写新的测试了。无论添加多少个新行为,每个测试都要保持聚焦和易于理解。这将使您的测试更有弹性,因为添加新的行为不太可能破坏现有的测试,而且更清晰,因为每个测试都包含只执行一种行为的代码

原文发布日期:April 14, 2014

原文作者:Erik Kuefler

原文链接Testing on the Toilet: Test Behaviors, Not Methods