ユニットテストの実践: CppUnitの導入(実装編)
5月 1st, 2008 Posted in C++, CppUnitの導入前回、CppUnitを導入しました。今回は実装編です。
本来はテストファーストで、CppUnitでテストケースを実装したあとにメインの実装を行うものなんでしょうけど、今回はすでに実装が終わったプロジェクトのユニットテストを行いました。今回作成したテストアプリはGUI版です。
まずはVCでプロジェクトを新規作成します。MFC AppWizard(exe)でダイアログベースで作成します。
[プロジェクト]-[設定]から[プロジェクトの設定]ダイアログを起動し、[C/C++]タブのカテゴリ[コード生成]で使用するランタイム ライブラリで[マルチスレッド(DLL、デバッグ)]を選択します。
同じく[C/C++]タブのカテゴリ[プロセッサ]インクルードファイルのパスに、メインのプロジェクトのパスを入力します。
[リンク]タブのカテゴリ[一般]オブジェクト・ライブラリ モジュールに、設定の対象がDebugの場合、
- cppunitd.lib testrunnerd.lib
Releaseの場合、
- cppunit.lib testrunner.lib
と入力します。
ここで、メインアプリのプロジェクトをワークスペースに追加しときます。そして[プロジェクト]-[依存関係]で、テストプロジェクトがメインプロジェクトに依存するように設定します。
テストアプリのStdAfx.hに次のincludeを記載します。
- #include <cppunit/ui/mfc/TestRunner.h>
- #include <cppunit/ui/text/TestRunner.h>
- #include <cppunit/extensions/TestFactoryRegistry.h>
- #include <cppunit/extensions/HelperMacros.h>
- #include <cppunit/CompilerOutputter.h>
C++アプリケーションの効率的なテスト手法(CppUnit編)なんかには、StdAfx.hは削除するとの記載がありますが、ボクの環境では削除しなくても問題ないです。てか削除するとダイアログベースのアプリなんでビルドできませんので。コンソールアプリの場合は必要ないでしょうね。
テストアプリのアプリクラス(CWinAppを継承しているクラス)のInitInstance()を次のように書き換えます。
- BOOL CTestApp::InitInstance()
- {
- AfxEnableControlContainer();
- #ifdef _AFXDLL
- Enable3dControls(); // 共有 DLL 内で MFC を使う場合はここをコールしてください。
- #else
- Enable3dControlsStatic(); // MFC と静的にリンクする場合はここをコールしてください。
- #endif
- CPPUNIT_NS::MfcUi::TestRunner runner;
- runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
- runner.run();
- return FALSE;
- }
メインプロジェクトのテスト対象となるクラスと対になるテスト用クラスを作成するのが流儀のようです。次のようなクラスを作成します。
- class CMainClass;
- class CTestClass : public CPPUNIT_NS::TestFixture
- {
- CPPUNIT_TEST_SUITE( CTestClass );
- CPPUNIT_TEST( testFunc1 );
- CPPUNIT_TEST( testFunc2 );
- CPPUNIT_TEST_SUITE_END();
- public:
- CTestClass();
- virtual ~CTestClass();
- void setUp();
- void tearDown();
- void testFunc1();
- void testFunc2();
- private:
- CMainClass* m_pApp;
- };
CPPUNIT_TEST_SUITEマクロとCPPUNIT_TEST_SUITE_ENDマクロの間にCPPUNIT_TESTマクロで、テスト対象となるテスト用の関数を登録します。setUp()はテスト用関数が実行される直前に呼ばれ、tearDown()はテスト終わったあとに呼ばれます。たとえばsetUp()でCMainClass* m_pAppをnewして、tearDown()でdeleteするような使い方になります。
では実装の方は、次のような感じ。
- CPPUNIT_TEST_SUITE_REGISTRATION( CTestClass );
- void CTestClass::setUp()
- {
- m_pApp = new CMainClass();
- }
- void CTestClass::tearDown()
- {
- delete m_pApp;
- }
- void CTestClass::testFunc1()
- {
- CPPUNIT_ASSERT_EQUAL( TRUE, m_pApp->Func1( 0 ) );
- CPPUNIT_ASSERT_EQUAL( FALSE, m_pApp->Func1( -1 ) );
- }
- void CTestClass::testFunc2()
- {
- CPPUNIT_ASSERT( m_pApp->Func2( 0 ) );
- CPPUNIT_ASSERT( m_pApp->Func2( -1 ) );
- }
CPPUNIT_ASSERT_EQUALは、第一引数と第二引数が等しい場合にテスト成功。違ったらテスト失敗となります。CPPUNIT_ASSERTは、引数がTRUEだったらテスト成功。FALSEだったらテスト失敗となります。ほかにも、
- CPPUNIT_ASSERT
- CPPUNIT_ASSERT_MESSAGE
- CPPUNIT_FAIL
- CPPUNIT_ASSERT_EQUAL_MESSAGE
- CPPUNIT_ASSERT_DOUBLES_EQUAL
などあります。ここでは詳しくは省略。
さて、ここでビルドするとリンクエラーが発生します。メインプロジェクトのCMainClass::Func1()やCMainClass::Func2()にリンクできないと。ここでしばらく悩みましたが、どうやらメインプロジェクトのCMainClassのCPPファイルを、テストプロジェクトに追加しないといけないようです。なんかこれ気持ち悪いんですけど。しょうがないんですかね。
これでちゃんとビルドが通ります。
実行するとダイアログが起動され、Browseからテストケースが確認できます。通常はALL Testsを選んでおいて、Autorun at startupにチェックを入れておけば、起動と同時にすべてのテストを実行してくれるので楽です。
【関連エントリ】
ユニットテストの実践: CppUnitの導入(インストール編)