進む

ちゅうわけで、自作スクリプトとCppUnitを使用してテストファーストの練習をしてみます。書いたスクリプトは3つ。
createTestCaseHeader.tcl テストケース.cppの.hを作る。
createTestCaseTemp.tcl テストケース.cppの雛型を作る。
createTestRunner.tcl テストケースを実行するソースとmakefileを作る。
これらのスクリプトはテストケースの追加とテストランナーの構築を簡単にするために書いたもので、これが無くても当然テストは可能です。あと、 cppunit.libとtestrunner.dllとtestrunner.libをビルドしておく必要があります。cppunitのバージョンは 1.8.0です。
スクリプトはまだ改造の途中でコメント付けとかルーチンでへぼいところも多多ありますのでご容赦ください。ていうか名前が長いのを何とかしたいです (汗)。このスクリプトはどなたでもご自由にお使いください。
あとなぜtclなのかと説教されそうですが・・・いいじゃないですか!!(逆切れ)


それでは、簡単に計算をするクラスを作ってみます。Keisanという名前のディレクトリにソースファイルを置くことにします。計算クラスCalcを Calc.hで定義します。

mkdir Keisan
cd Keisan
vim Calc.h

1  class Calc {
2 public:
3 int add(int x, int y);
4 int sub(int x, int y);
5 };
Calc.cppも適当に作っておきます。

vim Calc.cpp

 1  #include "Calc.h"
2
3 int
4 Calc::add(int x, int y) {
5 return 0;
6 }
7
8 int
9 Calc::sub(int x, int y) {
10 return 0;
11 }

それではテストを書きます。テストはtestsディレクトリを作成して、テスト用のソースとスクリプトを全て置くことにします。

mkdir tests
copy hogepath\createTestRunner.tcl tests
copy hogepath\createTestCaseTemp.tcl tests
copy hogepath\createTestCaseHeader.tcl tests
cd tests

ここでスクリプトを使ってテストケースの雛型を作ります。手で書いてもいいんですが、クラス宣言のヘッダファイルCalcTest.hはテストのビルド直 前 に自動生成させるつもりなので書きません。ちなみにスクリプトの関係上XXXTest.cppというふうにXXXをテストするときはTestを後ろにつけ た名前にしています。

tclsh createTestCaseTemp.tcl ../Calc.h > CalcTest.cpp
vim CalcTest.cpp

 1  #include "CalcTest.h"
2
3 /*
4 @TARGET
5 ../Calc.h
6 */
7
8 /*
9 @PRIVATE
10
11 */
12
13 void CalcTest::setUp () {
14 }
15
16 void CalcTest::tearDown () {
17 }
18
19 //========================================
20 // Test Cases
21 //========================================
22
23 void CalcTest::test_Calc () {
24 CPPUNIT_FAIL("NO TEST CODE");
25 }
26
27 void CalcTest::test_add () {
28 CPPUNIT_FAIL("NO TEST CODE");
29 }
30
31 void CalcTest::test_sub () {
32 CPPUNIT_FAIL("NO TEST CODE");
33 }
34
と、このような(しょぼい)ものが自動生成されるので、テストケースのメソッドを修正したり、追加していったりすればOKです。/*@TARGET */とあるのは、テスト対象のクラスのヘッダで、createTestRunner.tclスクリプトがmakefileを作るときに参照します。 /*@PRIVATE */とあるのは、createTestCaseHeader.tclスクリプトがヘッダファイルを作ってクラ ス宣言を出力するときにprivate宣言のとこに出力してくれます。テストケースクラスで使いたいprivate変数の宣言を書いておきます。


setUp、tearDownはそれぞれのテストケースを実 行する前と、実行した後に呼ばれるので変数の初期化の処理などを書きます。
で、テストで使えるAssertマクロは次のとおり。
CPPUNIT_ASSERT( condition ); conditionが偽(false,0)の時に失敗
CPPUNIT_ASSERT_MESSAGE( message, condition ); 偽の時に失敗してmessageを出力
CPPUNIT_FAIL( message ); 必ず失敗してmessageを出力
CPPUNIT_ASSERT_EQUAL( expected, actual ); 得られた結果actual!=期待する値expectedの時、失敗
CPPUNIT_ASSERT_EQUAL_MESSAGE( message, expected, actual ); CPPUNIT_ASSERT_EQUALで失敗した時message を出力
CPPUNIT_ASSERT_DOUBLES_EQUAL( expected, actual, delta ); 浮動少数値のEQUAL。誤差はdeltaで指定する。

