GoogleTestでマクロを使ってテストを自動生成するバッドノウハウ

Posted on

C++のテストフレームワークのGoogleTest、活用してますか!!私は大好きです!!(何

テストフレームワークを使ってると、マクロを使って似たようなテストをまとめたくなる時があると思います。前後のセットアップだけ同じで型名とテストする値だけが違うとか。できればテンプレートを使ったほうがいいと思いますが、そうもできない場合もあるでしょう。

#define _MY_TEST(TYPE, VALUE, EXPECTED) \
{ /* plus */\
TYPE result;\
ASSERT_NO_THROW( result=do_sth<TYPE>(VALUE) );\
ASSERT_EQ( EXPECTED, result );\
}\
{ /* minus */\
TYPE result;\
ASSERT_NO_THROW( result=do_sth<TYPE>(-VALUE) ); /* マイナス */\
ASSERT_EQ( EXPECTED, result );\
}

TEST(MyTest, TEST_A)
{
_MY_TEST(int, 0x7fffffff, 0x80000000); // do_sthは何するんだろう…
_MY_TEST(unsigned short, 0x7fff, 0x8000);
...
}

でも、コレは動かないのです。こんなエラーメッセージが出ちゃいます。

****.cpp: In member function ‘virtual void MyTest_TEST_A_Test::TestBody()’:
****.cpp:719:1140: error: duplicate label ‘gtest_label_testnothrow_719’
****.cpp:720:1171: error: duplicate label ‘gtest_label_testnothrow_720’

さて…GOTOのラベルがかぶっているようです。なんでなのかは、ASSERT_NO_THROWの先を見ればわかります。

#define GTEST_TEST_NO_THROW_(statement, fail) \
 GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
 if (::testing::internal::AlwaysTrue()) { \
 try { \
 GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
 } \
 catch (...) { \
 goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \
 } \
 } else \
 GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \
 fail("Expected: " #statement " doesn't throw an exception.\n" \
 "  Actual: it throws.")

というわけで、GOTOのラベルに__LINE__マクロで取得した行数を使っているのですが、関数風マクロで展開するソースは一行で展開されるので、先ほどの_MY_TESTの複数のASSERT_NO_THROWで同じ__LINE__が使われてしまい、GOTOのラベルがかぶるのでさっきのようなエラーでコンパイルエラーになってしまったのでした。

むー。困りましたね。

マクロ内でマクロのディレクティブは使えない

たとえばこんな事はできません。

#define _MY_TEST(TYPE, VALUE, EXPECTED, x) \
 { /* plus */\
 TYPE result;\
\
#line (x*1000) /*lineディレクティブで書き換えてしまえばよいのでは!? */\
 ASSERT_NO_THROW( result=do_sth<TYPE>(VALUE) );\
 ASSERT_EQ( EXPECTED, result );\
 }\
 { /* minus */\
 TYPE result;\
#line (x*1000)+1 /*上とは違う番号を使っている */\
 ASSERT_NO_THROW( result=do_sth<TYPE>(-VALUE) ); /* マイナス */\
 ASSERT_EQ( EXPECTED, result );\
 }

#lineというディレクティブを使うと__LINE__での行番号がこの行以降差し替わるので、これで変えてあげればかぶらなくなるのでは?と思ったのですが、やはり一行に展開されてしまうので、このように展開されてしまい…

{.... #line (x*1000)+1 ASSERT_NO_THROW(.... /*←改行なし*/

これでは文法エラーにしかなりません。

 仕方ないのでファイルを分ける

何かいい方法無いのかなーと考えていたのですが、プリプロセッサには関数風マクロ以外にもソースの繰り返しを行う方法がありました。includeです。

さっきのマクロの内容そのものの別ヘッダファイルを作り…

/* inc.h */
#define _MY_TEST(TYPE, VALUE, EXPECTED, x) 
 { /* plus */
 TYPE result;

 ASSERT_NO_THROW( result=do<TYPE>(VALUE) );
 ASSERT_EQ( EXPECTED, result );
 }
 { /* minus */
 TYPE result;
 ASSERT_NO_THROW( result=do<TYPE>(-VALUE) ); /* マイナス */
 ASSERT_EQ( EXPECTED, result );
 }

それをincludeして繰り返しすれば…

TEST(MyTest, TEST_A)
{
#define TYPE int
#define VALUE 0x7fffffff
#define EXPECTED 0x80000000
#include "inc.h"
#undef TYPE
#undef VALUE
#undef EXPECTED
}

別ファイル内でのgoto扱いになるので、さっきのエラーは出なくなります。若干不恰好ですが、手動で繰り返すよりは…。

できればテンプレートを使う方が良いと思う

マクロではなくテンプレートを使ってテストをパラメータ化すれば、このような問題は起きません。テンプレートではマクロの##(シンボル連結)とか#(シンボルの文字列化)みたいな機能は使えませんが、もっと安全だし楽なので、出来ればテンプレートで出来ないか先に考えるほうが良いと思います。。。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください