Visual Studio 11(以下、VS11)の最大のウリはWindows 8流インターフェース・デザイン:Metro対応なのでしょうけど、根っからのC++屋な僕はそこらへんには目もくれず、Visual C++の新機能に興味津々です。Virtual PC上に用意したWindows 7へのVS11βインストール中に「What's New for Visual C++ in Visual Studio 11 Beta」を流し読みしていると、C++ unit test frameworkの文字が目に留まりました。
.NET(C#/VB.NET)であればVisual StudioがUnit Testプロジェクトの雛形を作ってくれ、Visual Studio内でテストできるのに、C++だけが置いてけぼりを喰らっていました。C++/CLIでテストを書く、あるいはWinUnitやCppUnitなどのUnit Test Frameworkをインストールすりゃいいのだけれどメンドクサイのは避けられない。C++に対応したUnit Test FrameworkがVisual Studioに組み込まれたのはマコトに喜ばしい限りです。追加インストール不要というお手軽さゆえにC++ Unit Test Frameworkの定番になるのかも。早速遊んでみましょかね。
同時にリリースされたVisual Studio 11 Express Beta for Windows 8もC++ Unit Test Frameworkに対応していました。
正しい日付を判定する
Unit Test Frameworkはその名のとおり単体テストのためのツールなのでテスト対象なしには話が始まりません。僕のオトモダチの一人、小島さんのアーティクル「ソフトウェア開発をシンプルにする考え方のコツ」から拝借させていただきます。
お題
namespace orz {
class Date {
public:
Date();
Date& set(int year, int month, int day);
bool is_valid() const; // 日付として正しいならtrueを返す
private:
int year_;
int month_;
int day_;
...
};
}
コレを今回のお題としましょう。Visual Studio 11 Betaを立ち上げ、Win32 static libraryプロジェクト:Targetを起こし、お題に従いTarget/include/orz/Date.hとTarget/Date.cppを以下のように用意します:
#ifndef ORZ_DATE_H__
#define ORZ_DATE_H__
namespace orz {
class Date {
public:
Date() : year_(1), month_(1), day_(1) {}
Date(int year, int month, int day);
Date& set(int year, int month, int day);
int year() const { return year_; }
int month() const { return month_; }
int day() const { return day_; }
bool is_valid() const;
private:
int year_;
int month_;
int day_;
};
}
#endif
#include <orz/Date.h>
namespace orz {
Date::Date(int year, int month, int day)
: year_(year), month_(month), day_(day) {
}
Date& Date::set(int year, int month, int day) {
year_ = year;
month_ = month;
day_ = day;
return *this;
}
bool Date::is_valid() const {
return false;
}
}
ここまではまだ前準備。コンパイルエラーとはならないものの中身はスカスカ。is_valid()はどんな日付であっても正しくないと判定します。
さてと、Unit Test Frameworkの出番です。新たにネイティブ単体テストプロジェクト:Testを起こします。
TestプロジェクトのプロパティでincludeパスとlibraryをTargetのしかるべき値に設定し、プロジェクト依存関係を指定します。
ひな形として用意されたunittest1.cppをDateTest.cppにリネームした後、以下のように修正します:
#include "stdafx.h"
#include "CppUnitTest.h"
#include <orz/Date.h> // 追加
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace orz; // 追加
namespace orzTest {
TEST_CLASS(DateTest) {
public:
TEST_METHOD(set) {
Date date;
date.set(1,2,3);
Assert::AreEqual(1, date.year());
Assert::AreEqual(2, date.month());
Assert::AreEqual(3, date.day());
}
TEST_METHOD(good_date) {
Date date;
Assert::IsTrue(date.set(2012,3,31).is_valid());
}
};
}
テスト・コード(ここではDateTest.cpp)にはいくつかのおやくそくがあります。
TEST_CLASS(class_name) { ... };
後述するTEST_METHODをいくつかまとめたものがTEST_CLASSです。テスト対象ごとにTEST_CLASSを定義するのが標準と言えるでしょうが、シチュエーションあるいはお好みに応じて(例えば正常系と異常系とか)細分化しても構いません。その際は一本のソースに複数のTEST_CLASSを書き並べてもいいし、追加/新しい項目/C++単体テストクラスで別ソースに分けるもよし。
TEST_METHOD(method_name) { ... }
テストの各項目に相当します。この中でテスト対象を動かし、その結果を検証します(検証方法は後述)。
上記2つはお決まりのルールとして、省略可能なものが2×3=6つほど:
TEST_MODULE_INITIALIZE(method_name) { ... }
TEST_MODULE_CLEANUP(method_name) { ... }
全テストの実行直前/直後にそれぞれ一度だけ呼び出されます。その名のとおり、テスト・モジュールの前準備/後始末を行うためのもの。環境変数/グローバル変数を設定したりDLLをLoadLibarary/FreeLibraryしたりCOMを初期化/解放したり。
TEST_CLASS_INITIALIZE(method_name) { ... }
TEST_CLASS_CLEANUP(method_name) { ... }
TEST_CLASS内の一連のTEST_METHOD実行直前/直後に一度だけ行う前準備/後始末を定義します。例えばデータベースやSocketとの接続/切断だとか。
TEST_METHOD_INITIALIZE(method_name) { ... }
TEST_METHOD_CLEANUP(method_name) { ... }
各TEST_METHODの実行直前/直後に呼び出されます。テストごとの前準備/後始末を行うのが目的です。
Logger::WriteMessage()を埋め込んだ(空の)テスト:
#include "stdafx.h"
#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace dummy {
TEST_MODULE_INITIALIZE(test_module_initialize) { Logger::WriteMessage(__FUNCTION__); }
TEST_MODULE_CLEANUP(test_module_cleanup) { Logger::WriteMessage(__FUNCTION__); }
TEST_CLASS(DummyTest) {
public:
TEST_CLASS_INITIALIZE(test_class_initialize) { Logger::WriteMessage(__FUNCTION__); }
TEST_CLASS_CLEANUP(test_class_cleanup) { Logger::WriteMessage(__FUNCTION__); }
TEST_METHOD_INITIALIZE(test_method_initialize) { Logger::WriteMessage(__FUNCTION__); }
TEST_METHOD_CLEANUP(test_method_cleanup) { Logger::WriteMessage(__FUNCTION__); }
TEST_METHOD(TestMethod1) { Logger::WriteMessage(__FUNCTION__); }
TEST_METHOD(TestMethod2) { Logger::WriteMessage(__FUNCTION__); }
TEST_METHOD(TestMethod3) { Logger::WriteMessage(__FUNCTION__); }
};
}
これを実行すると以下の結果が得られます。
TEST_METHODの実行順序はTEST_CLASSに並べた順にはならないみたいなので、実行順序に依存するテストは御法度ですね。
今回はINITIALIZE/CLEANUPの必要はありませんからばっさり省略。ひとまずこの段階でテストを実行してみましょう。メニューから[表示]-[その他のウィンドウ]-[単体テストエクスプローラ]を開いてすべて実行すると、テスト対象とテストがビルドされた後、実行結果が表示されます。



