ラベル gtest の投稿を表示しています。 すべての投稿を表示
ラベル gtest の投稿を表示しています。 すべての投稿を表示

2020年12月1日火曜日

[C++] Concepts で外部ライブラリの template よりも優先されるオーバーロード関数を書く

※この記事は C++ Advent Calendar 2020 一日目の記事です。

はじめに

 C++20 で Concepts が導入されました。
コンセプト - cpprefjp C++日本語リファレンス

コンセプトはテンプレートパラメータを制約する機能で、「コンセプトによって関数オーバーロードやテンプレート特殊化」ができます。
今までテンプレート関数のオーバーロードを書く場合は SFINAE などのテクニックを使って書いていたと思いますが、コンセプトで書きやすくなりました。
これも大きな変化の1つですが、コンセプト関数が通常の関数よりも優先して lookup されるのも便利になった点の1つです。
コンセプトによるオーバーロードの場合、コンセプト関数が通常の関数よりも優先して lookup され、要件を満たすコンセプト関数がなかったら他の候補を探しに行きます。SFINAE では優先順位がつけられないケースでもコンセプトなら可能になりました。

どういうこと?
SFINAE では解決できなかった優先順位問題とコンセプトを使った実装の例

↑こちらは、std::cout << できない型の場合は "unknown" を出力するようにしようとしている様子です。

donot-change.h には std::cout するだけの template 関数 print があります。
こちらは変更ができない想定です。(外部ライブラリとか)

SFINAE.cpp では頑張って、 std::cout ができない場合で特殊化しオーバーロードしようとしてます。他の書き方を書いたりもしましたが、どうしても ambiguous になってしまって条件を満たすことができませんでした。

一方 Concepts.cpp は printable ではないコンセプト制約でオーバーロードするだけで実現できています。
すごく簡単ですね。

ちなみに
ちなみに donot-change.h を変更可能とした場合は、以下のように書くことができます。
namespace printer_internal
{
template<typename Elem, typename Traits, typename T>
::std::basic_ostream<Elem, Traits>& operator << (::std::basic_ostream<Elem, Traits>& os, const T&)
{
    return os << "unknown";
}
}

// 解決順序
// foo::operator <<
// ::operator <<
// ::printer_internal::operator <<
template<typename T>
void print(const T& val)
{
    using namespace ::printer_internal;
    ::std::cout << val << ::std::endl;
}
これは ADL の解決順序を利用して実現しています。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://wandbox.org/permlink/rlQoH37cvYTI23qo 
使い所は?
例でも示したとおり、オーバーロードしたい対象の関数を変更できるのであれば、そちらで解決する方法を模索するのが良いと思います。
ただ、外部ライブラリなど変更することができない場合、非侵入的に解決する方法として覚えておくと役に立つ日が来るかもしれません。

実例
そもそも私がなんで外部ライブラリの挙動をオーバーロードで変更したいと思ったのかというと、以下の理由でした。

* Google Test はアサーションが失敗すると、そのときの値を出力します
* std::cout できない場合はバイト列を出力します
* できない場合の対応は「ちなみに」の ADL を利用した方法で実現されています
* 古い Google Test ではこの機能がない
* 自作テスティングフレームワーク iutest は Google Test と互換性がある
* iutest は Google Test の拡張機能としての一面もある
* 古い Google Test と iutest の組み合わせでも不明な型を printable にしたい
* Google Test 側のコードは書き換えられない
* どうしよう

実際にコンセプトで対応してる部分はこちらになります。
最後に
教えていただいた いなむのみたま(@mitama_rs) 先生ありがとうございました。
ちなみにそのツイートにもぶら下がってますが、C++20 未満のコンパイラでこの問題を解決する方法も募集しておりますmm


2015年6月16日火曜日

[C++] Private な関数のテスト

本記事で扱うテスティングフレームワーク
Google Test
iutest (Google Test ライクなヘッダーオンリーテスティングフレームワーク)

C++ でテスト書いていると問題になるのが、private なメンバー関数をどうテストするか、です。
成功法としては、テストクラスを friend することです。
間違っても #define private public などとしてはいけません。

friend
Google Test の場合、テストクラスを friend する FRIEND_TEST マクロが用意されています。
また、テストフィクスチャクラスに対して friend 宣言すれば、そのテストケースではアクセス可能になります。

参照: GoogleTestでprivateメンバ関数をテストする | Geospatial屋
詳しいことは参照先を見ていただくとして、簡単に使い方を例として示します。

例1: FRIEND_TEST
// テスト対象クラス
class Hoge
{
   FRIEND_TEST(HogeTest, A); // TEST(HogeTest, A) から見えるようにする
   FRIEND_TEST(HogeTest, B); // TEST(HogeTest, B) から見えるようにする
private:
   int GetA() { return 1; }
   int GetB() { return 2; }
};

// テスト
TEST(HogeTest, A)
{
   Hoge hoge;
   ASSERT_EQ(1, hoge.GetA());
}
TEST(HogeTest, B)
{
   Hoge hoge;
   ASSERT_EQ(2, hoge.GetB());
}
テスト毎に FRIEND_TEST しないといけないので、ちょっと面倒ではありますが private メンバーにアクセスできるようになります。
例では書きませんでしたが、テストフィクスチャを使った場合(TEST_F)でも使用できます。
(※ 型付けテストでは使用できません。)

