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 テストの無視。
などなど。

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

0 件のコメント:

コメントを投稿