如何认真对待单元测试(和测试驱动)


所以我们写一些测试代码,然后让它通过,再重新开始。如果我们在当前的Pomodoro中还有一些时间,让我们重构一下,提取一些方法。很简单吧?

嗯,测试驱动开发有一个简单的定义,但有很多含义和假设,特别是关于重构部分。以下是我在PHP和Java中多年的TDD经验,在网络和机器学习应用程序中的一些提示。

只要写得好


public void increasesItsHeightWhenANewRowIsAdded()

public void processingNewRow()

public void testAddMethod()

public void changesItsHeightWhenANewRowIsAdded()
public void reducesItsHeightWhenANewRowIsAdded()
public void anEmptyRowCantBeAdded()

错误案例

天真的TDD的一个问题是测试驱动API的设计(方法名称和参数),但不是错误案例。真正彻底的测试不是只基于快乐的路径,也是基于管理错误;特别是在与外部实体打交道时, 发生的错误。

所以你所驱动的接口也应该由错误情况来塑造。

  • 返回值 。没有返回值显然是微不足道的;否则,它是一个一致的类型吗?在某些情况下你会返回false吗?你能返回一个Null对象吗?可以有一个接口来为结果定义,理解好的和坏的结果,就像一个 Maybe 类型。
  • s id-effects notifications 。第一种是预期的,你所做的调用的强制性效果。第二种包含了日志、事件和订阅者(如视图)。尽量减少效应耦合(简单地说,你有多少对象作为合作者。)
  • 异常 是API的一部分;@throws注解和throws子句为它们建模。一些测试应该专门用来尝试引发这些异常,并检查它们是否真的被抛出,而不是一个更通用的Exception或NullPointerException类。

重构测试

提取重复的内容对测试来说也是很好的做法;如果你缺乏幻想,一大堆模式帮助你组织测试代码。

  • 基础测试案例(只能扩展到1-2个父类)。
  • 外部对象,如FixtureLoader模式,建立一个假的数据库实例和它的人口。
  • 检查结果或传递给模拟的参数的约束对象。
  • 断言库。
  • 用于设置复杂实体或图形的对象构建器。

重构测试代码也意味着你不会因为规范中的第一个变化而丢弃测试套件,因为其中需要改变的代码行数太多了。如果你对这个列表中的元素没有一点概念,可以看看 xUnit模式书

不完整的东西

对你想到的新功能使用不完整的测试,以避免多任务;你真的应该在去做下一个功能之前完成这个测试,它的实现和重构。

在PHP中,测试可以用$this->markTestIncomplete()发出不完成的信号,而在Java中,你可以简单地抛出一个异常,这取决于你的测试框架。

这种技术对所有开发任务的概括是 在记事本上写作 ,这是我从TDD by Example和James Shore Let's TDD系列中学到的。如果我不和别人进行远程交流,纸张对我来说是最好的,但.txt文件在概念上是一样的。

当你准备好记事本的时候,你可以写下你要做的每一件事,当它出现在你的脑海中,但它与你当前的任务无关。额外的测试和功能可以作为方法插入,但很多时候你可以直接写下备忘录,这样你甚至不需要符合语法或为测试选择一个名字 现在

要做的重构和为其探索的想法必须放在记事本上:根据定义,你不能为重构写一个测试。我使用了一些部分,如:下一个仍未测试的场景,或不完整的测试和场景的可能变体(快乐的路径,错误案例,通知),要执行的重构,基础设施任务(如在构建中插入X,用MongoDB做一个尖峰。

记事本确保你做好工作,让你一次只做一个任务,但不会忘记任何想到的事情。每一个想法都可以在下一个Pomodoro中被接受和评估;也许你会实施它,也许你会判断它没有增加价值。)