测试用例VS ASSERTION语句
在我的大多数C ++项目中,我大量使用了ASSERTION语句,如下所示:
int doWonderfulThings(const int* fantasticData) { ASSERT(fantasticData); if(!fantasticData) return -1; // ,,, return WOW_VALUE; }
但是TDD社区似乎喜欢做这样的事情:
int doMoreWonderfulThings(const int* fantasticData) { if(!fantasticData) return ERROR_VALUE; // ... return AHA_VALUE; } TEST(TDD_Enjoy) { ASSERT_EQ(ERROR_VALUE, doMoreWonderfulThings(0L)); ASSERT_EQ(AHA_VALUE, doMoreWonderfulThings("Foo")); }
仅凭我的经验,我便消除了许多细微的错误。
但是TDD方法是处理遗留代码的非常聪明的主意。
他们在"谷歌"(Google)上将"第一方法"与"背着救生衣走到岸上,没有任何安全保护措施游动海洋"相提并论。
哪一个更好?
哪一个使软件更强大?
解决方案
回答
我不知道我们指的是哪个特定的TDD子社区,但是我遇到的TDD模式使用Assert.AreEqual()获得肯定的结果,或者使用ExpectedException机制(例如.NET中的属性)来声明应该观察到的错误。
回答
测试程序包没有理由不能捕获诸如doMoreWonderfulThings中的断言。这可以通过让ASSERT处理程序支持回调机制来完成,或者测试断言包含try / catch块。
回答
以我(有限)的经验,第一个选择要安全得多。在一个测试用例中,我们仅测试预定义的输入并比较结果,只要检查了每个可能的边缘用例,这就可以很好地工作。第一个选项仅检查每个输入,从而测试"实时"值,它可以快速真正过滤掉错误,但是会带来性能损失。
史蒂夫·麦康奈尔(Steve McConnell)在代码完成中向我们学习,第一种方法可以成功地用于滤除调试版本中的错误。在发行版构建中,我们可以过滤掉所有断言(例如,带有编译器标志)以获取额外的性能。
我认为最好的方法是同时使用两种方法:
方法1捕获非法值
int doWonderfulThings(const int* fantasticData) { ASSERT(fantasticData); ASSERTNOTEQUAL(0, fantasticData) return WOW_VALUE / fantasticData; }
和方法2测试算法的边缘情况。
int doMoreWonderfulThings(const int fantasticNumber) { int count = 100; for(int i = 0; i < fantasticNumber; ++i) { count += 10 * fantasticNumber; } return count; } TEST(TDD_Enjoy) { // Test lower edge ASSERT_EQ(0, doMoreWonderfulThings(-1)); ASSERT_EQ(0, doMoreWonderfulThings(0)); ASSERT_EQ(110, doMoreWonderfulThings(1)); //Test some random values ASSERT_EQ(350, doMoreWonderfulThings(5)); ASSERT_EQ(2350, doMoreWonderfulThings(15)); ASSERT_EQ(225100, doMoreWonderfulThings(150)); }
回答
两种机制都有其价值。任何体面的测试框架无论如何都会捕获标准的assert(),因此导致断言失败的测试运行将导致测试失败。
我通常在每个c ++方法的开头都有一系列断言,并带有注释" // preconditions";这只是对我期望对象在调用该方法时所具有的状态的完整性检查。它们很好地与任何TDD框架吻合,因为它们不仅在我们测试功能时在运行时起作用,而且在测试时也起作用。