web-dev-qa-db-fra.com

Tester qu'un type d'exception spécifique est levé ET que l'exception possède les bonnes propriétés

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?

39
Mr. Boy

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

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

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

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.

1
redwizard792

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)

Usage:

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

Mises en garde:

  • Comme la macro définit une variable dans la portée actuelle, elle ne peut être utilisée qu'une seule fois.
  • C++ 11 est nécessaire pour std::exception_ptr
1
Matthäus Brandl

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.

0
morini

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

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