测试引发特定异常类型并且该异常具有正确的属性

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

如何在 GTest 中最好地实现这一点?

90393 次浏览

Jeff Langr 在他的书《 现代 c + + 测试驱动开发编程:

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

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

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

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

(p. 95)

这是我曾经使用过的方法,也在其他地方见到过。

编辑: 正如@MikeKinghan 所指出的,这与 EXPECT_THROW提供的功能不匹配; 如果抛出了错误的异常,测试不会失败。可以增加一个 catch条款来解决这个问题:

catch(...) {
FAIL();
}

我大多数时候支持莉莎雅丝特的回答,但是你也应该这样做 验证未引发 错了异常类型:

#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();
}

您可以尝试 Boost 轻量级测试:

#include <boost/detail/lightweight_test.hpp>
#include <stdexcept>


void function_that_would_throw(int x)
{
if (x > 0) {
throw std::runtime_error("throw!");
}
}


int main() {
BOOST_TEST_THROWS(function_that_would_throw(10), std::runtime_error);
boost::report_errors();
}

我建议根据 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  \
<< "'!";                                                   \
}

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

我觉得这样更容易理解。

一个同事通过重新引发异常找到了解决方案。

诀窍: 不需要额外的 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 );
}

大部分答案我都喜欢。然而,由于 GoogleTest 似乎提供了 EXPECT _ PRED _ FORMAT 来帮助实现这一点,我想在答案列表中添加这个选项:

MyExceptionCreatingClass testObject; // implements TriggerMyException()


EXPECT_PRED_FORMAT2(ExceptionChecker, testObject, "My_Expected_Exception_Text");

其中 ExceptionChecker 定义为:

testing::AssertionResult ExceptionChecker(const char* aExpr1,
const char* aExpr2,
MyExceptionCreatingClass& aExceptionCreatingObject,
const char* aExceptionText)
{
try
{
aExceptionCreatingObject.TriggerMyException();
// we should not get here since we expect an exception
return testing::AssertionFailure() << "Exception '" << aExceptionText << "' is not thrown.";
}
catch (const MyExpectedExceptionType& e)
{
// expected this, but verify the exception contains the correct text
if (strstr(e.what(), aExceptionText) == static_cast<const char*>(NULL))
{
return testing::AssertionFailure()
<< "Exception message is incorrect. Expected it to contain '"
<< aExceptionText << "', whereas the text is '" << e.what() << "'.\n";
}
}
catch ( ... )
{
// we got an exception alright, but the wrong one...
return testing::AssertionFailure() << "Exception '" << aExceptionText
<< "' not thrown with expected type 'MyExpectedExceptionType'.";
}
return testing::AssertionSuccess();
}

GTest master在2020-08-24(1.10后)增加了一个新特性,我在 另一个答案中解释过。然而,我将留下这个答案,因为如果您正在使用的版本不支持这个新特性,它仍然会有所帮助。


因为我需要做几个这样的测试,所以我编写了一个宏,基本上包含了 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)

用法:

ASSERT_THROW_KEEP_AS_E(foo(), MyException)
{
ASSERT_STREQ("Cucumber overflow", e.msg());
}

警告:

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

我使用了 Matthäus Brandl 的宏,做了以下小小的修改:

放绳子

std::exception_ptr _exceptionPtr;

在宏定义之外(例如之前)

static std::exception_ptr _exceptionPtr;

避免符号 _exceptionPtr的多重定义。

在以前的答案上展开,一个宏,用于验证是否引发了给定类型的异常,以及该异常的消息是否以提供的字符串开头。

如果没有引发异常、异常类型错误或消息没有以提供的字符串开始,则测试失败。

#define ASSERT_THROWS_STARTS_WITH(expr, exc, msg) \
try\
{\
(expr);\
FAIL() << "Exception not thrown";\
}\
catch (const exc& ex)\
{\
EXPECT_THAT(ex.what(), StartsWith(std::string(msg)));\
}\
catch(...)\
{\
FAIL() << "Unexpected exception";\
}

用法例子:

ASSERT_THROWS_STARTS_WITH(foo(-2), std::invalid_argument, "Bad argument: -2");

我的版本; 它产生与 EXPECT _ THROW 相同的输出,只是添加了字符串测试:

#define EXPECT_THROW_MSG(statement, expected_exception, expected_what)                    \
try                                                                                     \
{                                                                                       \
statement;                                                                            \
FAIL() << "Expected: " #statement " throws an exception of type " #expected_exception \
".\n"                                                                       \
"  Actual: it throws nothing.";                                             \
}                                                                                       \
catch (const expected_exception& e)                                                     \
{                                                                                       \
EXPECT_EQ(expected_what, std::string{e.what()});                                      \
}                                                                                       \
catch (...)                                                                             \
{                                                                                       \
FAIL() << "Expected: " #statement " throws an exception of type " #expected_exception \
".\n"                                                                       \
"  Actual: it throws a different type.";                                    \
}

