什么是测试驱动开发?

使用 TDD 方法和给定的测试套件来解答练习


测试驱动开发(有时也叫测试先行开发或测试驱动设计)是一种先写单元测试的实践:在写下哪怕一行实现代码之前,先把测试写好。

在 Exercism,测试_就是_要求!

你做的所有实践练习(也就是那些不教你新概念的练习)都会有一些说明,用概括性的语言描述你需要做什么。 这些说明有意不涉及特定编程语言的实现细节,因为 Exercism 的 70 多个语言轨道都共用它们。 有些语言轨道会为你补充更具体的细节,但并不是所有轨道都会这样做。

开始做一道实践练习时,请仔细阅读说明。 说明会给你一个大致的概览,告诉你该如何着手实现解答。 但要理解完整而确切的要求,你必须阅读_测试_:

  • 结果必须是某种特定的数据结构吗?
  • 结果必须按某种顺序排序吗?
  • 你应当如何处理异常?等等。

当所有提供的测试都能运行并通过时,你就解出了这道练习。 换句话说,你的解答不只是对说明的一种“看起来没错”的解读,而是一个_满足给定测试_的程序。 测试代表了这道练习的完整要求。

Exercism 如何应用 TDD?

我们已经替你写好了整套单元测试。 你的目标是写出这样的解答:它包含的代码刚好足以让所有单元测试通过。

请记住:TDD 方法会帮你找到解答,但你不必止步于此。 如果你想在要求之外扩展你的解答,完全可以。 如果你选择和导师一起工作(我们鼓励你在测试通过后这样做),导师可以帮你重构和完善最初的实现,甚至提出新的单元测试。

在在线编辑器中工作

在 Exercism 网站上用代码编辑器做题时,你可以阅读测试,但无法编辑它们。 无论测试文件中标注了怎样的“跳过”机制,每次运行测试时,所有测试都会被执行。

当有多个测试失败时,网站最初只显示第一个失败的结果。 你也可以点击其他失败项,把它们展开! 有时第一个结果未必最有参考价值。

不要因为大量测试失败就灰心。 专注于让它们一个接一个地通过。

在本地工作

许多轨道会在测试文件中使用“跳过”的测试。 起初只有第一个测试是“激活”的,其余的都是未激活的(具体方式因轨道而异)。 你在自己的环境中运行测试套件时,只有第一个测试会运行。 我们这样做是为了鼓励你遵循下面的工作流:

  1. 在添加任何新代码之前,先运行测试套件:你应该会看到一个失败的测试。
  2. 添加_刚好足够_通过该测试的代码。
  3. 运行测试套件。
  4. 如果测试仍然失败,重复第 2 步。
  5. 测试通过后,按你的想法重构代码,同时确保所有激活的测试仍然通过。 重构可能包括:
    • 删除任何重复的代码,
    • 把过长的函数拆分成更小的函数,
    • 添加注释,等等。
  6. 对下一个测试“取消跳过”,然后从第 1 步重复。

重复这些步骤,直到你取消了所有测试的跳过。 当所有测试都通过时,恭喜,你已经解出了这道练习!

测试具体如何“取消跳过”(也就是激活),取决于所在的轨道。 在某些轨道中,可能是注释掉或删除某个注解。 在某些轨道中,可能是把某个属性从 true 改为 false。 请花点时间阅读你的轨道文档,它会解释这些细节。

对于不跳过测试的轨道,应用这套工作流可能很简单:把测试注释掉,然后逐个取消注释。

测试驱动开发的理由

虽然这看起来像是“本末倒置”,但先写单元测试、再写实现代码,有好几个充分的理由。

  1. 设计。 它迫使你先思考程序的接口(也就是它如何向外界暴露自己的功能),而不是直接跳到如何实现代码。 一个设计良好(而且可测试!)的接口,往往比一个高效的实现更重要。

  2. 自律。 写测试常常被视为一件苦差事,或者事后才想起的事;而_先_写测试,能保证你最终写出足够多的单元测试,覆盖代码的大部分甚至全部功能(而不是可能一直拖着没做)。

  3. 工作量更少。 如果你采用这样的紧密循环:写一个测试,然后写代码实现这个测试,再写下一个测试,你的代码就会自然地生长。 这通常(虽然并不总是)会减少无用功;你最终会写出所有需要的代码,而不写任何不需要的代码。

延伸阅读