测试用例 VS ASSERTION 语句

发布于 2024-07-04 08:03:52 字数 708 浏览 8 评论 0原文

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

“谷歌”——他们将“第一方法”比作“穿着救生衣走上岸,在没有任何安全防护的情况下畅游大海”。

哪一个更好? 哪一项使软件变得健壮?

In my most C++ project I heavily used ASSERTION statement as following:

int doWonderfulThings(const int* fantasticData)
{
    ASSERT(fantasticData);
    if(!fantasticData)
        return -1;
    // ,,,
    return WOW_VALUE;
}

But TDD community seems like to enjoy doing something like this:

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"));
}

Just with my experiences first approaches let me remove so many subtle bugs.
But TDD approaches are very smart idea to handle legacy codes.

"Google" - they compare "FIRST METHOD" to "Walk the shore with life-vest, swim ocean without any safe guard".

Which one is better?
Which one makes software robust?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

活雷疯 2024-07-11 08:03:52

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

Code Complete 中,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));
}

In my (limited) experience the first option is quite a bit safer. In a test-case you only test predefined input and compare the outcome, this works well as long as every possible edge-case has been checked. The first option just checks every input and thus tests the 'live' values, it filters out bugs real quickly, however it comes with a performance penalty.

In Code Complete Steve McConnell learns us the first method can be used successfully to filter out bugs in a debug build. In release build you can filter-out all assertions (for instance with a compiler flag) to get the extra performance.

In my opinion the best way is to use both methods:

Method 1 to catch illegal values

int doWonderfulThings(const int* fantasticData)
{
    ASSERT(fantasticData);
    ASSERTNOTEQUAL(0, fantasticData)

    return WOW_VALUE / fantasticData;
}

and method 2 to test edge-cases of an algorithm.

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));
}
吾家有女初长成 2024-07-11 08:03:52

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

我通常在每个 C++ 方法的开头有一系列断言,并带有注释“//前提条件”; 这只是对调用方法时我期望对象具有的状态进行健全性检查。 它们可以很好地融入任何 TDD 框架,因为它们不仅在测试功能时在运行时工作,而且在测试时也工作。

Both mechanisms have value. Any decent test framework will catch the standard assert() anyway, so a test run that causes the assert to fail will result in a failed test.

I typically have a series of asserts at the start of each c++ method with a comment '// preconditions'; it's just a sanity check on the state I expect the object to have when the method is called. These dovetail nicely into any TDD framework because they not only work at runtime when you're testing functionality but they also work at test time.

心凉 2024-07-11 08:03:52

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

There is no reason why your test package cannot catch asserts such as the one in doMoreWonderfulThings. This can be done either by having your ASSERT handler support a callback mechanism, or your test asserts contain a try/catch block.

℡Ms空城旧梦 2024-07-11 08:03:52

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

I don't know which particlar TDD subcommunity you're refering to but the TDD patterns I've come across either use Assert.AreEqual() for positive results or otherwise use an ExpectedException mechanism (e.g., attributes in .NET) to declare the error that should be observed.

萌无敌 2024-07-11 08:03:52

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

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

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

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

In C++, I prefer method 2 when using most testing frameworks. It usually makes for easier to understand failure reports. This is invaluable when a test months to years after the test was written.

My reason is that most C++ testing frameworks will print out the file and line number of where the assert occurred without any kind of stack trace information. So most of the time you will get the reporting line number inside of the function or method and not inside of the test case.

Even if the assert is caught and re-asserted from the caller the reporting line will be with the catch statement and may not be anywhere close to the test case line which called the method or function that asserted. This can be really annoying when the function that asserted may have been used on multiple times in the test case.

There are exceptions though. For example, Google's test framework has a scoped trace statement which will print as part of the trace if an exception occurs. So you can wrap a call to generalized test function with the trace scope and easily tell, within a line or two, which line in the exact test case failed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文