it-swarm.com.ru

Проверка конкретного типа исключения генерируется И исключение имеет правильные свойства

Я хочу проверить, что MyException выбрасывается в определенном случае. EXPECT_THROW здесь хорош Но я также хочу проверить, что исключение имеет определенное состояние, например, e.msg() == "Cucumber overflow".

Как это лучше всего реализовано в GTest?

39
Mr. Boy

Я в основном отвечаю ответом Лилшиесте, но добавлю, что вам также следует Убедиться, что исключение типа неправильный не выброшено:

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

Коллега придумал решение, просто перебросив исключение.

Недостаток: нет необходимости в дополнительных операторах 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 );
}
26
minastaros

Джефф Лэнгр (Jeff Langr) описывает хороший подход в своей книге, Современное программирование на C++ с тест-ориентированной разработкой :

Если ваша платформа [testing] не поддерживает однострочное декларативное утверждение, которое обеспечивает выдачу исключения, вы можете использовать следующую структуру в своем тесте:

    TEST(ATweet, RequiresUserNameToStartWithAnAtSign) {
        string invalidUser("[email protected]");
        try {
            Tweet tweet("msg", invalidUser);
            FAIL();
        }
        catch(const InvalidUserException& expected) {}
    }

[...] Вам также может понадобиться использовать структуру try-catch, если вы хотите проверить какие-либо постусловия после того, как сгенерировано исключение. Например, вы можете проверить текст, связанный с брошенным объектом исключения.

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

(С.95)

Этот подход я использовал и видел на практике в другом месте.

Edit: Как было отмечено @MikeKinghan, это не вполне не соответствует функциональности, предоставляемой EXPECT_THROW; тест не провалится, если выдается неправильное исключение. Для решения этой проблемы можно добавить дополнительное предложение catch:

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

Я рекомендую определить новый макрос на основе подхода Майка Кингхана.

#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  \
           << "'!";                                                   \
}

Пример Майка 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" );
}

который, я думаю, в конечном итоге становится гораздо более читабельным.

1
redwizard792

Поскольку мне нужно сделать несколько таких тестов, я написал макрос, который в основном включает ответ Майка Кингхана, но «удаляет» весь стандартный код:

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

Предостережения:

  • Поскольку макрос определяет переменную в текущей области видимости, он может использоваться только один раз.
  • C++ 11 необходим для std::exception_ptr
1
Matthäus Brandl

Я использую макрос Маттеуса Брандла со следующими незначительными изменениями:

Поставить линию

std::exception_ptr _exceptionPtr;

вне (например, до) определения макроса как

static std::exception_ptr _exceptionPtr;

чтобы избежать многократного определения символа _exceptionPtr.

0
morini

Расширяя предыдущие ответы, макрос, который проверяет, что было сгенерировано исключение данного типа, и сообщение о котором начинается с указанной строки. 

Тест не пройден, если либо не сгенерировано исключение, либо неверный тип исключения, либо сообщение не начинается с указанной строки.

#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");
0
Dave

Мне нравится большинство ответов. Однако, поскольку кажется, что 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();
}
0
EdS