例2: friend TestFixture
2番目の方法は、テストフィクスチャクラスを friend する方法です。
// テスト対象クラス
class Hoge
{
   friend class HogeTest; // HogeTest フィクスチャーに対して friend
private:
   int GetA() { return 1; }
   int GetB() { return 2; }
};

// テスト
class HogeTest : public ::iutest::Test 
{
protected:
    Hoge hoge;
    // アクセサを書く
    int GetA() { return hoge.GetA(); }
    int GetB() { return hoge.GetB(); }
};
TEST_F(HogeTest, A)
{
    ASSERT_EQ(1, GetA());
}
TEST_F(HogeTest, B)
{
    ASSERT_EQ(2, GetB());
}
テストフィクスチャが必要になるので、TEST(Hoge, A) のようなテストには使えませんが、
FRIEND_TEST の時とは違ってテストケースすべてに対してアクセスを許可することができるので、使い勝手は良いです。

ただ、テストフィクスチャクラスでアクセサを書く必要があります。これは Google Test がテスト毎にテストフィクスチャを継承したクラスを定義しているからです。
とはいえ、一度テストフィクスチャを定義してしまえば使い回しが可能ですし、テストコード側の変更だけでテストを追加できるのが、この方法のメリットでしょう。

iutest でも、もちろん上記方法が使えます。

Google Test の問題
Google Test が用意している FRIEND_TEST マクロは、型付けテストには使えません
template<typename T> をつければいいだけなので、以下のようにマクロを追加すれば OK です。

#define FRIEND_TYPED_TEST template<typename T>FRIEND_TEST

iutest は、FRIEND_TYPED_TEST に対応済みです。

プロダクトコードに変更を加えない方法
さて、friend を使った方法は、プロダクトコード側に friend 宣言をする必要がありました。
その程度の記述は大したことないかもしれませんが、やはりプロダクトコードを1行も変えずにアクセスできたら便利ですよね。
(テスト名を書かないといけないのも個人的には面倒だなと思います。)

iutest では非侵入的に private メンバーにアクセスする方法を提供しています。


// テスト対象クラス
class Hoge
{
private:
   int GetA() { return 1; }
   int GetB() { return 2; }
};

typedef int (Hoge::* HogePrivateFunc)(); // iutest v1.12.0 からは typedef 不要になります
IUTEST_MAKE_PEEP(HogePrivateFunc, Hoge, GetA);
IUTEST_MAKE_PEEP(HogePrivateFunc, Hoge, GetB);

// テスト
IUTEST(HogeTest, A)
{
   Hoge hoge;
   IUTEST_ASSERT_EQ(1, IUTEST_PEEP_GET(hoge, Hoge, GetA)());
}
IUTEST(HogeTest, B)
{
   Hoge hoge;
   IUTEST_ASSERT_EQ(1, IUTEST_PEEP_GET(hoge, Hoge, GetB)());
}

なんか記述量増えてるし、マクロやばそうと思われる方もいると思いますが、
この方法であれば、完全にプロダクトコードを汚すことなく private なメンバーのテストが書けます。

private メンバーにアクセスする方法は、こちらで紹介されている方法を使いました。
privateメンバに外部から非侵入的にアクセスする - redboltzの日記 (archive)
 (追記: Wayback Machine にアーカイブが残っていた)

いや~凄いですね。
さて、来週に iutest v1.12.0 のリリースを予定してます。
正規表現アサーションの他、いろいろ更新されてますのでよろしくお願いします。

2014年10月14日火曜日

[gtest] OR 条件のアサーション

Google Group で OR 条件のアサーションはどう書くの?というトピックがあったので、紹介します。
Google C++ Testing Framework › Logical OR of EXPECT_*

AND は簡単だけど、OR は・・・
AND 条件は簡単に書けます。
TEST(Hoge, AndTest)
{
    ASSERT_NE(0, a);
    ASSERT_NE(4, a);
}

これは、a != 0 && a != 4 を意味します。

では、a == 0 || a == 4 のような条件を考えます。
TEST(Hoge, OrTest)
{
    EXPECT_EQ(0, a);
    EXPECT_EQ(4, a);
}
当然これはダメです。これでは AND になってしまします。
例えば、下記のように書けると良いのですが、残念ながらできません。
TEST(Hoge, OrTest)
{
    EXPECT_EQ(0, a) || EXPECT_EQ(4, a);
}

最も簡単な方法
Google Group でも紹介されていた最も簡単な方法です。

TEST(Hoge, OrTest)
{
    ASSERT_TRUE( a == 0 || a == 4 );
}
[----------] 1 test from Hoge
[ RUN      ] Hoge.OrTest
main.cpp(99): error: Value of: a == 0 || a == 4
  Actual: false
Expected: true
[  FAILED  ] Hoge.OrTest (4 ms)
[----------] 1 test from Hoge (6 ms total)

ただ、これでは失敗したときに a の値がなんだったのかわかりません。
なので、以下のようにして a の値を出力するようにします。

TEST(Hoge, OrTest)
{
    ASSERT_TRUE( a == 0 || a == 4 ) << "a = " << a;
}
出力はこのようになります。
[----------] 1 test from Hoge
[ RUN      ] Hoge.OrTest
main.cpp(99): error: Value of: a == 0 || a == 4
  Actual: false
Expected: true
a = 1
[  FAILED  ] Hoge.OrTest (4 ms)
[----------] 1 test from Hoge (6 ms total)

