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)

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

2012年4月12日木曜日

Visual Studio + Google Test で gtest-spi が使えないあなたに

Visual Studio で Google Test を使っていて
error C2065: '__LINE__Var' : 定義されていない識別子です。
と出てお困りのあなた。(そうでない方はここに答えはないです。)

[プロジェクトのプロパティ] > [C/C++] > [全般] の
[デバッグ情報の形式] を プログラム データベース (/Zi) に変えてみてください。

/ZI だと、__LINE__ は __LINE__Var+N (Nは番号) になるようで、このためコンパイルエラーになってしまうようです。

どうしても エディット コンティニュ用プログラム データベース (/ZI) を使いたい場合、
#define __LINE__Var 0
としてみてください。
行番号の出力が狂いますが、ビルドはできるようになると思います。

2012年4月9日月曜日

「ゲーム開発環境勉強会@関西」 に参加してきました

4/8(金) に行われました「ゲーム開発環境勉強会@関西」に参加してきました。

今回ははじめに GDC2011 での自動テストのラウンドテーブルの内容紹介から始まり、
後半は GDC2011 のトピックの中から数個議題を取り上げ、3グループに別れてラウンドテーブルをしました。

ラウンドテーブルが初めてで最初戸惑いもありましたが、活発な話し合いができたと思います。
普段会社でやっていることが他の会社ではどうなのか、~ということを試してみたけどどうだったとか、
メリット・デメリット、こんな事に悩んでいるとか。etc...

自分が参加したテーブルでは、Jenkins を主軸に、導入してどうだったかとか静的解析のこととか、自動化に関係する部分での話が活発にでました。
振り返ってみて、普段から Jenkins を使っている身としてはメリット・デメリットがあんまりうまく伝えられなかったかなぁと思いました。実際使っていてメリットはものすごく感じてはいるのですが、いざそれを説明しようとなるとうまく纏められず。。。
伝える力って大事ですよね。

まぁ、でも Jenkins 自体は簡単なので一度試してみると良いと思います。
Jenkins 日本語 Wiki
Jenkins Advent Calender 2011


他のテーブルはテストの話をがっつりとしたようです。そちらの話も気になりますね。
まとめ
勉強会とかラウンドテーブルとか数を重ねてより良いものになると思うので、今後も機会があればドンドン参加していきたいと思います。
以上、まとめ。

2012年4月6日金曜日

C で main の前に関数コール

C言語のテストフレームワークを書いていて知ったこと。

C言語版のテストフレームワークでも、Google Test のようにテストの登録をいちいちしなくていいインターフェイスを目指していました。

それを実現するためには、どうしても main 関数が呼ばれる前に関数を呼ぶ必要がありました。
C++ なら、
int dummy = func();
int main()
{
    return 0;
}
とか、コンストラクタとか使う方法があったのですが、
C だと、上の方法が使えず。(いけると思っていたのでガッカリ)
もちろんコンストラクタなどないので、どうしたもんかという状態でした。

__attribute__((constructor))
Google 先生に聞いてみると __attribute__((constructor)) ってのがヒット。

これだ!えいや!できたー!!

あ、でもコレ GCC でないとダメじゃん。

あきらめるのはまだ早い
gcc には __attribute__((constructor)) というものがあるが、
Visual Studio ではどうしたらいいの?という事で、Google で検索してみると以下の記事がヒットしました。
http://stackoverflow.com/questions/1113409/attribute-constructor-equivalent-in-vc

まんま答えがありました。 CRT$XCU を使うようです。

CRT のグローバル初期化にぶち込むってことですかね。
MSDNにも説明がありました。

こちらも問題なく動作しました。
とりあえず、Visual Studio と GCC で使えれば個人的に満足なので、他の環境は考えないことにする。

C言語テストフレームワークとしての iutest
branche で作業してますので、良かったら見てください。

今のところ、以下のような感じでテストが書けるようになりました。
#include "iutest_c.h"

IUTEST_C_WORKSPACE(); // ワークの構築用に必要...

int main(int argc, char* argv[])
{
    iuInitIrisUnitTest(&argc, argv);
    return IUTEST_RUN_ALL_TESTS();
}

IUTEST(AssertionTest, Simple)
{
    IUTEST_ASSERT_EQ(0, 0);
    IUTEST_ASSERT_NE(0, 1);
    IUTEST_ASSERT_LE(1, 1);
    IUTEST_ASSERT_LT(0, 1);
    IUTEST_ASSERT_GE(1, 1);
    IUTEST_ASSERT_GT(1, 0);
}

2012年4月1日日曜日

エイプリルフールなので遊んでみた

エイプリルフールということで少し遊んでみました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        /// 
        /// アプリケーションのメイン エントリ ポイントです。
        /// 
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form());
        }
    }
}

C# のよくあるコードです。
こちらのコードを C++ でビルドできるようにしてみました。

プログラムのダウンロードはこちらから。
MD5 : 443fb06d8e8f44c5066e9c9b7fd50cd8

[STAThread] をなんとかできなかったのが残念。