功能复杂的TDD如何应用?

How to apply TDD with complicated function?

Bob 大叔的测试驱动开发三原则如下:

  1. 您不得编写任何产品代码,除非是为了让失败的单元测试通过。

  2. 您不得编写超过足以失败的单元测试;编译失败就是失败。

  3. 除了足以通过一个失败的单元测试之外,您不得编写任何更多的生产代码。

在现实世界中,例如,我有一个任务要完成一个需要一些复杂算法(例如一些高级数学知识)的方法,如果我应用上面的 3 个规则,它将是这样的:

我的问题是:这种方法在这种情况下是不是很不切实际并且会分散注意力?

我们为什么不编写第一个测试用例,然后专注于找到解决方案然后实施它,我的意思是着眼于全局,而不是编写足够的代码来通过第一个测试用例?

根据我使用 TDD 的经验,执行这个小步骤有一些好处:

让开发者思考代码应该如何使用

一个易于理解的方法名称、输入(我将从文件、数组中读取)、输出(将是 json、ArrayList)和每个特定输入的行为(引发异常,什么也不做).

测试反馈更精准

小步骤有助于您专注于如何处理算法中的特定情况。例如,假设一个带有快速排序算法的 TDD:

def "Given an empty list, when I use quicksort, should raise an exception"()
def "Given an list of one elements, should return the list itself"()
def "Given an ordered list of elements, should return the list itself"()
def "Given an unordered list of elements, should return a copy of ordered list"()

请注意,测试本身应该非常精确并详细说明您要测试的行为。 不太好的测试可能是这样的

def "Given an list, should give me an ordered list"()

如果上面这个测试失败了,是因为没有抛出异常?或者列表已排序但更改了原始列表?

您的测试代码成为规范

如果您对算法进行一些广泛的测试代码,您可能会隐藏算法的一些重要细节。您代码的所有重要部分都必须有一个写得很好的测试用例。这样做,您最终会得到一个测试代码,该代码基本上就是您的算法规范。 如果其他一些开发人员问你你的算法应该如何工作,就给他看测试用例。

一开始我和你的想法是一样的。感觉我们在浪费时间,做这些婴儿步骤似乎很荒谬。但是相信我,你练习得越多,你就越能体会到 TDD 的好处。

我认为另一个答案非常好,但除此之外:如果 one 方法将要求您实施 several 算法 - 你是吗确定这样的方法遵守单一责任原则?听起来这一种方法做的事情太多了。

因此,当您退后一步看到使用了 3 种算法时 - 这已经告诉我们 每个 算法可能应该进入其自己的方法。而且我们的 "initial" 方法只会 调用 这些其他方法来完成一些计算。

并朝其他方向发展 - 没有法律阻止您适应 TDD 以满足您的需求。这导致了一种我称之为 "bottom up TDD" 的做法。

它是这样的:而不是 首先 为我的一个巨大的方法编写测试 - 我实际上考虑了不同的 部分 我将需要在那个巨大的方法中。所以我只写下第一部分的测试;然后实施。我对所有部分都这样做。随着时间的推移,这些部分得到了增强,也许我会在途中将较小的部分合并成更大的部分(这很可能意味着将多个测试也合并成一个更大的测试)。

这项技术可能意味着您 可能 最终为您的大型方法得到一个测试用例 - 但您实际上在构建 "the big solution" 时使用 TDD 来测试小部分.

换句话说:我不是为那个方法的 public 契约编写一个大的功能测试——我首先为我的小辅助方法编写测试知道我会需要。最后,这些助手将是 private 方法——直接测试它们没有意义。但是您可以保留早期测试的那些在您的大 public 方法的上下文中有意义的部分。

长话短说:所有这些技巧都是为了指导你找到自己的路。如果您有 足够 的经验来即时设计,那么以这种方式使用 TDD 是可能的(而且实际上很有趣)!