値の出力には SCOPED_TRACE を使う方法もあります。

TEST(Hoge, OrTest)
{
    SCOPED_TRACE( ::testing::Message() << a );
    ASSERT_TRUE( a == 0 || a == 4 );
}

SCOPED_TRACE は記述したスコープの間はメッセージが追記されます。なので、複数のアサーションを記述している場合は一箇所の修正で済むので楽です。
[----------] 1 test from Hoge
[ RUN      ] Hoge.OrTest
main.cpp(99): error: Value of: a == 0 || a == 4
  Actual: false
Expected: true
Google Test trace:
main.cpp(99): 1
[  FAILED  ] Hoge.OrTest (4 ms)
[----------] 1 test from Hoge (6 ms total)

Matcher を使う方法や他の方法もあると思いますが、Google Test のみの場合ではこの方法が一番だと思います。

2013年9月22日日曜日

Google Test 1.7.0 がリリースされました

Google Test 1.7.0 がリリースされました。ダウンロードはこちらから。
1.6.0 から約2年ぶりのリリースになります。

1.7.0 RC の時点での変更点は「Google Test 1.7.0 RC がリリースされました」で書いていますので、
そちらを参照してください。
1.7.0 RC から 1.7.0 で、1つだけ変更があったので書き出しておきます。

新機能
New feature: Google Test now implements a protocol to allow
a test runner to detect that a test program has exited
prematurely and report it as a failure (before it would be
falsely reported as a success if the exit code is 0).
テスト開始時に、環境変数 [TEST_PREMATURE_EXIT_FILE] に指定されたファイルパスにファイルを作成します。
テスト終了時に、このファイルは削除されます。

これにより、テストが最後まで実行されたのか、途中で中断したのかを判断することができます。

まとめ
さて、gtest 1.7.0 がリリースされたことですし、iutest も 1.7.0 をリリースしていこうかと思います。
では。

2013年9月10日火曜日

Google Test 1.7.0 RC がリリースされました

(公開されてから少し経ちましたが…)
Google Test の version 1.7.0 release candidate がリリースされました。
約2年ぶりの更新になります。

変更履歴はこちら。
Changes for 1.7.0:

* New feature: death tests are supported on OpenBSD and in iOS
  simulator now.
* New feature: Test::RecordProperty() can now be used outside of the
  lifespan of a test method, in which case it will be attributed to
  the current test case or the test program in the XML report.
* New feature (potentially breaking): --gtest_list_tests now prints
  the type parameters and value parameters for each test.
* Improvement: char pointers and char arrays are now escaped properly
  in failure messages.
* Improvement: failure summary in XML reports now includes file and
  line information.
* Improvement: the <testsuites> XML element now has a timestamp attribute.
* Improvement: When --gtest_filter is specified, XML report now doesn't
  contain information about tests that are filtered out.
* Fixed the bug where long --gtest_filter flag values are truncated in
  death tests.
* Potentially breaking change: RUN_ALL_TESTS() is now implemented as a
  function instead of a macro in order to work better with Clang.
* Compatibility fixes with C++ 11 and various platforms.
* Bug/warning fixes.
今回の変更点をざっと見ていきたいと思います。

New feature: death tests are supported on OpenBSD and in iOS simulator now.
DEATHテストが OpenBSD と iOS シミュレータでも使えるようになった。

New feature: Test::RecordProperty() can now be used outside of the lifespan of a test method, in which case it will be attributed to the current test case or the test program in the XML report
RecordProperty がテスト期間以外でも呼べるようになります。
例えば、SetUpTestCase で呼ぶと XML の <testsuite> に保存されます。

New feature (potentially breaking): --gtest_list_tests now prints the type parameters and value parameters for each test.
--gtest_list_tests で表示されるテストリストにパラメータの型や値を表示するようになりました。

v1.6.0 の場合

v1.7.0 の場合

Improvement: char pointers and char arrays are now escaped properly in failure messages.
char* や char[] のメッセージ出力が改善されました。

Improvement: failure summary in XML reports now includes file and line information.
XML の失敗メッセージにファイルと行数を含むようになりました。

Improvement: the <testsuites> XML element now has a timestamp attribute.
XML の testsuites ノードに timestamp 属性を書き込むようになりました。

Improvement: When --gtest_filter is specified, XML report now doesn't contain information about tests that are filtered out.
--gtest_filter で除外されたテストは XML に出力しないようになりました。

Fixed the bug where long --gtest_filter flag values are truncated in death tests.
--gtest_filter の文字列が長い場合に、DEATHテストに送られる文字列が切り捨てられる不具合が修正されました。

Potentially breaking change: RUN_ALL_TESTS() is now implemented as a function instead of a macro in order to work better with Clang.
RUN_ALL_TESTS がマクロから関数になりました。

Compatibility fixes with C++ 11 and various platforms.
C++11 やその他のプラットフォームへの対応が入りました。

全くもって説明なっていませんね・・・すみません。。。

おおまかにいうと
「様々なプラットフォームへの対応と XML 出力が便利になりました」って感じでしょうか。
バグ修正や細かいところで使いやすくなっているので、
とりあえず 1.7.0 RC 使って正式版に備えておくといいかもしれません。



2012年6月3日日曜日

Google Test で成功時のメッセージを非表示にする方法

TDDBC 大阪。参加すればよかったなぁと、ツイッターを眺めていたら興味深いつぶやきがありました。
「GoogleTestはOKなテストも全部表示されてしまう」設定で切り替える方法はあるだろうか?

