测试一个特定的异常类型,该异常具有正确的属性

时间:2021-07-18 20:27:55

I want to test that MyException is thrown in a certain case. EXPECT_THROW is good here. But I also want to check the exception has a specific state e.g e.msg() == "Cucumber overflow".

我想测试在特定情况下抛出的MyException。EXPECT_THROW是好的。但我也想检查异常有一个特定的状态e。g e.msg() =“黄瓜溢出”。

How is this best implemented in GTest?

如何在GTest中实现这一点?

8 个解决方案

#1


30  

I mostly second Lilshieste's answer but would add that you also should verify that the wrong exception type is not thrown:

我主要支持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();
}

#2


14  

Jeff Langr describes a good approach in his book, Modern C++ Programming with Test-Driven Development:

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

If your [testing] framework does not support a single-line declarative assert that ensures an exception is thrown, you can use the following structure in your test:

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

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

[...] You might also need to use the try-catch structure if you must verify any postconditions after the exception is thrown. For example, you may want to verify the text associated with the thrown exception object.

[…如果在抛出异常后必须验证任何后置条件,您可能还需要使用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)

(p.95)

This is the approach I've used, and have seen in practice elsewhere.

这是我用过的方法,在实践中也见过。

Edit: As has been pointed out by @MikeKinghan, this doesn't quite match the functionality provided by EXPECT_THROW; the test doesn't fail if the wrong exception is thrown. An additional catch clause could be added to address this:

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

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

#3


13  

A colleague came up with the solution by just re-throwing the exception.

一个同事提出了解决方案,只是重新抛出了一个例外。

The knack: no need of extra FAIL() statements, just the two EXPECT... calls that test the bits you actually want: the exception as such and its value.

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

#4


0  

I recommend defining a new macro based on Mike Kinghan's approach.

我建议基于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's TEST(foo_test,out_of_range) example would then be

接下来是Mike的测试(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" );
}

which I think ends up being much more readable.

我觉得这样更容易读懂。

#5


0  

I like most of the answers. However, since it seems that GoogleTest provides EXPECT_PRED_FORMAT that helps facilitating this, I'd like to add this option to the list of answers:

我喜欢大部分的答案。然而,由于GoogleTest提供了EXPECT_PRED_FORMAT,这有助于实现这一目的,所以我想将这个选项添加到答案列表中:

MyExceptionCreatingClass testObject; // implements TriggerMyException()

EXPECT_PRED_FORMAT2(ExceptionChecker, testObject, "My_Expected_Exception_Text");

where ExceptionChecker is defined as:

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

#6


0  

As I need to do several of such tests I wrote a macro that basically includes Mike Kinghan's answer but "removes" all the boilerplate code:

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

Usage:

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

Caveats:

  • As the macro defines a variable in the current scope, so it can only be used once.
  • 因为宏在当前作用域中定义了一个变量,所以只能使用一次。
  • C++11 is needed for std::exception_ptr
  • std: exception_ptr需要使用c++ 11

#7


0  

I use Matthäus Brandl's macro with the following minor modification:

我使用Matthaus Brandl的宏,做了以下小修改:

Put the line

把线

std::exception_ptr _exceptionPtr;

outside (f.e. before) the macro definition as

外部(f.e. before)的宏定义为

static std::exception_ptr _exceptionPtr;

to avoid multiple definition of the symbol _exceptionPtr.

为了避免符号_exceptionPtr的多重定义。

#8


-1  

You can try Boost lightweight test:

你可以试试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();
}

#1


30  

I mostly second Lilshieste's answer but would add that you also should verify that the wrong exception type is not thrown:

我主要支持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();
}

#2


14  

Jeff Langr describes a good approach in his book, Modern C++ Programming with Test-Driven Development:

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

If your [testing] framework does not support a single-line declarative assert that ensures an exception is thrown, you can use the following structure in your test:

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

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

[...] You might also need to use the try-catch structure if you must verify any postconditions after the exception is thrown. For example, you may want to verify the text associated with the thrown exception object.

[…如果在抛出异常后必须验证任何后置条件,您可能还需要使用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)

(p.95)

This is the approach I've used, and have seen in practice elsewhere.

这是我用过的方法,在实践中也见过。

Edit: As has been pointed out by @MikeKinghan, this doesn't quite match the functionality provided by EXPECT_THROW; the test doesn't fail if the wrong exception is thrown. An additional catch clause could be added to address this:

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

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

#3


13  

A colleague came up with the solution by just re-throwing the exception.

一个同事提出了解决方案,只是重新抛出了一个例外。

The knack: no need of extra FAIL() statements, just the two EXPECT... calls that test the bits you actually want: the exception as such and its value.

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

#4


0  

I recommend defining a new macro based on Mike Kinghan's approach.

我建议基于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's TEST(foo_test,out_of_range) example would then be

接下来是Mike的测试(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" );
}

which I think ends up being much more readable.

我觉得这样更容易读懂。

#5


0  

I like most of the answers. However, since it seems that GoogleTest provides EXPECT_PRED_FORMAT that helps facilitating this, I'd like to add this option to the list of answers:

我喜欢大部分的答案。然而,由于GoogleTest提供了EXPECT_PRED_FORMAT,这有助于实现这一目的,所以我想将这个选项添加到答案列表中:

MyExceptionCreatingClass testObject; // implements TriggerMyException()

EXPECT_PRED_FORMAT2(ExceptionChecker, testObject, "My_Expected_Exception_Text");

where ExceptionChecker is defined as:

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

#6


0  

As I need to do several of such tests I wrote a macro that basically includes Mike Kinghan's answer but "removes" all the boilerplate code:

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

Usage:

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

Caveats:

  • As the macro defines a variable in the current scope, so it can only be used once.
  • 因为宏在当前作用域中定义了一个变量,所以只能使用一次。
  • C++11 is needed for std::exception_ptr
  • std: exception_ptr需要使用c++ 11

#7


0  

I use Matthäus Brandl's macro with the following minor modification:

我使用Matthaus Brandl的宏,做了以下小修改:

Put the line

把线

std::exception_ptr _exceptionPtr;

outside (f.e. before) the macro definition as

外部(f.e. before)的宏定义为

static std::exception_ptr _exceptionPtr;

to avoid multiple definition of the symbol _exceptionPtr.

为了避免符号_exceptionPtr的多重定义。

#8


-1  

You can try Boost lightweight test:

你可以试试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();
}