[技術講座] CppUnit 入門
続いて Dice のテストを書こうと思いますが、サイコロの目はランダムに生成されるので、結果を決定論的に書くことができません。とりあえず目が1以上6以下であるかチェックすることにしましょう。
Dice dice; dice.shuffle(); int value = dice.getValue(); TEST_ASSERT(1 <= value && value <= 6);
しかし、1回チェックしただけでは、たまたまその範囲に収まっただけかもしれません。できれば何回かチェックしたくなります。そういうときに便利なのが、CppUnit の拡張機能 RepeatedTest です。
dicegame/tests/DiceTest.cpp
#include "DiceTest.h" #include "dicegame/Dice.h" #include <cppunit/TestSuite.h> #include <cppunit/TestCaller.h> #include <cppunit/RepeatedTest.h> USING_NAMESPACE_CPPUNIT void DiceTest::testDice() { /* 省略 */ } void DiceTest::testLimits() { Dice dice; dice.shuffle(); int value = dice.getValue(); TEST_ASSERT(1 <= value && value <= 6); } Test* DiceTest::suite() { TestSuite* suite = new TestSuite("DiceTest"); suite->addTest(new TEST_CALLER(DiceTest, testDice)); /* DiceTest::testLimits() を12回実行する。 */ suite->addTest(new RepeatedTest(new TEST_CALLER(DiceTest, testLimits), 12)); return suite; }
次はコントローラクラス DiceGame のテストです。DiceGame は他のクラスと密接に関連しているので、それらをまとめてテストすることにします。しかしながら、Dice、DiceGamePlayer は作成 & テスト済みですが、ユーザインタフェースはまだできていません。そこで、テスト用のスタブクラスを用意することにします。ここで、設計時にインタフェース DiceGameListener を用意しておいたのが有効に働きます。
dicegame/DiceGameListener.h
#ifndef dicegame_DiceGameListener_h #define dicegame_DiceGameListener_h class DiceGameListener { public: enum OddOrEvenReply { ODD = 1, EVEN = 2, STOP = 9 }; public: virtual ~DiceGameListener() {} virtual void startRound() = 0; virtual void endRound() = 0; virtual int inquireOddOrEven() = 0; }; #endif /* !dicegame_DiceGameListener_h */
テストに使いやすいように DiceGameListener のスタブクラスを定義します。例えばこんな感じでどうでしょう。
dicegame/tests/DiceGameTest.h
#ifndef dicegame_tests_DiceGameTest_h #define dicegame_tests_DiceGameTest_h #include "dicegame/DiceGameListener.h" #include <cppunit/TestCase.h> USING_NAMESPACE_CPPUNIT class DiceGameTest : public TestCase { public: explicit DiceGameTest(const char* name) : TestCase(name) {} void testCreate(); void testInquireOddOrEven(); void testJudge(); void testDoRound(); void testRun(); static Test* suite(); public: /* テスト用スタブクラス */ class TestListener : public DiceGameListener { public: TestListener() : started(0), ended(0), inquired(0) {} virtual void startRound() { ++started; } virtual void endRound() { ++ended; } virtual int inquireOddOrEven(); int started, ended, inquired; }; }; #endif /* !dicegame_tests_DiceGameTest_h */
dicegame/tests/DiceGameTest.cpp
#include "DiceGameTest.h" #include "dicegame/DiceGamePlayer.h" #include "dicegame/DiceGame.h" #include <cppunit/TestSuite.h> #include <cppunit/TestCaller.h> #include <cppunit/RepeatedTest.h> USING_NAMESPACE_CPPUNIT int DiceGameTest::TestListener::inquireOddOrEven() { ++inquired; return (inquired < 5) ? inquired : STOP; } void DiceGameTest::testCreate() { /* 省略 */ } void DiceGameTest::testInquireOddOrEven() { /* 省略 */ } void DiceGameTest::testJudge() { /* 省略 */ } void DiceGameTest::testDoRound() { /* 省略 */ } void DiceGameTest::testRun() { /* TestListener を使って DiceGame を生成。 */ TestListener listener; DiceGame diceGame(&listener); /* TestListener の各属性が初期化されているか? */ TEST_ASSERT_EQUALS(0, listener.started); TEST_ASSERT_EQUALS(0, listener.ended); TEST_ASSERT_EQUALS(0, listener.inquired); diceGame.run(); /* TestListener::inquireOddOrEven() の戻り値は、 * 1回目: ODD * 2回目: EVEN * 3回目: 不正値 * 4回目: 不正値 * 5回目: STOP * となるので、こういう結果になるはず。 */ TEST_ASSERT_EQUALS(2, listener.started); TEST_ASSERT_EQUALS(2, listener.ended); TEST_ASSERT_EQUALS(5, listener.inquired); TEST_ASSERT_EQUALS(2, diceGame.getRound()); DiceGamePlayer* player = diceGame.getPlayer(); TEST_ASSERT_EQUALS(2, player->getWins() + player->getLosses()); } Test* DiceGameTest::suite() { TestSuite* suite = new TestSuite("DiceGameTest"); suite->addTest(new TEST_CALLER(DiceGameTest, testCreate)); suite->addTest(new TEST_CALLER(DiceGameTest, testInquireOddOrEven)); suite->addTest(new TEST_CALLER(DiceGameTest, testJudge)); suite->addTest(new RepeatedTest( new TEST_CALLER(DiceGameTest, testDoRound), 4)); suite->addTest(new TEST_CALLER(DiceGameTest, testRun)); return suite; }
ユーザの入力を、テスト用スタブクラスでシミュレートしながらテストを書いていくわけです。
最後に DiceGameUI ですが、今回このクラスの単体テストはしないことにします。キャラクタベースなら入出力を std::istream
、std::ostream
にしておいて std::stringstream
を使ってテストを書くということも可能ですが、GUI ですとテストが書きにくく、またユーザインタフェースは仕様も変化しやすいので、ロジック部分をできるだけ下位のクラスに移して、そちらを重点的にテストするようにした方が効果的な気がします。
ユーザインタフェースのテストは受け入れテストで行うことにしましょう。
© 2001 OGIS-RI Co., Ltd. |
|