私の知る限りでは、できないです。
「設定」というのがマクロ定義やコマンドラインオプションであるなら、そのような機能はなかったと思います。

ただし、
「イベントリスナー」を使えばできちゃいます。

イベントリスナーって?
Google Test にはイベントリスナーというものがあります。
これについては、マニュアルにも記載されてますので参考にしてください。

イベントリスナーは、テストケースやテストが開始・終了したとき、テストが失敗したときなど、
Google Test で発生したイベントを処理することができます。
デフォルトのコンソール出力や XML 出力もこのイベントリスナーで実装されています。

というわけで、このデフォルトイベントリスナーを置き換えることで目的が達成できます。

実装
class QuietResultPrinter : public ::testing::TestEventListener
{
public:
    QuietResultPrinter(::testing::TestEventListener* default_printer)
        : m_default_printer(default_printer)
    {}

    virtual void OnTestEnd(const ::testing::TestInfo& test_info)
    {
        if( test_info.result()->Failed() )
        {
            // 失敗したときのみ出力する
            m_default_printer->OnTestEnd(test_info);
        }
    }
    // その他のイベントは、デフォルトをそのまま呼ぶ
    // ...
private:
    ::testing::TestEventListener* m_default_printer;
};

まずは、::testing::TestEventLisener を継承してカスタムイベントリスナーを作成します。
本当は、デフォルトコンソール出力イベントリスナーを継承したいのですが、
こちらはヘッダーに公開されていないので、ポインターを受け取ってデフォルトを呼ぶようにしています。
デフォルトのイベントリスナーは、UnitTest::GetInstance()->listeners().default_result_printer() で取得できます。

OnTestEnd 関数は、テストが終了したときに呼ばれるイベントです。
ここでテストが失敗したかどうか判定し、失敗した場合のみデフォルトの出力をするようにしています。

省略されていますが、他のイベントに対してはデフォルトを呼ぶようにします。

次にこのイベントリスナーを Google Test に登録します。

::testing::TestEventListeners& listeners =
    ::testing::UnitTest::GetInstance()->listeners();
::testing::TestEventListener* def =
    listeners.Release(listeners.default_result_printer());
listeners.Append(new QuietResultPrinter(def));
return RUN_ALL_TESTS(); // run all

listners.Release でデフォルトの登録を解除し、Append でカスタムイベントリスナーを登録します。
実行結果がこちら。
※画像が小さいですが、クリックすると大きく表示されます。

左がデフォルトイベントリスナーで、右がカスタマイズしたイベントリスナーの出力です。
少しコンパクトになりましたね。

もう少し変更
テスト開始時の出力も非表示にしてしまおうと思います。

virtual void OnTestStart(const ::testing::TestInfo& test_info) {}

    virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result)
    {
        if( ::testing::UnitTest::GetInstance()->current_test_info() != NULL )
        {
            const ::testing::TestInfo& test_info = *::testing::UnitTest::GetInstance()->current_test_info();
            if( test_info.result()->Failed()
                && test_info.result()->total_part_count() == 1 )
            {
                m_default_printer->OnTestStart(test_info);
            }
        }
        m_default_printer->OnTestPartResult(test_part_result);
    }

先ほどの、QuietResultPrinter を変更します。
OnTestStart ではテストが失敗したかまだわからないので、なにも出力しません。
OnTestPartResult 関数でテスト結果が来た時に、最初の失敗の時だけデフォルトの開始メッセージを出力するようにしてみました。

これを実行するとこのようになります。


ただし、これには問題があります。
テスト開始から最初の失敗までに出力があった場合に、それが開始メッセージよりも前に表示されてしまいます。
その点に注意してください。

OnTestCaseStart/End も同様に変更すれば、さらに簡潔になると思います。

最後に
今回紹介したコードを github で公開しました。ご自由に使用してください。
また、これはほんの一例に過ぎませんので、気に食わないところがあったらカスタマイズしてみてください。

2012年3月11日日曜日

Visual Studio 11 Beta で Google Test を使う

Visual Studio 11 Beta が公開されましたね。
そこで Visual Studio 11 Beta でも Google Test を使ってみようと思います。

今回の元ネタは、Google グループの Google C++ Testing Framework のこちらの投稿です。
備忘録としてブログにまとめておこうと思います。

プロジェクトの準備
まずは、ダウンロードページより取得した Google Test を解凍します。
解凍したフォルダの中に msvc フォルダがあり、そこの gtest.sln を Visual Studio 11 で開きます。
(Visual Studio 11 Beta のインストールは済ませておいてください。)

プロジェクトのアップグレード(変換)ダイアログが出るので、
アップグレードします。
プロジェクトが正常に読み込まれたら、準備完了です。

ビルド
F7 キーを押して終わり。とはいきません・・・
error C2977: 'std::tuple' : テンプレート 引数の数が多すぎます
error C3203: 'tuple' : 非特殊クラス テンプレート は、テンプレート 引数として テンプレート パラメーター 'T' に使用できません。実際の型を指定してください
とか
error C2955: 'testing::internal::ParamIteratorInterface' : クラス テンプレート を使用するには テンプレート 引数リストが必要です
というエラーがでます。

一番簡単な対処方法は GTEST_HAS_TR1_TUPLE=0 としてしまうことです。
ただし、こうすると値をパラメータ化したテストで Combine が使えなくなってしまいます。

