测试抛出的特定异常类型,并且异常具有正确的属性

Mr.*_*Boy 48 c++ unit-testing exception googletest

我想测试MyException在某种情况下抛出的东西.EXPECT_THROW这里很好.但我也想检查异常是否有特定的状态,例如e.msg() == "Cucumber overflow".

如何在GTest中实现最佳效果?

Mik*_*han 47

我主要是第二个Lilshieste的答案,但是会补充一点,你还应该验证是不是抛出了错误的异常类型:

#include <stdexcept>
#include "gtest/gtest.h"

struct foo
{
    int bar(int i) {
        if (i > 100) {
            throw std::out_of_range("Out of range");
        }
        return i;
    }
};

TEST(foo_test,out_of_range)
{
    foo f;
    try {
        f.bar(111);
        FAIL() << "Expected std::out_of_range";
    }
    catch(std::out_of_range const & err) {
        EXPECT_EQ(err.what(),std::string("Out of range"));
    }
    catch(...) {
        FAIL() << "Expected std::out_of_range";
    }
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
Run Code Online (Sandbox Code Playgroud)

  • 我不知道这是否在某些 gtest 版本上发生了变化,但是 `catch(...)` 并不是使测试失败所必需的。以异常结束测试是测试失败。 (2认同)

min*_*ros 36

一位同事通过重新抛出异常提出了解决方案.

诀窍:不需要额外的FAIL()语句,只需要测试你实际需要的两个EXPECT ...调用:这样的异常及其值.

TEST(Exception, HasCertainMessage )
{
    // this tests _that_ the expected exception is thrown
    EXPECT_THROW({
        try
        {
            thisShallThrow();
        }
        catch( const MyException& e )
        {
            // and this tests that it has the correct message
            EXPECT_STREQ( "Cucumber overflow", e.what() );
            throw;
        }
    }, MyException );
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该是选择的答案,因为它使用两个简单的EXPECT宏干净地测试了正确的类型和消息。 (5认同)

Mat*_*ndl 23

我之前在一个旧的答案中提供了一个宏来解决这个问题。然而,随着时间的推移,GTest 中添加了一项新功能,无需宏即可实现此目的。

该功能是一组匹配器,例如,Throws可以与 结合使用EXPECT_THAT()。然而文档似乎没有更新,所以唯一的信息隐藏在这个 GitHub 问题中。


该功能的使用方式如下:

EXPECT_THAT([]() { throw std::runtime_error("message"); },
    Throws<std::runtime_error>());

EXPECT_THAT([]() { throw std::runtime_error("message"); },
    ThrowsMessage<std::runtime_error>(HasSubstr("message")));

EXPECT_THAT([]() { throw std::runtime_error("message"); },
    ThrowsMessageHasSubstr<std::runtime_error>("message"));

EXPECT_THAT([]() { throw std::runtime_error("message"); },
    Throws<std::runtime_error>(Property(&std::runtime_error::what,
         HasSubstr("message"))));
Run Code Online (Sandbox Code Playgroud)

请注意,由于EXPECT_THAT()工作原理,您需要将 throwing 语句放入不带参数的可调用内容中。因此上面例子中的 lambda 表达式。


编辑:此功能从版本 1.11开始包含。

另请注意,此功能并未包含在 1.10 版本中,但已合并到master. 由于 GTest 遵循 Abseil 的 live at head 政策,因此目前没有计划推出新版本。此外,他们似乎没有遵循 Abseil 的政策,为那些不能/不会生活在头部的用户发布特定版本。

  • 恕我直言,这比所有其他答案都要好,因为它简洁并检查异常及其消息。请注意,这需要使用 Gmock (gmock/gmock.h),而不仅仅是纯 GTest(gtest/gtest.h)。 (3认同)

Lil*_*ste 15

Jeff Langr在他的书" 使用测试驱动开发的现代C++编程"中描述了一种很好的方法:

如果您的[testing]框架不支持确保抛出异常的单行声明性断言,则可以在测试中使用以下结构:

    TEST(ATweet, RequiresUserNameToStartWithAnAtSign) {
        string invalidUser("notStartingWith@");
        try {
            Tweet tweet("msg", invalidUser);
            FAIL();
        }
        catch(const InvalidUserException& expected) {}
    }
Run Code Online (Sandbox Code Playgroud)

[...] 如果必须在抛出异常后验证任何后置条件,则可能还需要使用try-catch结构.例如,您可能希望验证与抛出的异常对象关联的文本.

    TEST(ATweet, RequiresUserNameToStartWithAtSign) {
        string invalidUser("notStartingWith@");
        try {
            Tweet tweet("msg", invalidUser);
            FAIL();
        }
        catch(const InvalidUserException& expected) {
            ASSERT_STREQ("notStartingWith@", expected.what());
        }
    }
Run Code Online (Sandbox Code Playgroud)

(第95页)

这是我使用的方法,并在其他地方实践过.

编辑:正如@MikeKinghan所指出的,这与提供的功能并不完全相符EXPECT_THROW; 如果抛出错误的异常,测试不会失败.catch可以添加一个附加条款来解决这个问题:

catch(...) {
    FAIL();
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*ndl 6

GTest 于 2020 年 8 月 24 日(v1.10 后)添加了一项新功能master,我在单独的答案中对此进行了解释。不过,我会留下这个答案,因为如果您使用的版本不支持新功能,它仍然有帮助。此外,这个解决方案和类似的解决方案更容易使用。


另请参阅Bryant解决方案,该解决方案对此进行了改进。(不再exception_ptr污染你的范围)


由于我需要做几个这样的测试,我编写了一个宏,它基本上包含Mike Kinghan的答案,但“删除”了所有样板代码:

#define ASSERT_THROW_KEEP_AS_E(statement, expected_exception) \
    std::exception_ptr _exceptionPtr; \
    try \
    { \
        (statement);\
        FAIL() << "Expected: " #statement " throws an exception of type " \
          #expected_exception ".\n  Actual: it throws nothing."; \
    } \
    catch (expected_exception const &) \
    { \
        _exceptionPtr = std::current_exception(); \
    } \
    catch (...) \
    { \
        FAIL() << "Expected: " #statement " throws an exception of type " \
          #expected_exception ".\n  Actual: it throws a different type."; \
    } \
    try \
    { \
        std::rethrow_exception(_exceptionPtr); \
    } \
    catch (expected_exception const & e)
Run Code Online (Sandbox Code Playgroud)

用法:

ASSERT_THROW_KEEP_AS_E(foo(), MyException)
{
    ASSERT_STREQ("Cucumber overflow", e.msg());
}
Run Code Online (Sandbox Code Playgroud)

注意事项:

  • 由于宏在当前作用域中定义了一个变量,因此它只能使用一次。
  • 需要 C++11std::exception_ptr


red*_*792 5

我建议根据 Mike Kinghan 的方法定义一个新的宏。

#define ASSERT_EXCEPTION( TRY_BLOCK, EXCEPTION_TYPE, MESSAGE )        \
try                                                                   \
{                                                                     \
    TRY_BLOCK                                                         \
    FAIL() << "exception '" << MESSAGE << "' not thrown at all!";     \
}                                                                     \
catch( const EXCEPTION_TYPE& e )                                      \
{                                                                     \
    EXPECT_EQ( MESSAGE, e.what() )                                    \
        << " exception message is incorrect. Expected the following " \
           "message:\n\n"                                             \
        << MESSAGE << "\n";                                           \
}                                                                     \
catch( ... )                                                          \
{                                                                     \
    FAIL() << "exception '" << MESSAGE                                \
           << "' not thrown with expected type '" << #EXCEPTION_TYPE  \
           << "'!";                                                   \
}
Run Code Online (Sandbox Code Playgroud)

迈克的TEST(foo_test,out_of_range)例子是

TEST(foo_test,out_of_range)
{
    foo f;
    ASSERT_EXCEPTION( { f.bar(111); }, std::out_of_range, "Out of range" );
}
Run Code Online (Sandbox Code Playgroud)

我认为最终会更具可读性。