c++ unit-testing exception googletest

c++ - Probar un tipo de excepción específico lanzado Y la excepción tiene las propiedades correctas



unit-testing exception (8)

Quiero probar que MyException se arroja en un caso determinado. EXPECT_THROW es bueno aquí. Pero también quiero comprobar que la excepción tiene un estado específico, por ejemplo, e.msg() == "Cucumber overflow" .

¿Cómo se implementa mejor en GTest?


Como necesito hacer varias de esas pruebas, escribí una macro que básicamente incluye la respuesta de Mike Kinghan pero "elimina" todo el código repetitivo:

#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)

Uso:

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

Advertencias:

  • Como la macro define una variable en el alcance actual, por lo que solo se puede usar una vez.
  • C ++ 11 es necesario para std::exception_ptr

Jeff Langr describe un buen enfoque en su libro, Modern C ++ Programming with Test-Driven Development :

Si su marco de [prueba] no admite una afirmación declarativa de una sola línea que garantice que se lanza una excepción, puede usar la siguiente estructura en su prueba:

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

[...] También es posible que necesite utilizar la estructura try-catch si debe verificar cualquier condición posterior después de lanzar la excepción. Por ejemplo, es posible que desee verificar el texto asociado con el objeto de excepción arrojado.

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

(p.95)

Este es el enfoque que he usado y he visto en práctica en otros lugares.

Editar: como ha señalado @MikeKinghan, esto no coincide con la funcionalidad proporcionada por EXPECT_THROW ; la prueba no falla si se lanza la excepción incorrecta. Se podría agregar una cláusula de catch adicional para abordar esto:

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


Me gusta la mayoría de las respuestas. Sin embargo, dado que parece que GoogleTest proporciona EXPECT_PRED_FORMAT que ayuda a facilitar esto, me gustaría agregar esta opción a la lista de respuestas:

MyExceptionCreatingClass testObject; // implements TriggerMyException() EXPECT_PRED_FORMAT2(ExceptionChecker, testObject, "My_Expected_Exception_Text");

donde ExceptionChecker se define como:

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


Principalmente respondí en segundo lugar a Lilshieste, pero agregaría que también debes verificar que no se lanza el tipo de excepción incorrecto :

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


Puedes probar la prueba ligera 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(); }


Recomiendo definir una macro nueva basada en el enfoque de 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 / << "''!"; / }

El ejemplo de TEST(foo_test,out_of_range) de Mike TEST(foo_test,out_of_range) sería entonces

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

que creo que termina siendo mucho más legible


Un colega propuso la solución simplemente volviendo a lanzar la excepción.

La destreza: no se necesitan declaraciones FAIL () adicionales, solo las dos llamadas EXPECT ... que prueban los bits que realmente quiere: la excepción como tal y su valor.

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


Utilizo la macro de Matthäus Brandl con la siguiente modificación menor:

Pon la linea

std::exception_ptr _exceptionPtr;

afuera (antes) la definición de macro como

static std::exception_ptr _exceptionPtr;

para evitar la definición múltiple del símbolo _exceptionPtr .