web-dev-qa-db-ja.com

特定の例外タイプがスローされ、例外に適切なプロパティがあることをテストする

特定のケースでMyExceptionがスローされることをテストしたい。ここでは_EXPECT_THROW_が適切です。しかし、e.msg() == "Cucumber overflow"などの特定の状態にある例外もチェックしたいです。

これはGTestでどのように最適に実装されていますか?

47
Mr. Boy

Lilshiesteの答えの大部分は2番目ですが、wrong例外タイプがスローされないことも確認する必要があることを付け加えます。

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

同僚が例外を再スローするだけで解決策を思い付きました。

コツ:余分なFAIL()ステートメントは不要で、実際に必要なビットをテストする2つの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 );
}
34
minastaros

Jeff Langrは、彼の本で良いアプローチを説明しています テスト駆動開発によるモダンC++プログラミング

[テスト]フレームワークが、例外がスローされることを保証する単一行の宣言的なアサートをサポートしていない場合、テストで次の構造を使用できます。

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

[...]例外のスロー後に事後条件を検証する必要がある場合は、try-catch構造を使用する必要があります。たとえば、スローされた例外オブジェクトに関連付けられているテキストを確認できます。

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

(p.95)

これは私が使用したアプローチであり、他の場所で実際に見ています。

編集:@MikeKinghanが指摘したように、これは提供された機能とquiteに一致しませんEXPECT_THROW;間違った例外がスローされてもテストは失敗しません。これに対処するために、追加のcatch句を追加できます。

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

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  \
           << "'!";                                                   \
}
_

マイクの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" );
}
_

最終的にはもっと読みやすくなると思います。

1
redwizard792

このようなテストをいくつか行う必要があるため、マイク・キングハンの答えを基本的に含むマクロを作成しましたが、すべての定型コードを「削除」します。

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

警告:

  • マクロは現在のスコープで変数を定義するため、1回しか使用できません。
  • std::exception_ptrにはC++ 11が必要です
1

私はMatthäusBrandlのマクロを次の小さな変更を加えて使用します。

行を入れて

std::exception_ptr _exceptionPtr;

マクロ定義の外側(f.e.以前)

static std::exception_ptr _exceptionPtr;

シンボル_exceptionPtrの多重定義を避けるため。

0
morini

以前の回答を拡張し、特定のタイプの例外がスローされ、そのメッセージが指定された文字列で始まることを検証するマクロ。

例外がスローされない場合、例外タイプが間違っている場合、またはメッセージが指定された文字列で始まっていない場合、テストは失敗します。

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

私は答えのほとんどが好きです。ただし、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();
}
0
EdS