测试用例VS ASSERTION语句

时间:2020-03-05 18:40:57  来源:igfitidea点击:

在我的大多数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框架吻合,因为它们不仅在我们测试功能时在运行时起作用,而且在测试时也起作用。