2012年4月26日木曜日

Visual Studio 11 : C++ Unit Test Framework で Google Test を使ってみた(実験)

Visual Studio 11 には C++ のテストフレームワークがついてくるということで、早速試してみました。
参考にしたのは、こちらの記事
Visual Studio 11 : C++ Unit Test Framework ── C++単体テストの決定版(かもしれない)
(Visual Studio 11 C++ Unit Test Framework : 以下 VS11UnitTest。)

試してみた感触としては良い感じ。
ただ、テスト書いてからテストを実行するまでに時間がかかりすぎな気もしました。

Google Test を使いたい
本題。
普段は、Google Test(以下、gtest) を使っているので gtest のコードをそのまま VS11UnitTest でも使いたくなります。

まずは、基本的な使い方からできるようにしてみたいと思います。
今回の目標は以下のコードが VS11UnitTest でも使えることです。

TEST(Test, Basic1)
{
    ASSERT_EQ(2, 3);
}
TEST(Test, Basic2)
{
    ASSERT_EQ(2, 3);
}

VS11UnitTest の基本構成
Visual Studio 11 の新規プロジェクトから、「ネイティブ単体テスト プロジェクト」を作るとできるデフォルトコードがこちら。
#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace unittest_sample
{       
    TEST_CLASS(UnitTest1)
    {
    public:
        
        TEST_METHOD(TestMethod1)
        {
            // TODO: Your test code here
        }

    };
}

TEST_CLASS に TEST_METHOD がぶら下がった構成になってます。
アサーションはこんな感じ。
Assert::AreEqual(0, 1);