よくは知りませんが、テストがちゃんと失敗する事を最初に確認するのが流儀のようなので、とりあえずこのままテストを実行してみることにします。出力した 雛型はCPPUNIT_FAILが書いてあるので、実行すれば全て失敗するはずです。

テストを実行するには、今書いたCalcTestクラスを呼び出して実行して、さらにその結果を収集するプログラムが別に必要です。これを createTestRunner.tclで作ります。引数にテストケースのクラス名 (=ファイル名)を渡します。

tclsh createTestRunner.tcl CalcTest


そうすると次のファイルが生成されます。
TestRunnerGUI.cpp
MFCを使ったGUI版のテスト実行プログラムのソース
TestRunner.cpp
コンソール版のテスト実行プログラムのソース
makefile
上記二つのmakefile。VC6.0用です。7.0は不明です。

一応それぞれの中身を確認しておきますかね・・・

vim TestRunnerGUI.cpp

 1  #include <afxwin.h> //MFC Core
2
3 #include <cppunit/ui/mfc/TestRunner.h>
4 #include <cppunit/extensions/TestFactoryRegistry.h>
5
6 #include "CalcTest.h"
7 CPPUNIT_TEST_SUITE_REGISTRATION( CalcTest );
8
9 class TestRunnerGUI : public CWinApp{
10 public:
11 virtual BOOL InitInstance(){
12 // CppUnit Test Run
13 CppUnit::MfcUi::TestRunner runner;
14 runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() );
15 runner.run();
16 return TRUE;
17 }
18 };
19
20 TestRunnerGUI theApp;
MFCはなんちゅーかややこしくてよくわからなかったのでバッサバッサと切って・・・。一応動いてますが、おかしいところがあった ら教えてください。

vim TestRunner.cpp

 1  #include <cppunit/ui/text/TestRunner.h>
2
3 #include "CalcTest.h"
4 CPPUNIT_TEST_SUITE_REGISTRATION( CalcTest );
5
6 int main(){
7 CppUnit::TextUi::TestRunner runner;
8 runner.addTest( CppUnit::TestFactoryRegistry::getRegistry().makeTest() );
9 bool retcode = runner.run();
10 return !retcode;
11 }

vim makefile

 1  CPPUNITROOT  = D:\My Documents\Visual Studio Projects\cppunit-1.8.0
2 MAKEHEADER = tclsh createTestCaseHeader.tcl
3 CC = cl
4 LD = link
5 RM = del /Q
6 CFLAGS = /nologo -c /MD /GR /GX /I"$(CPPUNITROOT)/include"
7 LFLAGS = /nologo /libpath:"$(CPPUNITROOT)/lib"
8 OBJS = Calc.obj
9 TOBJS = CalcTest.obj
10
11
12 all: TestRunner.exe TestRunnerGUI.exe
13
14
15 #***********************************************
16 # Test Runner Text CUI
17 #***********************************************
18 TestRunner.exe: TestRunner.cpp $(OBJS) $(TOBJS)
19 $(CC) $(CFLAGS) TestRunner.cpp
20 $(LD) $(LFLAGS) TestRunner.obj $(OBJS) $(TOBJS) cppunit.lib
21
22 #***********************************************
23 # Test Runner MFC GUI
24 #***********************************************
25 TestRunnerGUI.exe: TestRunnerGUI.cpp testrunner.dll $(OBJS) $(TOBJS)
26 $(CC) $(CFLAGS) TestRunnerGUI.cpp /D "_AFXDLL"
27 $(LD) $(LFLAGS) TestRunnerGUI.obj $(OBJS) $(TOBJS) \
28 cppunit.lib testrunner.lib -subsystem:windows
29
30 testrunner.dll: "$(CPPUNITROOT)\lib\testrunner.dll"
31 copy "$(CPPUNITROOT)\lib\testrunner.dll" ./
32
33 #***********************************************
34 # Test Suites
35 #***********************************************
36
37 #---------------------
38 # CalcTest
39 #---------------------
40 CalcTest.obj: CalcTest.cpp CalcTest.h "../Calc.h"
41 $(CC) $(CFLAGS) CalcTest.cpp
42
43 CalcTest.h: CalcTest.cpp
44 $(MAKEHEADER) CalcTest.cpp "../Calc.h" > CalcTest.h
45
46 #***********************************************
47 # Test Target
48 #***********************************************
49 Calc.obj: ../Calc.cpp ../Calc.h
50 $(CC) $(CFLAGS) ../Calc.cpp
51
52
53 clean:
54 if exist *.pdb $(RM) *.pdb
55 if exist *.idb $(RM) *.idb
56 if exist *.ilk $(RM) *.ilk
57 if exist *.exp $(RM) *.exp
58 if exist *.obj $(RM) *.obj
59 if exist *.lib $(RM) *.lib
60 if exist *.dll $(RM) *.dll
61 if exist *.exe $(RM) *.exe
62

