main関数は登録されたテスト・スイートをまとめて実行し、失敗したテスト・ケースをレポートとして出力します。これは以下のコードをそのまま流用してもらって構いません。本稿では「Main.cpp」という名前のソース・ファイルを追加して、そこにmain関数を実装しました。
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TestRunner.h>
int main( int argc, char* argv[] ) {
// イベント・マネージャとテスト・コントローラを生成する
CPPUNIT_NS::TestResult controller;
// テスト結果収集リスナをコントローラにアタッチする
CPPUNIT_NS::TestResultCollector result;
controller.addListener( &result );
// 「.」で進行状況を出力するリスナをアタッチする
CPPUNIT_NS::BriefTestProgressListener progress;
controller.addListener( &progress );
// テスト・ランナーにテスト群を与え、テストする
CPPUNIT_NS::TestRunner runner;
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
runner.run( controller );
// テスト結果を標準出力に吐き出す
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
outputter.write();
return result.wasSuccessful() ? 0 : 1;
}
では、CounterTest.cppファイルとMain.cppファイルからなるCounterTestプロジェクトをビルドし、実行してみましょう。
VS 2005内からであれば、メニュー・バーの[デバッグ]−[デバッグなしで開始]を実行します。次のような結果が得られるはずです。
ご覧のとおり、初期化(test_init)、インクリメント(test_incr)、クリア(test_clear)の各テスト・ケースで失敗したことが報告されます。最初に用意したのがハリボテ実装ですから当然の結果です。
ここで次のコードのように、コンストラクタとgetメソッドに正しい実装を施します。
#include "Counter.h"
Counter::Counter() : count_(0) {
}
int Counter::get() const {
return count_;
}
void Counter::incr() {
}
void Counter::clear() {
}
再度ビルドし、実行すると……。
失敗個所が減りましたね。では、残るincrメソッドとclearメソッドを正しく実装し、テストしてください。全テスト・ケースにパスすれば、以下の結果が得られます。
このように、CppUnitを使った単体テストでは複数のテスト項目(=テスト・ケース)を一気に実行し、その結果を出力します。数百のテスト項目でも恐らく数秒で全項目のテストが完了するでしょう。「少しずつ実装してはテストを繰り返し、仕様から起こしたテスト項目をすべてパスすれば実装完了」とする開発スタイルでは、“高速なテスト実行”が重要です。テストの実行に長い時間を要するなら、“少しずつ実装してはテスト”のリズムを崩してしまいますからね。
しかしこれは裏を返せば、「CppUnitはUI(ユーザー・インターフェイス)が絡む部分のテストには不向き」ということになります。UIを持った部分はどうしても、ユーザーのキー入力やマウス操作を伴いますから、多くのテスト項目を全件、一気に、素早く実行できません。従ってMFC(Microsoft Foundation Class Library)を用いたWindowsアプリケーションのように、UIを伴うアプリケーションで単体テストを適用できるのはUIの絡まないロジック層に限られることになります。
ユーザーのキー入力やマウス操作を疑似的に模倣してテストを行うことも可能ではありますが、たとえそのような方法を採ったにしても、あるいはUIのテストを人手で行うにしても、ロジック層の正常動作が保証されていないとUIのテストに支障をきたします。“期待する結果”が得られなかったとき、それが「UIによるものか、ロジック層の不具合によるものか」を判断しなければならないからです。
そこで筆者は、アプリケーションからUI周りとロジック層とを切り分けるため、MFCのDocument/Viewアーキテクチャを活用することをお勧めします。さらにDocument部からMFCに依存しない純ロジック部を分離し、Documentがそれを内包・移譲する形式を採れば単体テストがとても楽になります。
Copyright© Digital Advantage Corp. All Rights Reserved.