2

我以前从未使用过单元测试,所以我给 CxxTest 一个机会。我写了一个测试来检查一个函数是否正确地对一个 std::vector 进行排序。首先,当向量未排序时,我确保测试失败,然后作为健全性检查,我测试了 std::sort 是否有效(当然,它确实有效)。到现在为止还挺好。

然后我开始编写自己的排序函数。但是,我犯了一个错误,该函数没有正确排序。由于我的测试在排序时没有输出向量的中间状态,因此很难判断我在排序函数中哪里出错了。我最终使用cout语句(我可以使用调试器)来查找我的错误,并且在我知道我的排序功能有效之前从未使用过单元测试。

我在这里做错了吗?我认为单元测试很简单

1) 编写测试
2) 编写函数
3) 测试函数
4) 如果测试失败,修改函数
5) 重复 3 和 4 直到测试通过

我使用的过程更像

1) 编写测试
2) 编写函数
3) 测试函数
4) 如果测试失败,调试函数直到它正常工作
5) 重复 3(即使函数已经知道可以工作)

我觉得我的过程不是真正的 TDD,因为我的排序功能的设计不是由我编写的测试驱动的。我是否应该编写更多测试,例如在对矢量进行排序时检查其中间状态的测试?

4

5 回答 5

4

测试不应该为您调试代码。

我觉得我的过程并不是真正的 TDD

你写了一个测试。它发现了一个错误。你修复了这个错误。你的测试通过了。系统有效!

这就是测试驱动开发的本质。你的测试告诉你什么时候有错误,他们告诉你什么时候完成。

无论如何,因为您没有实现纯 TDD 或纯 OOP 或任何心理障碍而感到内疚。前进并富有成效。

于 2009-11-23T15:52:29.950 回答
2

不要试图测试所有中间状态。没有人关心你的排序算法是如何工作的,只关心它可靠、快速地完成它的工作。

相反,编写测试来检查许多不同数据集的排序性。测试所有典型问题集:已经排序的数据、反向排序的数据、随机数据等。

如果您的应用程序需要稳定的排序,则您的检查必须更加小心。您可以为要排序的每个项目添加一个唯一标签,仅用于测试目的,排序的比较功能在排序时不会测试,但可用于确保两个其他相等的值在最终结果中以相同的相对顺序结束输出。

最后一点建议:在测试中,一定要努力预先考虑所有可能的失败案例,但不要期望成功。准备好在以后发现更多边缘案例时添加测试。测试套件应该朝着正确的方向发展,而不是一开始就期望是完美的,除非有数学上的理由证明它们应该是正确的。

于 2009-11-23T15:47:53.110 回答
2

单元测试关注的是你正在写的东西的具体外部行为,它不能真正理解算法的中间状态。排序函数是一种相当特殊的情况。

更常见的是,我们正在处理这种业务逻辑

“订单价格是订单商品价格的总和,如果总价值大于 20 英镑,则折扣 10%,如果客户是黄金会员,则再折扣 5%”

我们可以立即编写测试,例如

  • 没有订购商品
  • 一件订单商品价值 20.00 英镑
  • 一个订单商品价值 20.01 英镑
  • 两个订单项目总价值 £20.00 黄金客户
  • ...

等等——现在应该清楚的是,这些测试适用于代码的不同分支,并且确实有助于使其正确。

对于您的排序代码,进行测试可能会有所帮助,例如

  • {0}
  • { 1, 2 }
  • { 2, 1 }
  • { 1, 1 }

等等,但测试并不真正知道你是在做 QuickSort 还是 BubbleSort 或其他什么。

于 2009-11-23T15:55:38.540 回答
2

TDD 过程是

  1. RED:编写测试,验证失败

  2. GREEN 只写足够的代码让它通过,验证它通过

  3. 重构代码——测试和生产代码

如果您发现自己不得不在第 2 步使用调试器,则可能是您一次测试太多。分而治之。尽管对于排序算法来说除法不是那么容易,但您是否从排序一个空向量开始,然后是一个具有单个元素的向量,然后是一个具有两个元素已排序的向量,一个具有两个元素顺序错误的向量......

于 2009-11-24T07:42:28.490 回答
1

我看不出上面序列中的#4 之间有根本的区别。在 TDD 中,您编写单元测试,这样,如果它们通过了,您就可以确定代码可以正常工作。您在代码上工作,直到它通过为止。如果你发现了一个错误,你编写另一个测试来找到它,当测试通过时你就完成了代码的工作。(如果您仍然对它没有信心,请编写更多测试。)在您的情况下,让代码满足测试的难度比您预期的要大。

好处不在于让代码单元工作,而在于知道当你改变事物时它们仍然工作,并且清楚地定义它们何时工作。(还有其他优点:例如,测试可以作为代码应该做什么的文档。)

可能是您想编写一些较小的测试,但我不确定您会写什么在排序函数的中间有用。在我看来,它们将严重依赖于函数的实现方式,在我看来,这与 TDD 的精神背道而驰。

顺便说一句,你为什么要编写自己的排序函数?我希望这是因为您想编写一个排序函数(用于上课、娱乐或学习),而不是出于任何生产原因。几乎可以肯定,标准功能会比您要编写的任何东西更可靠、更容易理解并且通常更快,并且您不应该在没有充分理由的情况下用自己的代码替换它。

于 2009-11-23T15:49:57.367 回答