测试用例太 DRY 了? 应该让它们 DAMP!

乔梁 | 2021-02-16

下面的测试用例遵循了 DRY 原则 (“Don’t Repeat Yourself”), 它是鼓励重用,消除重复的一个最佳实践,例如, 通过抽取 helper 函数 或 通过使用循环的方式。 但是,它真的是一个 写得好的测试用例?

def setUp(self):
  # users 这个字段可以在多个测试用例中复用。
  self.users = [User('alice'), User('bob')]
  self.forum = Forum()

def testCanRegisterMultipleUsers(self):
  self._RegisterAllUsers()
  # 使用一个 for 循环 来验证所有用户都是已注册用户。
  for user in self.users:
    self.assertTrue(self.forum.HasRegisteredUser(user))

# 注册用户的 helper 方法,可在多个测试用例中复用.
def _RegisterAllUsers(self):
  for user in self.users:
    self.forum.Register(user)

虽然上面的测试用例是简洁的,但读者需要花上一些心力,才能更好地理解它。

比如,从 setUp() 方法,看 RegisterAllUsers()时,self.users 要跟踪到 setUp() 方法才知道。

因为测试用例本身通常是没有测试的,所以,人们必须要能做到:很容易地手动检查它们的正确性,即使是以更大的代码重复为代价。这意味 DRY 原则通常不适合于单元测试,尽管它是生产代码的最佳实践。

在这些测试用例中,我们可以使用**DAMP 原则 (即:Descriptive and Meaningful Phrases,描述性且有意义的短语)**, 它强调可读性要高于不重复性。使用这一原则可能会带来重复代码(例如,类似的代码片段),但是,它可能让测试的语义表达更加恰当。让我们使用 DAMP 对上面的测试用例改造一下:

def setUp(self):
  self.forum = Forum()
def testCanRegisterMultipleUsers(self):
  # 在测试用例中创建用户,而不是在 setUp() 中
  user1 = User('alice')
  user2 = User('bob')
  # 在测试用例中进行注册,而不是使用 helper 方法,并且也不使用 for 循环。
  self.forum.Register(user1)
  self.forum.Register(user2)

  # 一个一个的单独验证,而非使用 for 循环。
  self.assertTrue(self.forum.HasRegisteredUser(user1))
  self.assertTrue(self.forum.HasRegisteredUser(user2))

注意:

在测试用例中,DRY原则仍然是有效的

例如,使用 helper 函数创建值对象,从而把测试主体中的冗余细节抽取出来,从而使测试用例的表达更清晰。

“具有可读性且唯一性的测试代码”为最佳,但有时需要权衡。

在编写单元测试时,要在 DRY 和 DAMP 原则之间做出选择,应更倾向于 DAMP 原则

原文链接

谷歌TotT:让测试DAMP