gtest-port.h
Combine を使えるようにするために、gtest/internal/gtest-port.h を編集します。
まずは、MSVC11 の <tuple> は名前空間が異なっているため、対応します。
gtest-port.h の 506 行目に以下を付け足します。
#  if defined(_MSC_VER) && _MSC_VER >= 1700
namespace std { namespace tr1 { using std::tuple; } }
#  endif

次に、Variadic Templates の上限を増やします。
えっ、と思う方もいるかもしれませんが、そういうもんだと思ってください。

gtest-port.h の 38 行目あたりに以下を付け足します。
#if defined(_MSC_VER) && _MSC_VER == 1700
#  ifndef _VARIADIC_MAX
#    define _VARIADIC_MAX 10
#  elif _VARIADIC_MAX < 9
#    define GTEST_HAS_TR1_TUPLE 0
#  endif
#endif
インクルード順の関係で、_VARIADIC_MAX がすでに定義されていたら、残念ながら使えないようにしています。

もし、確実に使いたい場合は
プロジェクトのプロパティからプリプロセッサの定義に _VARIADIC_MAX=10 を追加してください。

以上の対処でビルドエラーは解消されたはずです。
あとは今まで通りに使えると思います。

今回はここまでです。それでは。

2012年2月25日土曜日

Google Test を使ってみる - その4(オプション)

Google Test では様々なオプションを環境変数または起動時引数に指定できます。
今回はそちらの紹介をしたいと思います。

オプション内容
--gtest_list_testsテストの一覧を出力します。
--gtest_filter=<条件>実行するテストを限定します。
--gtest_also_run_disabled_testsDISABLE なテストも実行します。
--gtest_repeat=<COUNT>テストの繰り返し回数を指定します。
負数にすると無限に繰り返します。
--gtest_shuffleテストの実行順序をシャッフルします。
--gtest_random_seed=<NUMBER>シャッフルする際の乱数シードを指定します。
0 を指定すると時間からシードを決定します。
--gtest_color=<yes|no|auto>出力を色付で表示するか選択します。
--gtest_print_time=0テストの実行時間を出力しないようにします。
--gtest_output=xml[:DIRECTORY|:FILE]指定のディレクトリまたはファイルにテスト結果の xml を出力します。デフォルトのファイル名は test_details.xml です。
--gtest_break_on_failureテストが失敗したときに break します。
--gtest_throw_on_failureテストが失敗したときに例外をスローします。
--gtest_catch_exceptionsテストが例外をキャッチするようにします。
--helpヘルプを表示します。

環境変数の場合は、最初の -- をのぞいた変数名を使います。(GTEST_FILTER, GTEST_COLOR, etc...)

よく使うオプション
それぞれのオプションの機能については、マニュアルを見ればわかるのでそちらを見ていただいて、ここでは個人的に使用頻度の高い機能と思うオプションを紹介します。

--gtest_filter=<条件>
実行するテストを指定します。
条件には、 * が使用できます。 * は任意の文字列にマッチします。
--gtest_filter=* ですべてのテスト名にマッチします。
--gtest_filter=FooTest.* でテストケース FooTest 内のすべてを実行します。

:(コロン) で OR 条件を記述できます。
--gtest_filter=*Constructor*:*Copy* で、Constructor または Copy を含むテストを実行します。

-(マイナス) で除外も可能です。
--gtest_filter=FooTest.*-FooTest.Bar で、FooTest.Bar テストを除く、テストケース FooTest 内のテストを実行します。

--gtest_filter の詳しい使用方法は、上級ガイドのテストを選択するを参照してください。

--gtest_shuffle
このオプションを指定するとテストケース・テストそれぞれランダムな順番で実行されます。
テストが他のテストの結果に依存していないか確認するのに最適です。
乱数のシードは、--gtest_random_seed=<NUMBER> で指定可能です。

--gtest_repeat=<COUNT>
テストを繰り返し実行します。
繰り返し実行させることで、再現性の低いテスト失敗が出るまで繰り返すことができます。
テストで乱数を使っている場合などに有効でしょう。
--gtest_repeat=-1 とすると無限に繰り返すので、
退社するまえにテストを走らせておいて一晩テストをさせることもできます。
また、--gtest_shuffle を指定すると毎回異なるシードでテストを行うので活用してみてください。

※ Google Test 関係の記事一覧はこちら

2012年2月18日土曜日

Google Test で Boost.Test の WARN flavor を実現する

Boost.Test には flavors というものがあり、アサーションのレベルがそれぞれ違います。
もちろん、Google Test にも同等のものが存在します。

エラー:ならない
テストの実行:継続
エラー:なる
テストの実行:継続
エラー:なる
テストの実行:中断
Google Test---EXPECTASSERT
Boost.TestWARNCHECKREQUIRE

ご覧のように Google Test には、"エラーにならないアサーション" がありません。
そこで、必要かどうかはさておき Google Test にもエラーにならないアサーションを提供したいと思います。

ベースとなるマクロ
まずは、アサーションで失敗が発生したときに失敗したことにならないようにします。
EXPECT をベースに作っていきましょう。
// GTEST_EXPECT_FAILURE_ をベースに作成
#define GTEST_INFORM_FAILURE_(message) \
 GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess)
                                // kSuccess とし成功を指定する