我建议使用

EXPECT_THROW(function_that_will_throw(), exception);

如果你的函数返回了一些东西,那么就使它无效:

EXPECT_THROW((void)function_that_will_throw(), exception);

我之前提供了一个宏来解决 一个更古老的答案中的这个问题。然而,时间已经过去了,GTest 添加了一个新特性,它允许在没有宏的情况下实现这一点。

这个特性是一组匹配器,例如,可以与 EXPECT_THAT()结合使用的 Throws。但是文档似乎没有更新,因此唯一的信息隐藏在 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"))));

请注意,由于 EXPECT_THAT()的工作原理,您需要将抛出语句放入无参数的可调用内容中。因此,上面例子中的 lambdas。


编辑: 这个特性从 版本1.11开始包含。

还要注意的是,这个特性并没有包含在1.10版本中,但是它已经合并到了 master中。因为 GTest 遵循 abseil 的现场直播策略,目前有 没有新的版本计划。此外,他们似乎没有遵循 abseil 的政策,发布特定版本的使用谁不能/不会生活在头部。

上面的答案很有帮助,但是我想分享一个我正在使用的解决方案,它可以在测试异常类型和“ what”值的同时保持每个测试的简短。它适用于引发从 异常派生的异常的任何函数,但是如果需要,可以修改(或模板化)以捕获其他类型。

我最初尝试使用 完美的转发类型的包装函式,但最终只是使用 Lambda作为包装。使用 Lambda对于重用来说非常灵活,允许直接调用函数时发生的所有预期的隐式类型转换,避免传递指针到成员函数等等。.

重要的一点是让包装器模板函数为“ what-mismatch”抛出一个自定义异常,该异常的类型不会从正在测试的函数中抛出。这会导致 抛出打印一个关于不匹配的漂亮错误。我从 运行时错误派生,因为该类有一个接受 字符串的构造函数。

class WhatMismatch : public std::runtime_error {
public:
WhatMismatch(const std::string& expectedWhat, const std::exception& e)
: std::runtime_error(std::string("expected: '") + expectedWhat +
"', actual: '" + e.what() + '\'') {}
};


template<typename F> auto call(const F& f, const std::string& expectedWhat) {
try {
return f();
} catch (const std::exception& e) {
if (expectedWhat != e.what()) throw WhatMismatch(expectedWhat, e);
throw;
}
}

应该抛出 : domain _ error的函数 的测试如下:

EXPECT_THROW(call([] { foo(); }, "some error message"), std::domain_error);

如果函数接受参数,只需像下面这样在 Lambda中捕获它们:

EXPECT_THROW(call([p1] { foo(p1); }, "some error message"), std::domain_error);

我发现 Brandlingo (宏观解决方案)给出的原始答案创建了比 EXPECT_THAT更好的体验,如果您需要以非平凡的方式检查异常的内容,那么 EXPECT_THAT仍然是有限的。

对于 Brandlingo 的回答,有一些警告,不过我在这里已经解决了:

  1. 通过嵌套 try 块,并仅在预期的异常发生时才抛出到外部块,删除了多次使用会造成烦恼的未作用域的 eption _ ptr。
  2. 假设任何意外的异常来自 std: : eption,并打印“ what ()”的值。对于提供关于发生了什么的提示很有用。如果异常不是从 std: : Exception 派生的,那么测试仍然会失败,只是不会得到一个行号。你可以通过添加另一个捕获来处理它,但是那真的不应该发生。
  3. 传入用于异常的变量名(为了可读性)。
#define ASSERT_THROW_AND_USE(statement, expected_exception, exception_varname)                   \
try {                                                                                         \
try {                                                                                     \
(statement);                                                                          \
FAIL() << "Expected: " #statement " throws an exception of type " #expected_exception \
".\n  Actual: it throws nothing.";                                          \
} catch (const expected_exception&) {                                                     \
throw;                                                                                \
} catch (const std::exception& e) {                                                       \
FAIL() << "Expected: " #statement " throws an exception of type " #expected_exception \
".\n  Actual: it throws a different type, with message: "                   \
<< std::quoted(e.what());                                                      \
}                                                                                         \
} catch (const expected_exception& exception_varname)

然后像这样使用它:

ASSERT_THROW_AND_USE(foo(), MyException, e)
{
ASSERT_STREQ("fail message", e.MyMethod());
}

如果抛出了错误的异常,您将得到如下结果:

Failed
Expected: foo() throws an exception of type MyException.
Actual: it throws a different type, with message: "some other thing"