测试用例VS ASSERTION语句

时间:2008-08-19 23:19:18

标签: c++ tdd defensive-programming

在我的大多数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方法是处理遗留代码的非常明智的想法。

“谷歌” - 他们将“第一种方法”与“带着救生衣走在岸边,没有任何安全防护的游泳海洋”进行比较。

哪一个更好? 哪一个使软件健壮?

5 个答案:

答案 0 :(得分:4)

在我的(有限)体验中,第一个选项更加安全。在测试用例中,您只测试预定义的输入并比较结果,只要检查了每个可能的边缘情况,这都可以正常工作。第一个选项只是检查每个输入,从而测试“实时”值,它可以快速过滤出错误,但是它会带来性能损失。

Code Complete Steve McConnell了解到第一种方法可以成功用于过滤 debug 构建中的错误。在发布版本中,您可以过滤掉所有断言(例如使用编译器标志)以获得额外的性能。

在我看来,最好的方法是使用两种方法:

捕获非法值的方法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));
}

答案 1 :(得分:2)

这两种机制都有价值。任何体面的测试框架都会捕获标准的assert(),因此导致assert失败的测试运行将导致测试失败。

我通常在每个c ++方法的开头都有一系列断言,带有注释'// preconditions';它只是对我希望对象在调用方法时所具有的状态进行完整性检查。这些可以很好地融入任何TDD框架,因为它们不仅可以在运行时在测试功能时工作,而且还可以在测试时工作。

答案 2 :(得分:1)

没有理由说您的测试包无法捕获断言,例如doMoreWonderfulThings中的断言。这可以通过让ASSERT处理程序支持回调机制,或者你的测试断言包含try / catch块来完成。

答案 3 :(得分:0)

我不知道你指的是哪个特定的TDD子社区,但是我遇到的TDD模式要么使用Assert.AreEqual()来获得正面结果,要么使用ExpectedException机制(例如,.NET中的属性)声明应该遵守的错误。

答案 4 :(得分:0)

在C ++中,我更喜欢使用大多数测试框架的方法2。它通常使得更容易理解故障报告。在编写测试后几个月到几年的测试时,这是非常宝贵的。

我的理由是大多数C ++测试框架会打印出断言发生的位置的文件和行号,而不会显示任何类型的堆栈跟踪信息。因此,大多数情况下,您将获得函数或方法内部的报告行号,而不是测试用例内部。

即使从调用者捕获并重新声明了断言,报告行也将使用catch语句,并且可能不会接近调用断言的方法或函数的测试用例行。当在测试用例中多次使用断言的函数时,这可能会非常烦人。

但也有例外。例如,Google的测试框架有一个范围的跟踪语句,如果发生异常,它将作为跟踪的一部分进行打印。因此,您可以使用跟踪范围包含对广义测试函数的调用,并在一行或两行中轻松地告知确切测试用例中的哪一行失败。