アサーションは最終的に GTEST_<level>_FAILURE_ を呼ぶので、エラーにならないアサーション用に GTEST_INFORM_FAILURE_ を定義します。(エラーにならないアサーションを INFORM としています。)

述語アサーション
次に、ほとんどのアサーションが述語アサーションを利用しているので、そちらを用意します。
#define INFORM_PRED_FORMAT1(pred_format, v1) \
  GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_INFORM_FAILURE_)
#define INFORM_PRED1(pred, v1) \
  GTEST_PRED1_(pred, v1, GTEST_INFORM_FAILURE_)

#define INFORM_PRED_FORMAT2(pred_format, v1, v2) \
  GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_INFORM_FAILURE_)
#define INFORM_PRED2(pred, v1, v2) \
  GTEST_PRED2_(pred, v1, v2, GTEST_INFORM_FAILURE_)
GTEST_INFORM_FAILURE_ を使うように書き換えただけです。
(述語アサーションは "5" まであるので、すべて用意します。)

各アサーションの定義
最後に、INFORM_TRUE とか INFORM_EQ マクロを定義します。
先に定義した述語アサーションを使うようにするだけです。
#define INFORM_TRUE(condition) GTEST_TEST_BOOLEAN_(condition, #condition \
    , false, true, GTEST_INFORM_FAILURE_)

#define INFORM_EQ(expected, actual) \
  INFORM_PRED_FORMAT2(::testing::internal:: \
   EqHelper<GTEST_IS_NULL_LITERAL_(expected)>::Compare, \
   expected, actual)
#define INFORM_NE(expected, actual) \
  INFORM_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual)
#define INFORM_LE(val1, val2) \
  INFORM_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2)
#define INFORM_LT(val1, val2) \
  INFORM_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2)
// 他にもありますが、ここでは省略します。

使い方
他のアサーションと同様に使います。
TEST(InfromTest, Sample)
{
    INFORM_TRUE(rand() != 0);
    INFORM_EQ(5, rand());
}

出力イベントリスナー
さて、ここまで順調にきましたが 問題 が発生します。
Google Test デフォルトの出力イベントリスナーだと何も出力されません。
TestPartResult::kSuccess のときは、何もせず return するようにコーディングされているからです。

そこで、ユーザー定義のイベントリスナーを追加してやります。

class InformPrinter : public ::testing::EmptyTestEventListener
{
private:
 virtual void OnTestPartResult(const TestPartResult& test_part_result)
 {
  // 成功のときに出力
  if( test_part_result.type() == TestPartResult::kSuccess )
  {
   // 出力してもらえるようにダミーのタイプを設定する
   TestPartResult tmp(TestPartResult::kNonFatalFailure
    , test_part_result.file_name()
    , test_part_result.line_number()
    , test_part_result.message());
   ::testing::UnitTest::GetInstance()->
    listeners().default_result_printer()->OnTestPartResult(tmp);
  }
 }
};

int main(int argc, char **argv) {
 testing::InitGoogleTest(&argc, argv);
 // InformPrinter をイベントリスナーに追加
 ::testing::UnitTest::GetInstance()->
  listeners().Append( new InformPrinter() );
 return RUN_ALL_TESTS();
}

iutest でできるようにしました
ここまで紹介してきた INFORM マクロを iutest で使えるようにしました。(v0.18.1.0 以降)
iutest は自作の C++ テストフレームワーク です。
Boost.Test には、include するだけ使える方法もあるようですが、
iutest を使えば、include するだけで Google Test の記法を使うことができます。
詳しくは、 SourceForge.Jp の プロジェクトページ を見てください。

2012年1月28日土曜日

Google Test を使ってみる - その3(テストケース)

Google Test シリーズ第3弾。

今回は、様々なテストの書き方を見てみたいと思います。

※ Google Test 関係の記事一覧はこちら
最も簡単なテスト
最も簡単なテストの作り方は以下になります。
TEST(CaseName, TestName)
{
    // ここにテストコード
}
テストフィクスチャ
テストフィクスチャを使うことで、テストリソースの共有が可能です。
class TestFix : public ::testing::Test
{
protected:
    std::vector data;

    virtual void SetUp()
    {
        printf("TestFix::SetUp\n");
        data.push_back(0);
        data.push_back(1);
        data.push_back(2);
    }
    virtual void TearDown()
    {
        printf("TestFix::TearDown\n");
    }
}:
TEST_F(TestFix, Test1)
{
    data.push_back(3);  // TestFix のメンバーにアクセス可能
    TEST_ASSERT_EQ(3, data.size());
}
TEST_F(TestFix, Test2)
{
    TEST_ASSERT_EQ(3, data.size());
}
TEST_F マクロの第一引数に、::testing::Test クラスを継承した
テストフィクスチャのクラス名を指定します。
このテストフィクスチャは、テスト毎に作られテスト開始前に SetUp 関数をコールします。
(テスト終了後には、TearDown をコール)

実行結果
[----------] 2 tests from TestFix
[ RUN      ] TestFix.Test1
TestFix::SetUp
main.cpp(21): error: Value of: data.size()
  Actual: 4
Expected: 3
TestFix::TearDown
[  FAILED  ] TestFix.Test1 (0 ms)
[ RUN      ] TestFix.Test2
TestFix::SetUp
TestFix::TearDown
[       OK ] TestFix.Test2 (0 ms)
[----------] 2 tests from TestFix (1 ms total)

