我之前从未使用过单元测试,所以我给了CxxTest。我写了一个测试,以检查函数是否正确排序std :: vector。首先,我确保测试失败,当矢量没有排序,然后作为一个完整性检查,我测试了std :: sort是否有效(当然,它确实)。到目前为止,非常好。
然后我开始编写自己的排序功能。但是,我犯了一个错误,功能没有正确排序。由于我的测试没有输出矢量的中间状态,因为它正在排序,很难说我的排序函数出错了。我最终使用cout
语句(我本来可以使用调试器)来查找我的错误,直到我知道我的排序功能起作用之后才使用单元测试。
我在这里做错了吗?我认为单元测试就像
一样简单1)写测试
2)写功能
3)测试功能
4)如果测试失败,修改功能
5)重复3和4直到测试通过
我使用的过程更像是
1)写测试
2)写功能
3)测试功能
4)如果测试失败,调试功能直到它正常工作
5)重复3(即使功能已知可行)
我觉得我的过程不是真正的TDD,因为我编写的测试中我的排序功能的设计并非驱动。我应该写更多的测试,例如检查向量的中间状态的测试是否正在排序?
答案 0 :(得分:4)
测试不应该为您调试代码。
我觉得我的过程不是真正的TDD
你写了一个测试。它发现了一个错误。你修复了这个bug。你的测试通过了。系统有效!
这是测试驱动开发的本质。您的测试会在您遇到错误时告诉您,并在完成后告诉您。
无论如何,因为你没有达到纯粹的TDD或纯粹的OOP或任何心理障碍而感到内疚。前进,富有成效。
答案 1 :(得分:2)
不要试图测试所有中间状态。没有人关心你的排序算法是如何工作的,只是它能够可靠而快速地完成它的工作。
相反,编写测试以检查许多不同数据集的排序。测试所有典型的问题集:已排序的数据,反向排序的数据,随机数据等。
如果您的应用需要stable种类,那么您的检查必须更加谨慎。您可以为正在排序的每个项目添加一个唯一标记,仅用于测试目的,排序的比较函数在排序时不会测试,但可用于确保两个其他相等的值在最终中以相同的相对顺序结束输出
最后一点建议:在测试中,尽一切努力预先考虑所有可能的故障情况,但不要期望成功。准备好在以后发现更多边缘情况时添加测试。测试套件应该朝着正确性的方向发展,不要指望它是完美的,除非有数学原因,为什么它们应该是正确的。
答案 2 :(得分:2)
单元测试侧重于您正在编写的事物的特定外部行为,它无法真正理解算法的中间状态。排序功能是一个相当特殊的情况。
通常我们处理的是那种商业逻辑
“订单价格是订单商品的价格总和,如果总价值大于20英镑则减少10%的折扣,如果客户是黄金会员则减少5%”
我们可以立即编写
等测试依此类推 - 现在应该清楚的是,这些测试适用于代码的不同分支,并帮助实现正确。
对于您的排序代码,进行
等测试可能会有所帮助等等,但测试并不知道你是在做QuickSort还是BubbleSort等等。
答案 3 :(得分:2)
TDD流程
RED:写一个测试,验证它是否失败
GREEN编写足够的代码使其通过,验证通过
重构代码 - 测试代码和生产代码
如果您发现自己不得不在步骤2使用调试器,那么<em>可能就是您一次测试的太多了。 分而治之。虽然对于排序算法来说划分不是那么容易,但是你首先要排序一个空向量,然后是一个带有单个元素的向量,然后是一个已经排序了两个元素的向量,一个带有错误顺序的两个元素的向量...
答案 4 :(得分:1)
我没有看到上述序列中#4s之间存在根本区别。在TDD中,您编写单元测试,以便在它们通过时,您可以确定代码是否正常工作。您可以使用代码直到它通过。如果你发现了一个bug,你可以编写另一个测试来找到它,并且在测试通过时你正在处理代码。 (如果你对它仍然没有信心,可以写更多的测试。)在你的情况下,你得到的代码比你预期的更难以满足测试。
优势不在于让代码单元工作,因为知道它们在你改变事物时仍然可以工作,并且明确定义它们何时起作用。 (还有其他优点:例如,测试用作代码应该做的文档。)
可能你想要编写一些较小的测试,但我不确定你在编写的函数中有什么用处。在我看来,他们在很大程度上依赖于函数的实现方式,而这似乎违背了TDD的精神。
顺便问一下,为什么要编写自己的排序功能?我希望这是因为你想编写一个排序函数(用于课堂,或用于娱乐或学习),而不是出于任何制作原因。标准功能几乎肯定会更可靠,更容易理解,并且通常比你要编写的任何内容都要快,而且你不应该在没有充分理由的情况下用你自己的代码替换它。