测试用例与断言语句

9

在我的大部分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个回答

4
根据我的(有限)经验,第一种选项更加安全。在测试用例中,您只测试预定义的输入并比较结果,只要每个可能的边缘情况都已检查,这种方法就可以很好地工作。第一种选项仅检查每个输入,因此测试“实时”值,它可以快速过滤出错误,但会带来性能损失。
Code Complete中,Steve McConnell向我们学习了第一种方法可以成功地用于在< strong>调试构建中过滤出错误。在发布构建中,您可以过滤掉所有断言(例如使用编译器标志)以获得额外的性能。
在我看来,最好的方法是同时使用两种方法:
方法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));
}

2

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

我通常在每个C++方法的开头使用一系列断言,并添加注释“//前提条件”,这只是对我期望对象在调用方法时具有的状态进行的合理性检查。这些断言与任何TDD框架完美衔接,因为它们不仅在测试功能时在运行时起作用,而且还在测试时间起作用。


1

你的测试包无法捕获像doMoreWonderfulThings中的asserts这样的异常是没有道理的。这可以通过使你的ASSERT处理程序支持回调机制,或者在你的测试asserts中包含try/catch块来实现。


0

在C++中,当使用大多数测试框架时,我更喜欢使用方法2。这通常会使失败报告更易于理解。当测试几个月或几年后再次运行时,这是非常宝贵的。

我之所以偏爱此方法,是因为大多数C++测试框架将打印出断言发生的文件和行号,而不提供任何类型的堆栈跟踪信息。因此,大部分时间内,你将得到函数或方法内部的报告行号,而不是测试用例内部的报告行号。

即使assert被捕获并从调用者重新断言,报告行也将与catch语句一起,可能与调用断言的方法或函数所在的测试用例行不相邻。当断言的函数在测试用例中多次使用时,这可能会非常令人恼火。

当然也有例外。例如,谷歌的测试框架具有作用域跟踪语句,如果发生异常,该跟踪将作为跟踪的一部分打印出来。因此,您可以使用跟踪范围包装对泛化测试函数的调用,并且可以轻松地确定在哪个具体的测试用例中的哪一行失败。


0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接