如何用TDD开发复杂的方法

时间:2009-08-25 13:00:42

标签: tdd

几个星期前,我用TDD开始了我的第一个项目。到目前为止,我只读了一本关于它的书。

我主要担心:如何编写复杂方法/类的测试。我写了一个计算二项分布的类。因此,该类的方法将n,k和p作为输入,并计算resp。可能性。 (事实上​​它确实多了一点,这就是为什么我必须自己编写它,但为了方便论证,让我们坚持这个类的描述。)

我测试这种方法的方法是:将我在网络中找到的不同n的一些表复制到我的代码中,随机选择此表中的一个条目,然后输入。 n,k和p的值进入我的函数,并查看结果是否接近表中的值。我为每张桌子多次重复一次。

这一切现在都运行良好,但在编写测试后,我不得不坦克几个小时来真正编写功能。从阅读本书开始,我的印象是我不应该编写超过几分钟的代码,直到测试显示为绿色。我在这做错了什么?当然,我已经用很多方法打破了这个任务,但它们都是私有的。

一个相关的问题:从表中随机抽取数字是不是一个坏主意?如果出现错误,我将显示此运行使用的随机种子,以便我可以重现该错误。

7 个答案:

答案 0 :(得分:6)

我不同意人们说可以测试私有代码,即使你将它们分成不同的类。您应该测试应用程序(或您的库,如果它是您正在编码的库)的入口点。当您测试私有代码时,您将限制您的重新分解可能性(因为重构您的私有类意味着重构您的测试代码,您应该避免这样做)。如果您最终在其他地方重新使用此私有代码,那么请确保创建单独的类并测试它们,但在您这样做之前,假设您不需要它。

要回答你的问题,我认为是的,在某些情况下,这不是“直到你走绿色的2分钟”的情况。在这些情况下,我认为测试需要花费很长时间才能实现绿色环保。但大多数情况 “直到你走绿色2分钟”的情况。在你的情况下(我不知道关于二项分布的深蹲),你写道你有3个参数,n,k和p。如果你保持k和p不变,你的函数是否更容易实现?如果是,您应该首先创建始终具有常数k和p的测试。测试通过后,为k引入一个新值,然后为p。

引入

答案 1 :(得分:3)

“我的印象是我的代码不应超过几分钟,直到测试显示为绿色。我在这里做错了什么?”

Westphal在某一点上是正确的。

某些功能从简单开始,只需简单测试和编码即可。

有些功能并不简单。简单很难实现。 EWD说,简单性并不重要,因为它很难实现。

如果你的函数体很难写,那就不简单了。这意味着你必须更加努力地将其简化为简单的事情。

在你最终实现简单性之后,你也可以写一本书,说明它是多么简单。

直到你实现简单,写东西需要很长时间。

“从表格中随机选择数字是不是一个坏主意?”

是。如果您有样本数据,请针对所有样本数据运行测试。使用循环或其他东西,并测试您可能测试的所有内容。

不要随意选择一行,否则选择所有行。

答案 2 :(得分:1)

你应该使用婴儿步骤进行TDD。尝试考虑需要编写更少代码的测试。然后编写代码。然后写另一个测试,依此类推。

尝试将问题分解为更小的问题(您可能使用了其他一些方法来完成代码)。你可以TDD这些较小的方法。

- 编辑 - 基于评论

测试私有方法不一定是坏事。它们有时真的包含实现细节,但有时它们也可能像界面一样(在这种情况下,你可以按照我的建议下一段)。

另一个选择是创建其他类(使用注入的接口实现)以承担一些责任(可能是一些较小的方法),并单独测试它们,并在测试主类时模拟它们。 p>

最后,我认为花费更多时间编码是一个非常大的问题。有些问题实施起来比测试要复杂得多,需要很多思考时间。

答案 3 :(得分:1)

对于简短的快速重构,你是正确的,无论变化有多复杂,我在重建/测试之间都不会超过几分钟。这需要一点点练习。

您描述的测试更多的是系统测试,而不是单元测试。单元测试尝试永远不会测试多个方法 - 为了降低复杂性,您应该将问题分解为多种方法。

系统测试应该在使用小型直接方法进行小型单元测试构建功能后完成。

即使这些方法只是从更长的方法中获取公式的一部分,您也会获得可读性的优势(方法名称应该比它替换的公式部分更具可读性)并且如果方法是最终的JIT应该内联它们,这样你就不会失去任何速度。

另一方面,如果你的公式不是那么大,也许你只需用一种方法来编写它并像测试一样进行测试,并采取停机时间 - 规则被打破。

答案 4 :(得分:0)

如果不了解您想要实施的内容,就很难回答您的问题。听起来他们在可测试的部件中并不容易发挥作用。功能可以作为一个整体运行,也可以不运行。如果是这种情况,那么您无需花费数小时来实施它。

关于你的第二个问题:是的,我认为让测试夹具随机是一个坏主意。你为什么一开始就这样做?更换夹具会改变测试。

答案 5 :(得分:0)

在开发简单方法作为更复杂方法的构建块之前,请避免使用TDD开发复杂方法。 TDD通常用于创建一定数量的简单功能,这些功能可以组合在一起以产生更复杂的行为。复杂的方法/类应始终能够分解为更简单的部分,但并不总是很明显,如何且通常是特定于问题的。您编写的测试听起来可能更像是集成测试,以确保所有组件正确地协同工作,尽管您描述的问题的复杂性仅限于需要一组组件来解决它的边缘。你描述的情况听起来像这样:

A级{     public doLotsOfStuff()//调用doTask1..n     private doTask1()     私人doTask2()     private doTask3()  }

如果您开始编写最大功能单元的测试(即doLotsOfStuff()),您会发现使用TDD很难开发。通过将问题分解为更多可管理的块并从最简单的功能结束接近它,您还可以创建更多的离散测试(比检查所有内容的测试更有用!)。也许你的潜在解决方案可以像这样重新制定:

A级{     public doLotsOfStuff()//调用doTask1..n     public doTask1()     public doTask2()     public doTask3()  }

虽然您的私有方法可能是实现细节,但这并不是避免单独测试它们的理由。就像许多问题一样,分而治之的方法在这里会证明是有效的。真正的问题是,什么尺寸是一个适当的可测试和可维护的功能块?只有你可以根据你对问题的了解以及你自己对将自己的能力运用到任务中的判断来回答这个问题。

答案 6 :(得分:0)

我认为您的测试风格完全适合主要用于计算的代码。不是从已知结果表中选择一个随机行,而是仅对重要边缘情况进行硬编码会更好。通过这种方式,您的测试始终在验证相同的内容,当有人打破时,您就会知道它是什么。

是的TDD规定了从测试到实施的短暂跨度,但是你所做的还远远超出了业内标准。您现在可以依赖代码来计算它应该是什么样的,并且可以确定地重构/扩展代码,而不是破坏代码。

当您学习更多测试技术时,您可能会发现缩短红色/绿色循环的不同方法。与此同时,不要为此感到难过。它是达到目的的手段,而不是目的本身。