Я хочу проверить, что MyException
выбрано в определенном случае. EXPECT_THROW
здесь хорошо. Но я также хочу проверить, что исключение имеет конкретное состояние, например e.msg() == "Cucumber overflow"
.
Как это лучше всего реализовать в GTest?
Я хочу проверить, что MyException
выбрано в определенном случае. EXPECT_THROW
здесь хорошо. Но я также хочу проверить, что исключение имеет конкретное состояние, например e.msg() == "Cucumber overflow"
.
Как это лучше всего реализовать в GTest?
Я в основном второй ответ Лильшиэста, но добавлю, что вы также должны убедитесь, что неправильный тип исключения не выбрасывается:
#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();
}
Сотрудник придумал решение, просто перекинув исключение.
Уловка: нет необходимости в дополнительных операциях 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 );
}
Джефф Лэнгр описывает хороший подход в своей книге: Современное программирование на С++ с тестовым развитием:
Если ваша [тестовая] среда не поддерживает однострочное декларативное утверждение, обеспечивающее исключение, вы можете использовать следующую структуру в своем тесте:
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)
Это тот подход, который я использовал, и видел на практике в другом месте.
Изменить: Как было указано в @MikeKinghan, это не совсем соответствует функциональности, предоставляемой EXPECT_THROW
; тест не прерывается, если выбрано неправильное исключение. Для этого можно добавить дополнительное предложение catch
:
catch(...) {
FAIL();
}
Я рекомендую определить новый макрос на основе подхода Майка Кингхана.
#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" );
}
который, как мне кажется, становится более читаемым.
Как мне нужно сделать несколько таких тестов, я написал макрос, который в основном включает ответ Майка Кингхана, но "удаляет" весь шаблонный код:
#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
Мне нравится большинство ответов. Однако, поскольку кажется, что 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();
}
Я использую макрос 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");
Вы можете попробовать легкий легкий тест:
#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();
}