Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

Visual Studio 11 : C++ Unit Test Framework
── C++単体テストの決定版(かもしれない)

Windows環境下でのC++単体テストはこれでキマリ!?

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2012/04/02 14:00

 2月末日、Windows 8 Consumer Previewと共にVisual Studio 11 Betaがリリースされました。まぎれもなく「現時点で最新のWindows開発環境」なわけで、このテのオモチャに目のない僕は早速インストールしたわけです。

目次

 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を以下のように用意します:

Target/include/orz/Date.h
#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
Target/Date.cpp
#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にリネームした後、以下のように修正します:

Test/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()を埋め込んだ(空の)テスト:

DummyTest.cpp
#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の必要はありませんからばっさり省略。ひとまずこの段階でテストを実行してみましょう。メニューから[表示]-[その他のウィンドウ]-[単体テストエクスプローラ]を開いてすべて実行すると、テスト対象とテストがビルドされた後、実行結果が表示されます。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • επιστημη(エピステーメー)

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~) だったり わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準講座 (監修) -...

All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5