あっ、云うの忘れてましたが、makefileの最初の方を見てください。
 1  CPPUNITROOT  = D:\Visual Studio Projects\cppunit-1.8.0
CPPUNITROOTはCPPUNITのディレクトリで以下にlibやらincludeなどのディレクトリを含んでいる必要があります。


ほんでは・・・

nmake

コンパイルが通ればOKです。
nmakeはVCのmakeです。nmakeが見つからないという時は、call "C:\Program Files\Microsoft Visual Studio\VC98\Bin\VCVARS32.BAT"とするとパスがとおります。

TestRunner.exeとTestRunnerGUI.exeが出来ているのを確認してください。どっちも同じテスト を実行するので、どっちか片方を実行するだけでいいんですが、ここはせっかくなので両方実行してみます。

TestRunner.exe

(実行結果)

.F.F.F

!!!FAILURES!!!
Test Results:
Run:  3   Failures: 3   Errors: 0


1) test: CalcTest.test_Calc (F) line: 24 CalcTest.cpp
 "NO TEST CODE"

2) test: CalcTest.test_add (F) line: 28 CalcTest.cpp
 "NO TEST CODE"

3) test: CalcTest.test_sub (F) line: 32 CalcTest.cpp
 "NO TEST CODE"




全ての(といっても3つですが)テストケースが失敗しているのを確認します。では次にGUI版をば・・・

TestRunnerGUI.exe





赤いバーは失敗の証拠です。全て成功すると緑です。


これでテストがちゃんと動いていることを確認したので、次にテストコードを実装します。
・・・と、その前に、スクリプトから自動出力したCalcTest.hを確認しておきます。(見るだけで何もしませんが)

vim CalcTest.h

 1  #ifndef DEFINE_UNITTESTS_H_CalcTest
2 #define DEFINE_UNITTESTS_H_CalcTest
3
4 #include <cppunit/extensions/HelperMacros.h>
5
6 #include "../Calc.h"
7
8 class CalcTest : public CppUnit::TestFixture {
9 CPPUNIT_TEST_SUITE( CalcTest );
10 CPPUNIT_TEST( test_Calc );
11 CPPUNIT_TEST( test_add );
12 CPPUNIT_TEST( test_sub );
13 CPPUNIT_TEST_SUITE_END();
14
15 private:
16
17 public:
18 void setUp ();
19 void tearDown ();
20
21 protected:
22 void test_Calc ();
23 void test_add ();
24 void test_sub ();
25
26 };
27
28 #endif /* DEFINE_UNITTESTS_H_CalcTest */

makefileを確認すればわかると思いますが、CalcTest.cppが更新されると、これも自動で更新されるような仕組みになっています。なの で、実際にテストケースを追加したり削除したりするときに編集するのはCalcTest.cppだけよいわけです。


それじゃあ、テストコードを実装します。


つづく