さらに、テストケース名を分けたい場合は以下のようにも書けます。
class TestFix : public ::testing::Test

// 略 

TEST_F(TestFix, Test1)
{
    TEST_ASSERT_EQ(3, data.size());
}

typedef TestFix TestFix2; // 別名を定義する
TEST_F(TestFix2, Test1)
{
    TEST_ASSERT_EQ(3, data.size());
}
値をパラメータ化したテスト
値をパラメータ化することで、I/Oテストなどが書きやすくなります。
値のパラメータ化テストを使用する場合、まずは、::testing::TestWithParam クラスを継承したクラスを用意します。(このクラスは、::testing::Test も継承しているのでテストフィクスチャと同じように使用できます。)

次に、TEST_P マクロを使ってテストを作成します。
// template 引数にはパラメータの型を渡す
class TestP : public ::testing::TestWithParam {};

TEST_P(TestP, TestA)
{
   int param = GetParam(); // パラメータは GetParam 関数で取得できる。
   printf("%d\n", param);
}
これだけでは、まだテストは実行しません。
最後に、パラメータを与えてテストをインスタンス化します。
INSTANTIATE_TEST_CASE_P(InstantiationName, TestP
    , ::testing::Values(1, 3, 0)); // ここがパラメータ
パラメータの作成には、::testing::Values 以外に
Range, ValuesIn, Bool, Combine があります。

実行結果
[----------] 6 tests from InstantiationName/TestP
[ RUN      ] InstantiationName/TestP.TestA/0
1
[       OK ] InstantiationName/TestP.TestA/0 (0 ms)
[ RUN      ] InstantiationName/TestP.TestA/1
3
[       OK ] InstantiationName/TestP.TestA/1 (0 ms)
[ RUN      ] InstantiationName/TestP.TestA/2
0
[       OK ] InstantiationName/TestP.TestA/2 (0 ms)

型付けテスト
template クラスや関数のテストをしたいときに、便利なのが型付けテストです。
// 型を受け取る(T は型リストから渡される)
template<class T>
class TypedTest : public testing::Test {
};
// 型リスト
typedef ::testing::Types<int, float, double> TestTypes;
// 型リストとテストを関連づける
TYPED_TEST_CASE(TypedTest , TestTypes);

// テストを記述する
TYPED_TEST(TypedTest, Test1)
{
    TypeParam p = 0; // TypeParam で型を取れる
    // ここにテストを書く
}
型付けテストを使用するのに必要な準備は3つ。
  • フィクスチャクラステンプレート(TypedTest)
  • 型リスト(TestTypes)
  • 型リストとテストクラスの関連付け(TYPED_TEST_CASE)
この3つを用意したら、あとは TYPED_TEST マクロを使ってテストを書くだけです。

実行結果
[----------] 1 tests from TypedTest/0, where TypeParam = int
[ RUN      ] TypedTest/0.Test1
[       OK ] TypedTest/0.Test1 (0 ms)
[----------] 1 tests from TypedTest/0 (0 ms total)

[----------] 1 tests from TypedTest/1, where TypeParam = float
[ RUN      ] TypedTest/1.Test1
[       OK ] TypedTest/1.Test1 (0 ms)
[----------] 1 tests from TypedTest/1 (0 ms total)

[----------] 1 tests from TypedTest/2, where TypeParam = double
[ RUN      ] TypedTest/2.Test1
[       OK ] TypedTest/2.Test1 (0 ms)
[----------] 1 tests from TypedTest/2 (0 ms total)
型をパラメータ化したテスト
型付けテストと違い、テスト自体は型リストのことを知らなくて良いのが、大きな違いです。
あらかじめテストを書いておき、あとからインスタンス化できます。
さらに、インスタンス化は異なる型リストで複数つくることが可能です。
//
// test.h

template<class T>
class TypedTestP : public testing::Test {
};
// テストケースを設定
TYPED_TEST_CASE_P(TypedTestP);

// テストを記述する
TYPED_TEST_P(TypedTestP , Test1)
{
    TypeParam p = 0; // TypeParam で型を取れる
    // ここにテストを書く
}
TYPED_TEST_P(TypedTestP , Test2)
{
}

// テストケースにテストを登録する
REGISTER_TYPED_TEST_CASE_P(TypedTestP  // テストケース名
    , Test1, Test2, Test3);  // テスト名の列挙
これで、テストをする部分が完成です。
値をパラメータ化したテストと同様に、まだテストが実行されない状態です。)

あとは、好きなところで好きな型リストを与えてインスタンス化します。
// test.cpp
#include "test.h"

typedef ::testing::Types<int, char> TestTypes;
// インスタンス化
INSTANTIATE_TYPED_TEST_CASE_P(InstantiateName // このインスタンスの名前
    , TypedTestP // テストケース名
    , TestTypes); // 型リスト

typedef ::testing::Types<float, double> TestTypes2;
// 別の型リストでインスタンス化
INSTANTIATE_TYPED_TEST_CASE_P(Floating // このインスタンスの名前(同名はダメ)
    , TypedTestP // テストケース名
    , TestTypes2); // 型リスト


実行結果
[----------] 2 tests from InstantiateName/TypedTestP/0, where TypeParam = int
[ RUN      ] InstantiateName/TypedTestP/0.Test1
[       OK ] InstantiateName/TypedTestP/0.Test1 (0 ms)
[ RUN      ] InstantiateName/TypedTestP/0.Test2
[       OK ] InstantiateName/TypedTestP/0.Test2 (0 ms)
[----------] 2 tests from InstantiateName/TypedTestP/0 (0 ms total)

