特定のケースでMyException
がスローされることをテストしたい。ここでは_EXPECT_THROW
_が適切です。しかし、e.msg() == "Cucumber overflow"
などの特定の状態にある例外もチェックしたいです。
これはGTestでどのように最適に実装されていますか?
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();
}
同僚が例外を再スローするだけで解決策を思い付きました。
コツ:余分な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 );
}
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();
}
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" );
}
_
最終的にはもっと読みやすくなると思います。
このようなテストをいくつか行う必要があるため、マイク・キングハンの答えを基本的に含むマクロを作成しましたが、すべての定型コードを「削除」します。
#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
にはC++ 11が必要です私はMatthäusBrandlのマクロを次の小さな変更を加えて使用します。
行を入れて
std::exception_ptr _exceptionPtr;
マクロ定義の外側(f.e.以前)
static std::exception_ptr _exceptionPtr;
シンボル_exceptionPtr
の多重定義を避けるため。
以前の回答を拡張し、特定のタイプの例外がスローされ、そのメッセージが指定された文字列で始まることを検証するマクロ。
例外がスローされない場合、例外タイプが間違っている場合、またはメッセージが指定された文字列で始まっていない場合、テストは失敗します。
#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");
私は答えのほとんどが好きです。ただし、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();
}