正在创建您认为需要在TDD中进行第一次测试的对象


15

我对TDD相当陌生,在创建任何测试代码之前的第一个测试时遇到了麻烦。在没有任何实现代码框架的情况下,我可以随意编写我的第一个测试,但是它似乎总是被我的Java / OO问题思考方式所困扰。

例如,在我的Github ConwaysGameOfLifeExample中,我编写的第一个测试(rule1_zeroNeighbours)首先创建了一个尚未实现的GameOfLife对象。称为不存在的set方法,不存在的step方法,不存在的get方法,然后使用断言。

随着我编写了更多测试并进行了重构,这些测试也在不断发展,但最初看起来是这样的:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

当我根据在早期阶段决定编写此第一项测试的方式来强制实施设计时,这感觉很奇怪。

以您理解TDD的方式可以吗?我似乎遵循TDD / XP的原则,因为我的测试和实现随着时间的推移随着重构的发展而发展,因此,如果这种最初的设计被证明是无用的,那就可以改变了,但是感觉就像我在向开发者强加一个方向。通过这种方式解决。

人们还如何使用TDD?我可以从没有GameOfLife对象开始,而只有原语和静态方法开始,进行更多的重构迭代,但这似乎太虚构了。


5
TDD既不能替代精心计划,也不能替代精心设计模式的选择。就是说,在编写任何实现来满足测试的前几行的实现之前,比编写依赖项来识别计划是愚蠢的,选择了错误的模式甚至是仅仅花了好得多的时间。以您的测试要求的方式来调用一个类是尴尬或令人困惑的。
svidgen

Answers:


9

当我根据在早期阶段决定编写此第一项测试的方式来强制实施设计时,这感觉很奇怪。

我认为这是您问题的关键,这是否可取取决于您是否倾向于codeninja的想法,即应该先进行设计,然后使用TDD来填充实现,还是durron的想法是将测试包含在其中。淘汰设计和实施。

我认为您更喜欢发现其中哪一个(或介于中间)。了解每种方法的利弊是很有用的。可能很多,但我要说的主要是:

专业前期设计

  • TDD在驱动设计方面有多么出色的过程,但这并不是完美的。不考虑具体目的地的TDing有时可能会走到尽头,并且至少可以避免一些死角,而要事先想一想到底要去哪里。这篇博客文章使用罗马数字kata的示例进行了论证,并给出了一个相当不错的最终实现。

专业测试驱动设计

  • 通过围绕代码客户端(您的测试)构建实现,只要您不开始编写不需要的测试用例,您就可以免费获得YAGNI-adherence。更一般而言,您将获得一个围绕使用者使用而设计的API,这最终是您想要的。

  • 在编写任何代码之前先绘制一堆UML图然后填补空白的想法是不错的,但很少现实。在史蒂夫·麦康奈尔(Steve McConnell)的《代码大全》中,设计被著名地描述为“邪恶的问题”,这是您必须首先至少部分解决该问题才能完全理解的问题。再加上以下事实:潜在的问题本身可能会随着需求的变化而改变,因此这种设计模型开始让人感到绝望。通过测试驱动,您可以一次完成一项工作-在设计中,而不仅仅是在实施方面-并且知道至少在从红色变为绿色的整个生命周期中,该任务仍将是最新且有意义的。

关于您的特定示例,如durron所说,如果您确实采用了通过编写最简单的测试来消耗设计的方法,并使用了可能的最小接口,那么您可能会以比代码片段中的接口更简单的方式开始。


该链接非常好阅读本。感谢您的分享。
RubberDuck

1
@RubberDuck不客气!实际上,我并不完全同意,但是我认为在论证这一观点方面做得很好。
Ben Aaronson

1
我不确定我也不会这样做,但是确实可以解决问题。我认为正确的答案在中间。您必须有一个计划,但是如果您的测试感到尴尬,请一定重新设计。反正... ++很好地展示了老豆。
RubberDuck

17

为了首先编写测试,您必须设计然后要实现的API。通过编写测试来创建整个 GameOfLife对象并使用该对象来实现测试,您已经走错了脚。

来自使用JUnit和Mockito进行实用单元测试

起初,您可能会因为写一些甚至没有的东西而感到尴尬。它需要稍微改变您的编码习惯,但是一段时间后,您会发现它是一个很好的设计机会。通过首先编写测试,您就有机会创建一个方便客户端使用的API。您的测试是新生API的第一个客户端。这就是TDD真正要解决的问题:API的设计。

您的测试并没有过多尝试设计API。您已经建立了一个有状态的系统,其中所有功能都包含在外部GameOfLife类中。

如果要编写此应用程序,那么我会考虑要构建的部分。例如,Cell在进入更大的应用程序之前,我可能会创建一个类,为此编写测试。我当然会为“正确实现” Conway并进行测试所需的“各个方向的无限”数据结构创建一个类。完成所有这些操作后,我将考虑编写具有main方法的整个类,以此类推。

掩盖“编写失败的测试”步骤很容易。但是编写能够按照您希望的方式运行的失败测试是TDD的核心。


1
考虑到Cell只是a的包装boolean这种设计的性能肯定会更差。除非将来需要扩展到具有两个以上状态的其他细胞自动机?
user253751 2015年

2
@immibis令人困惑的细节。您可以从代表单元格集合的类开始。如果性能存在问题,您也可以稍后迁移/合并单元格类及其测试,并使用一个代表单元格集合的类。
埃里克

@immibis出于性能原因,可以存储活动邻居的数量。数蜱细胞的活着,用于着色的原因..
Blorgbeard超出

@immibis过早的优化是邪恶的...此外,避免原始的迷恋是编写高质量代码的好方法,无论它支持多少状态。看看:jamesshore.com/Blog/PrimitiveObsession.html
Paul

0

关于这有不同的思想流派。

有人说:测试未编译是错误的-修正编写最小的可用生产代码。

有人说:可以编写测试,首先检查它是否吮吸蚂蚁,然后创建缺少的类/方法,这是可以的

使用第一种方法,您实际上处于红绿色重构周期。其次,您对要实现的目标有了更广泛的了解。

您可以选择自己的工作方式。恕我直言,这两种方法都是有效的。


0

即使当我以“一起破解”的方式实现某些东西时,我仍然会考虑整个程序中涉及的类和步骤。因此,您已经考虑了这一点,并首先将这些设计思想写下来作为测试-太好了!

现在,继续遍历这两种实现方式以完成此初始测试,然后添加更多测试以改进和扩展设计。

可能会帮助您的是使用Cucumber或类似工具编写测试。


0

在开始编写测试之前,您应该考虑如何设计系统。在设计阶段,您应该花费大量时间。如果您做到了,您将不会对TDD感到困惑。

TDD只是一个开发方法链接:TDD
1.添加一个测试
2.运行所有测试,看看新测试是否失败
3.编写一些代码
4.运行测试
5.重构代码
6.重复

TDD可帮助您涵盖开始开发软件之前计划的所有必需功能。链接:好处


0

因此,我不喜欢用Java或C#编写的系统级测试。查看SpecFlow for c#或Java的基于Cucumber的测试框架之一(也许是JBehave)。然后您的测试可以看起来像这样。

在此处输入图片说明

而且,您可以更改对象设计而不必更改所有系统测试。

(“常规”单元测试在测试单个类时非常有用。)

Java的BDD框架之间有什么区别?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.