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
.