Je veux tester que MyException
est lancé dans un certain cas. EXPECT_THROW
est bon ici. Mais je veux aussi vérifier que l'exception a un état spécifique, par exemple e.msg() == "Cucumber overflow"
.
Comment cela est-il mieux implémenté dans GTest?
Je rejoins principalement la réponse de Lilshieste, mais ajouterais que vous devriez également vérifier Que le type mauvais exception n'est pas levé
#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();
}
Un collègue a proposé la solution en rejetant simplement l'exception.
Le talent: pas besoin d'instructions FAIL () supplémentaires, juste les deux appels EXPECT ... qui testent les bits que vous voulez réellement: l'exception en tant que telle et sa valeur.
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 );
}
Jeff Langr décrit une bonne approche dans son livre, Programmation C++ moderne avec développement piloté par les tests :
Si votre infrastructure [testing] ne prend pas en charge une assertion déclarative à une seule ligne garantissant la levée d'une exception, vous pouvez utiliser la structure suivante dans votre test:
TEST(ATweet, RequiresUserNameToStartWithAnAtSign) { string invalidUser("notStartingWith@"); try { Tweet tweet("msg", invalidUser); FAIL(); } catch(const InvalidUserException& expected) {} }
[...] Vous devrez peut-être également utiliser la structure try-catch si vous devez vérifier les post-conditions après la levée de l'exception. Par exemple, vous pouvez vérifier le texte associé à l'objet exception levé.
TEST(ATweet, RequiresUserNameToStartWithAtSign) { string invalidUser("notStartingWith@"); try { Tweet tweet("msg", invalidUser); FAIL(); } catch(const InvalidUserException& expected) { ASSERT_STREQ("notStartingWith@", expected.what()); } }
(p.95)
C’est cette approche que j’ai utilisée et que j’ai vue pratiquer ailleurs.
Edit: Comme l'a souligné @MikeKinghan, cela ne correspond pas à la fonctionnalité {tout à fait} _ fournie par EXPECT_THROW
; le test n'échoue pas si la mauvaise exception est levée. Une clause catch
supplémentaire pourrait être ajoutée pour remédier à cela:
catch(...) {
FAIL();
}
Je recommande de définir une nouvelle macro basée sur l'approche 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 \
<< "'!"; \
}
L'exemple TEST(foo_test,out_of_range)
de Mike serait alors
TEST(foo_test,out_of_range)
{
foo f;
ASSERT_EXCEPTION( { f.bar(111); }, std::out_of_range, "Out of range" );
}
qui, je pense, finit par être beaucoup plus lisible.
Comme j'ai besoin de faire plusieurs de ces tests, j'ai écrit une macro qui inclut essentiellement la réponse de Mike Kinghan mais "supprime" tout le code standard:
#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
J'utilise la macro de Matthäus Brandl avec la modification mineure suivante:
Mettre la ligne
std::exception_ptr _exceptionPtr;
en dehors (par exemple, avant) de la définition de macro comme
static std::exception_ptr _exceptionPtr;
pour éviter plusieurs définitions du symbole _exceptionPtr
.
En développant les réponses précédentes, une macro qui vérifie qu'une exception d'un type donné a été levée et dont le message commence par la chaîne fournie.
Le test échoue si aucune exception n'est levée, si le type d'exception est incorrect ou si le message ne commence pas par la chaîne fournie.
#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";\
}
Exemple d'utilisation:
ASSERT_THROWS_STARTS_WITH(foo(-2), std::invalid_argument, "Bad argument: -2");
J'aime la plupart des réponses. Cependant, comme il semble que GoogleTest fournisse EXPECT_PRED_FORMAT pour faciliter cette tâche, j'aimerais ajouter cette option à la liste des réponses:
MyExceptionCreatingClass testObject; // implements TriggerMyException()
EXPECT_PRED_FORMAT2(ExceptionChecker, testObject, "My_Expected_Exception_Text");
où ExceptionChecker est défini comme:
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();
}