2009-08-25 39 views
5

几周前,我开始了我的第一个TDD项目。到目前为止,我只读过一本关于它的书。如何用TDD开发复杂的方法

我主要关心的是:如何为复杂的方法/类编写测试。我写了一个计算二项分布的类。因此,这个类的方法将n,k和p作为输入,并计算出resp。可能性。 (实际上它做得更多,这就是为什么我必须自己写,但为了便于说明,我们坚持这个类的描述。)

我做了什么来测试这种方法是:复制一些不同的表我在网上找到我的代码,在表中随机选择一个条目,并提出了要求。 n,k和p的值加入到我的函数中,并查看结果是否接近表中的值。我为每张桌子多次重复这一点。

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

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

+0

“我的印象是,我不应该编写超过几分钟的代码,直到测试再次显示绿色。”哪里 - 具体地说 - 你是否得到了这样的印象?请提供报价或参考。这很少是真的,我很好奇你在哪里阅读/看到/听到过这个。 – 2009-08-25 13:11:49

+0

这是在德国的一本书“Testegetriebene Entwicklung mit JUnit&FIT”中,由Frank Westphal,1st Edition编写的。例如。在第13页,前两句话。 – matthias 2009-08-25 13:23:43

+1

由于您很可能无法访问本书,因此我尝试了一下翻译:“测试驱动开发与简单设计之间的交互作用导致了分钟编码的循环,实际上,您的代码不会长于只需几分钟,不用通过测试来关闭反馈回路。“ (嗯,我在这里接近我的英语的限制,希望这个翻译是正确的。) – matthias 2009-08-25 13:41:08

回答

3

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

韦斯特法尔是正确的一点。

一些功能启动简单,可以简单地测试和编码。

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

如果你的函数体很难写,这并不简单。这意味着你必须更加努力地将其减少到简单的程度。

当你最终达到简洁之后,你也可以写一本书来展示它是多么简单。

在实现简单性之前,写东西需要很长时间。

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

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

不要选择一行 - 随机或其他方式选择所有行。

+0

感谢您的评论。我感觉你是对的。我只需要学习很多东西。只要我没有几个月/几年的经验,我不得不接受我的解决方案不是最佳的。 – matthias 2009-08-26 10:56:43

+0

这不是“最佳”的问题。问题在于“简单”真的很难实现。珍贵的少数人能够很好地实现真正的简单。由于作者花了很多时间去简单,所以书中的例子是最糟糕的。我们都必须努力;他们可以隐藏所需的大量努力来获得一个简单的例子。 – 2009-08-26 14:34:00

0

很难回答你的问题,而不了解你想要实现的东西。这听起来像是他们在可测试部件中不容易分开。这些功能可以作为一个整体工作,或者它不工作。如果是这种情况,那么毫无疑问你会花费数小时来实现它。

至于你的第二个问题:是的,我认为把测试夹具随机化是一个坏主意。你为什么首先这样做?更换灯具会改变测试。

1

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

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

- 编辑 - 基于评论

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

另一个选择是创建其他类(使用注入的接口实现)来承担一些责任(也许这些较小的方法中的一些),然后单独测试它们,并在测试主类时对它们进行模拟。

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

+0

是的,我可以TDD这些较小的方法,但后来我有测试私人方法秒。据我了解,它们是实施细节的一部分,不应该进行测试。 – matthias 2009-08-25 13:26:48

+0

刚刚编辑我的答案 – 2009-08-25 13:53:18

+0

如果按照你想要的方式做TDD有帮助,你可以从这些私有方法开始公开/保护,并且测试驱动那些小部分功能。当你创建了公共方法时,你可以删除直接测试私有方法的测试。 您还应该问问自己,如果让它们处于受保护/默认可见性状态,以便从相同的包中进行测试,将会破坏封装。如果没有,我会说这是一个公平的交易。 – Grundlefleck 2009-08-25 21:05:15

0

避免使用TDD开发复杂的方法,直到开发出简单方法作为更复杂方法的构建块为止。 TDD通常用于创建一些简单的功能,这些功能可以组合起来产生更复杂的行为。复杂的方法/类应该总是能够被分解成更简单的部分,但它并不总是显而易见的,并且通常是特定问题。您所写的测试听起来可能更像是集成测试,以确保所有组件正确协作,尽管您所描述的问题的复杂性只与需要一组组件边界来解决它的边界有关。您所描述的情况听起来是这样的:

类A { 公共doLotsOfStuff()//调用doTask1..n 私人doTask1() 私人doTask2() 私人doTask3() }

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

类A { 公共doLotsOfStuff()//调用doTask1..n 公共doTask1() 公共doTask2() 公共doTask3() }

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

1

对于简短的快速重构,你无论在重建/测试之间几乎没有超过几分钟的时间,无论改变多么复杂。这需要一点练习。

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

系统测试应该在您通过小型简单方法的小单元测试建立功能后完成。

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

在另一方面,如果你的公式并不大,也许你只是把它写在同一个方法和测试它像你这样走的停机时间 - 规则是用来被打破的。

6

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

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

0

我认为你的测试风格完全适用于主要是计算的代码。而不是从已知的结果表中随机选择一行,最好是对重要的边缘案例进行硬编码。通过这种方式,您的测试始终验证相同的事情,并且一旦发生断裂,您就知道它是什么。

是TDD规定短跨度从测试到实施,但你下来的是仍然远远超出你在同行业中找到标准。您现在可以依靠代码来计算它应该如何应用,并且可以通过一定程度的确定性来重构/扩展代码,而不是破坏代码。

随着您学习更多测试技术,您可能会发现缩短红色/绿色周期的不同方法。同时,不要为此感到不快。它是达到目的的手段,而不是目的本身。