[----------] 2 tests from InstantiateName/TypedTestP/1, where TypeParam = char
[ RUN      ] InstantiateName/TypedTestP/1.Test1
[       OK ] InstantiateName/TypedTestP/1.Test1 (0 ms)
[ RUN      ] InstantiateName/TypedTestP/1.Test2
[       OK ] InstantiateName/TypedTestP/1.Test2 (0 ms)
[----------] 2 tests from InstantiateName/TypedTestP/1 (0 ms total)

[----------] 2 tests from Floating/TypedTestP/0, where TypeParam = float
[ RUN      ] Floating/TypedTestP/0.Test1
[       OK ] Floating/TypedTestP/0.Test1 (0 ms)
[ RUN      ] Floating/TypedTestP/0.Test2
[       OK ] Floating/TypedTestP/0.Test2 (0 ms)
[----------] 2 tests from Floating/TypedTestP/0 (0 ms total)

[----------] 2 tests from Floating/TypedTestP/1, where TypeParam = double
[ RUN      ] Floating/TypedTestP/1.Test1
[       OK ] Floating/TypedTestP/1.Test1 (0 ms)
[ RUN      ] Floating/TypedTestP/1.Test2
[       OK ] Floating/TypedTestP/1.Test2 (0 ms)
[----------] 2 tests from Floating/TypedTestP/1 (0 ms total)
最後に
ここで紹介した方法は gtest のほんの一部です。
より詳しいことは、マニュアルにわかりやすく書かれているので、そちらをご確認下さい。
日本語訳されたマニュアルはこちらです。

2011年11月26日土曜日

Google Test を使ってみる - その1

まず、はじめに断っておきますが、
私自身テストを書くようになったのは最近の話で、テストの書き方・やり方に関しては素人です。

ただ、あとからヒーヒー言ってバグを直すのは嫌なので、
事前にテストを書いて防げたらという思いでやり始めました。
その甲斐あってか、今の会社にいるわけですが、
そこで google test(以下、gtest) なるものを使い始めたので、簡単に紹介したいと思います。

※ Google Test 関係の記事一覧はこちら
ダウンロードからリンクまで
gtest をダウンロードして使える状態にします。

  1. ダウンロード
    以下のリンクから、gtest-1.6.0.zip をダウンロードします。
    http://code.google.com/p/googletest/
  2. 展開
    お好きなディレクトリに解凍します。解凍したパスを GTEST_ROOT とします。
  3. ビルド
    gtest を使うためには、ライブラリをビルドする必要があります。
    いくつかビルドファイルが用意されていますが、ここでは CMake と MSBuild を使います。

    以下の内容をバッチファイルとして GTEST_ROOT に保存し、実行するだけでビルドできます。(cmake 必須)
    @echo off
    
    cd msvc
    if not exist vc2008 mkdir vc2008
    cd vc2008
    
    rem cmake
    cmake -G "Visual Studio 9 2008" ../../
    rem MD の場合は、こっち
    rem cmake -G "Visual Studio 9 2008" -Dgtest_force_shared_crt=ON ../../
    if errorlevel 1 goto error
    
    rem msbuild へのパスを通す
    if exist "%VS90COMNTOOLS%" (
     call "%VS90COMNTOOLS%\vsvars32.bat"
    ) else SET PATH=%WINDIR%\Microsoft.NET\Framework\v3.5;%PATH%
    
    rem Debug ビルド
    msbuild /p:Configuration=Debug /p:Platform=Win32 /t:Build gtest.sln
    if errorlevel 1 goto error
    rem Release ビルド
    msbuild /p:Configuration=Release /p:Platform=Win32 /t:Build gtest.sln
    if errorlevel 1 goto error
    
    goto end
    
    :error
    pause
    
    :end
    
    
  4. リンク
    GTEST_ROOT/include をインクルードパスに追加。
    3. で作成したライブラリをリンクさせる。
    (Visual Studio の場合、 #pragma comment(lib, "gtest.lib") など。)

テストコードを書く
次に、テストコードを書きます。
必要なのは gtest/gtest.h のインクルードと、作成した gtest.lib のリンクだけです。

// gtest を使うために必要なヘッダー
#include <gtest/gtest.h>

// gtest.lib をリンク
#pragma comment (lib, "gtest.lib")

int main(int argc, char **argv)
{
    // gtest の初期化
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();    // gtest の実行
}

int Factorial(int n)
{
    if( n <= 1 ) return 1;
    return n * Factorial(n-1);
}
// test 内容
TEST(Foo, Factorial)
{
    ASSERT_EQ(1, Factorial(1));
    ASSERT_EQ(2, Factorial(2));
    ASSERT_EQ(6, Factorial(3));
    ASSERT_EQ(40320, Factorial(8));
}

main 関数で、gtest の初期化とテストの実行を呼び出しています。

テスト自体の作成はマクロを使用します。
TEST(テストケース名, テスト名)
でテストを登録、以降にテスト内容を書きます。

あとは、必要なテストを追加していくだけ。
非常に簡単です。

ドキュメント
実は、日本語訳されたドキュメントがあります。http://opencv.jp/googletestdocs/
ブログでは事細かく説明するつもりはないので、こちらを読んでいただければと思います。