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 方法是处理遗留代码的非常聪明的想法。

“Google”——他们将“FIRST METHOD”比作“穿着救生衣走在岸边,在没有任何安全防护的情况下畅游大海”。

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

4

5 回答 5

4

在我(有限的)经验中,第一个选项更安全。在测试用例中,您只测试预定义的输入并比较结果,只要检查了每个可能的边缘情况,这种方法就可以很好地工作。第一个选项只是检查每个输入并因此测试“实时”值,它可以快速过滤掉错误,但是它会带来性能损失。

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

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

方法一捕获非法值

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));
}
于 2008-08-19T23:49:54.450 回答
2

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

我通常在每个 c++ 方法的开头都有一系列断言,并带有注释'// preconditions';这只是对调用该方法时我期望对象具有的状态的健全性检查。这些与任何 TDD 框架非常吻合,因为它们不仅在您测试功能时在运行时工作,而且在测试时也工作。

于 2008-08-22T22:24:14.350 回答
1

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

于 2008-08-19T23:31:17.367 回答
0

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

于 2008-08-19T23:23:51.487 回答
0

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

我的原因是大多数 C++ 测试框架会打印出断言发生的文件和行号,而没有任何类型的堆栈跟踪信息。所以大多数时候你会在函数或方法内部而不是在测试用例内部获得报告行号。

即使断言被调用者捕获并重新断言,报告行也将使用 catch 语句,并且可能不会靠近调用断言的方法或函数的测试用例行。当断言的函数可能已在测试用例中多次使用时,这可能真的很烦人。

不过也有例外。例如,Google 的测试框架有一个范围跟踪语句,如果发生异常,它将作为跟踪的一部分打印出来。因此,您可以使用跟踪范围包装对通用测试函数的调用,并在一两行内轻松判断确切测试用例中的哪一行失败。

于 2013-08-27T21:01:59.080 回答