TEST マクロ
まずは、TEST(Test, Basic) が認識されるようにします。
TEST(aaa, bbb) と書いたら、TEST_CLASS と TEST_METHOD を作ってやればいいので、
#undef TEST
#define TEST(test_case_name, test_name) \
    VCTEST(test_case_name##test_name##_class, test_case_name##_##test_name)
#define VCTEST(className, methodName)   \
    TEST_CLASS(className) { public: TEST_METHOD(methodName); }; \
    void className::methodName() 



こんな感じに書き換えます。
クラス名を連結させているのは、名前が衝突しないようにするためで、
テスト名を連結させているのは、テスト実行したときに分かりやすいようにするためです。

これで、テストの登録ができました。






各種アサーション
全部のアサーションを VS11UnitTest のアサーションに置き換えてもいいのですが、
とてもメンドクサイので、イベントリスナーを使います。

class VCCppUnitTestPartResultReporter : public testing::EmptyTestEventListener
{
public:
    virtual void OnTestPartResult(const testing::TestPartResult& result)
    {
        // VC にも送る
        if( result.failed() )
        {
            size_t size=0;
            wchar_t buf1[4096];
            wchar_t buf2[260];
            ::mbstowcs_s(&size, buf1, sizeof(buf1)/sizeof(buf1[0]), result.message(), _TRUNCATE);
            ::mbstowcs_s(&size, buf2, sizeof(buf2)/sizeof(buf2[0]), result.file_name(), _TRUNCATE);
            ::Microsoft::VisualStudio::CppUnitTestFramework::Assert::Fail(buf1
                , &Microsoft::VisualStudio::CppUnitTestFramework::__LineInfo(buf2, "", result.line_number()) );
        }
    }
};

OnTestPartResult でテスト結果が来たら、VS11UnitTest にも通知しています。
あとは、このリスナーを gtest に追加します。

namespace unittest_sample
{
    TEST_MODULE_INITIALIZE(SetUp)
    {
        testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners();
        listeners.Append( new VCCppUnitTestPartResultReporter  );
    }
TEST_MODULE_INITIALIZE は全テストの実行直前に実行されるので、そこで追加をしています。


まとめ
最終的なコードをまとめました。

gtest_vcunit.h
#pragma once

#include <gtest/gtest.h>

#undef TEST
#define TEST(test_case_name, test_name) \
    VCTEST(test_case_name##test_name##_class, test_case_name##_##test_name)
#define VCTEST(className, methodName)   \
    TEST_CLASS(className) { public: TEST_METHOD(methodName); }; \
    void className::methodName() 

class VCCppUnitTestPartResultReporter : public testing::EmptyTestEventListener
{
public:
    virtual void OnTestPartResult(const testing::TestPartResult& result)
    {
        // VC にも送る
        if( result.failed() )
        {
            size_t size=0;
            wchar_t buf1[4096];
            wchar_t buf2[260];
            ::mbstowcs_s(&size, buf1, sizeof(buf1)/sizeof(buf1[0]), result.message(), _TRUNCATE);
            ::mbstowcs_s(&size, buf2, sizeof(buf2)/sizeof(buf2[0]), result.file_name(), _TRUNCATE);
            ::Microsoft::VisualStudio::CppUnitTestFramework::Assert::Fail(buf1
                , &Microsoft::VisualStudio::CppUnitTestFramework::__LineInfo(buf2, "", result.line_number()) );
        }
    }
};

inline void SetUpCppUnitTest(void)
{
    testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners();
    listeners.Append( new VCCppUnitTestPartResultReporter  );
}

unittest1.cpp
#include "stdafx.h"
#include "CppUnitTest.h"
#include "gtest_vcunit.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace unittest_sample
{       
    TEST_MODULE_INITIALIZE(SetUp)
    {
        SetUpCppUnitTest();
    }
    TEST_CLASS(UnitTest1)
    {
    public:
        
        TEST_METHOD(TestMethod1)
        {
            // TODO: Your test code here
            Assert::AreEqual(2, 3);
        }

    };
}

TEST(Test, Basic1)
{
    ASSERT_EQ(2, 3);
}
TEST(Test, Basic2)
{
    ASSERT_EQ(2, 3);
}

実行結果

今後の目標
現状できないことがたくさんあります。

できること。
  • TEST マクロの使用。
  • 各種アサーションの使用。

できないこと。
  • テストフィクスチャの使用。
  • 値をパラメータ化したテスト。
  • 型付けテスト、型をパラメータ化したテスト。
  • テスト名の取得。RecordProperty。
  • DISABLE テストの無視。
などなど。

対応していきたいなぁと思って進めてはいるのですが、あんまり進んではいないです。。。
進歩があったら、またブログに書きたいと思います。

2012年4月20日金曜日

Jenkins で bugspots の結果を見やすくする

bugspots を運用しはじめて数週間経ちますが、どうにもイイ運用方法がみつかりません。
bugspots の導入の話はこちら

今のところ、Jenkins で実行して結果をコンソール出力から確認しています。が、
コンソール出力を眺めるのはちょいと面倒なので、Jenkins に集計させようと思います。

Warning Plugin の設定
今回は Warning Plugin を使います。
こちらのプラグインではコンパイラの警告などを集計してくれるのですが、ユーザー独自のパーサーを定義できます。

Warning Plugin をインストールしたら、「Jenkinsの管理」>「システムの設定」ページの「Compiler Warnings」にパーサーを追加します。
追加ボタンを押すと設定項目が表示されます。

Name は適当に、Bugspots としておきましょう。

Regular Expression のところに検出したい部分を抜き出す正規表現を記述します。
今回は Bugspots の「0.9723 - ext/ed.cpp」のように出力される部分にマッチさせます。
^\s*(\d+\.\d+) - (.*)

続いて、Mapping Script を書きます。
「?」ボタンを押すとヘルプが出ます。そちらにサンプルがあるのでそれを参考にしました。
あとは、重要度 の項目があるので適当な閾値で分けるようにしました。
import hudson.plugins.warnings.parser.Warning
import hudson.plugins.analysis.util.model.Priority

String filename = matcher.group(2)
String category = "bugspots"
String message = matcher.group(1)
float value = Float.parseFloat(message)

Priority prio = Priority.NORMAL
if( value > 1.0 ) prio = Priority.HIGH
if( value < 0.1 ) prio = Priority.LOW
return new Warning(filename, 0, "bugspots", category, message, prio)

設定するとこんな感じになります。

プロジェクトの設定
Warning Plugin の設定ができました。続いて、プロジェクトの設定をします。

プロジェクトの設定の「コンパイラの警告の集計」にチェックを入れます。
「コンソールログをスキャンする」の「Parser」に先程追加した「Bugspots」があるのでそれを選択します。


あとは、保存して設定は終わりです。
まとめ
設定したプロジェクトを実行すると、結果は「コンパイラの警告」に表示されます。



これで少しは見やすくなりました。

重要度ごとに閾値を設けてステータスを変えることもできるので、
重要度High が多かったら 失敗 にするのもありですね。

今回はここまでです。

2012年4月15日日曜日

Jenkins のコンソール出力に色をつける(+ Google Test の編集)

きっかけは、Google Test(もとい、iutest)を実行させたときのコンソール出力がこんなことになっていたから。



あぁ、色変わらないんだなぁ~と思っていたら、AnsiColor Pluginってのがあったので試してみました。

結果
AnsiColor Plugin をインストールしたら、ビルド環境の Color ANSI Console Output にチェックを入れるだけです。

実行した結果がこちら。



色つきで表示されましたね!イイ感じです!!

Windows ターゲットだと…
Windows ターゲットでビルドされた場合、WIN32API でコンソールの色を変えるため、エスケープシーケンスが出力されません。
でも、こうして色つき出力されると Windows の場合でもしたくなります。
Google Test の改変
gtest.cc の ColoredPrintf 関数を見ると、Windows の場合とそれ以外とで #ifdef されているのがわかります。
色々とめんどくさいので、環境変数でエスケープシーケンスで出力するか分岐させたいと思います。

今回追加するのは以下のコード。ColoredPrintf 関数の
const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
の前にぶち込みます。Google Test のバージョンは 1.6 です。
if( String::CaseInsensitiveCStringEquals(posix::GetEnv("GTEST_ANSI_COLOR"), "yes") ) {
    printf("\033[0;3%sm", GetAnsiColorCode(color));
    vprintf(fmt, args);
    printf("\033[m");  // Resets the terminal to default.
    va_end(args);
    return;
  }
使う時は、SET GTEST_ANSI_COLOR=yes として使います。
(動作確認してないけど、たぶん大丈夫でしょう…)

iutest の場合
--iutest_color=ansi
で、エスケープシーケンスで出力されるようにしました。(v0.24.0.0)

まとめ
色がついたからといって何かあるわけではない。
ただ、視認性が上がるので、致命的な失敗があったら赤色で表示するとかイイかも。