More Related Content More from Kuniaki Igarashi More from Kuniaki Igarashi (20) プログラマのためのテスト21. プログラマのためのテスト
Kuniaki IGARASHI
http://igarashikuniaki.net/tdiary/
igarashikuniaki@gmail.com
2007.4.24 3. UnitTest
ユニットテスト
メソッド単位でコードを検証するテストコードを書き、
戻り値、副作用が妥当であることを確認するテスト
UnitTest(C++/CPPUnit)のCode例
// テスト対象メソッド addition(int arg1, int arg2)
// 引数の和を返し、メンバ変数m_lastResultに結果を格納するメソッド
int result = addition(2,3); // テスト対象のメソッドを実行して
CPPUNIT_ASSERT_EQUAL((int) 5, result); // 結果を確認
CPPUNIT_ASSERT_EQUAL((int) 5, m_lastResult); // 結果を確認
Green テスト成功 Red テスト失敗 4. UnitTestの基本方針
• 確実にNGになるケースをまず書こう
テストが実行されていることを確かめましょう Red テスト失敗
例:CPPUNIT_FAIL(quot;messagequot;); /// 必ず失敗し、messageを表示するテスト
テストできたらCPPUNIT_FAILを外してGreenにします。 Green テスト成功
• テストを書こう
新規実装であれば仕様をコードに落としながら。
既存実装の修正
1. 既存のコードに合致するテストを書く。 Green テスト成功
2. 修正後の仕様を満たすテストを書く。 Red テスト失敗
• 本番コードを書こう
テストがGreenになるように本番コードを書いていきます。 Green テスト成功
テストで仕様抜け、テストコードパス抜けを見つけたらテストを書き足します。
• リファクタリング
必要があれば、テストを成功のまま保ちつつリファクタリングを行います。
Green テスト成功 5. テスト基本形
// テスト対象メソッド Calc::addition(int arg1, int arg2)
// 引数の和を返し、メンバ変数m_lastResultに結果を格納するメソッド
void CalcUnitTest::test_Addition(void)
{
Calc calc; // テスト対象クラスを作成
int result = calc.addition(2,3); // テスト対象のメソッドを実行して
CPPUNIT_ASSERT_EQUAL((int) 5, result); // 結果を確認
CPPUNIT_ASSERT_EQUAL((int) 5, calc.m_lastResult); // 結果を確認
// ↑calc.m_lastResultにテストクラスからアクセスできるように、
// テスト対象クラスのヘッダファイルで以下のようにfriend指定しておきます。
// friend class FrameBuilderUnitTest;
}
CPPUNIT_ASSERT_EQUAL (x,y) → x==y の場合にOK。
CPPUNIT_ASSERT_EQUAL((int) 期待値, テスト対象値);
通常、期待値を第1引数に、テスト対象値を第2引数に渡します。
型をCPPUnitに伝えるために、明示的にキャストする必要があります。 6. 例外送出のテスト
/// テスト対象 Yarinage::Set(Yari yari)
/// 槍をセットされると槍型の例外を投げるメソッド。
void YarinageUnitTest::test_Set(void)
{ CPPUNIT_ASSERT_THROW(実行,送出予期型);
Yarinage yarinage; // テスト対象クラスを作成 CPPUNIT_ASSERT_NO_THROW();
Yari yari; // テストに使うクラスを作成
CPPUNIT_ASSERT_THROW(yarinage.Set(yari), Yari);
// 飛ばないことを確認するには
// CPPUNIT_ASSERT_NO_THROW(); void YarinageUnitTest::test_Set(void)
} {
Yarinage yarinage;
Yari yari;
try{
yarinage.Set(yari);
// NGパターン
CPPUNIT_FAIL(quot;何も飛んできませんでしたよ。quot;);
} catch (Yari e) {
// OKパターン 槍が投げられました。
または、特別なことをせず } catch (...){
意図しないコードパスにCPPUNIT_FAILを // NGパターン
CPPUNIT_FAIL(quot;槍じゃないものが飛んできましたよ。quot;);
入れてNGにすれば良い。 }
} 8. Mock(Stub)
制御可能なニセモノ下位モジュール
ホンモノコード テストコード
テスト対象モジュール
Calc::Calc(){ // コンストラクタ
#ifndef UNITTEST
m_paiCalc = new PaiCalc(); // ホンモノ
#else
m_paiCalc = new PaiCalcMock(); // ニセモノ
#endif
}
PaiCalc PaiCalcMock
GetResult (1000000) GetResult (1000000)
5分計算して結果を返す てきとーな値ですぐに結果を返す
返す値はテストコードから制御可能
C++ヘッダファイルからMockを生成するMockMaker作りました。
(C++ヘッダのパースがまだまだ全然ダメですが)
http://igarashikuniaki.net/data/MockMaker/MockMaker.zip 9. メソッド呼び出しの確認
テスト対象モジュール
テストコード
Foo::DoCalcPai()
g_is_ PaiCalcMock_GetResult _Invoked = false;
内で foo.DoCalcPai(); // テスト実行
PaiCalc::GetResult() CPPUNIT_ASSERT_EQUAL((bool)true,
が呼び出されるか g_is_ PaiCalcMock_GetResult _Invoked);
確認するテスト
Foo(テスト対象モジュール)
Foo::DoCalcPai() {
m_paiCalc->GetResult (1000000);
// Test時はMockが呼び出される
}
PaiCalcMock
PaiCalcMock::GetResult (1000000){
g_is_ PaiCalcMock_GetResult _Invoked = true
テスタで導通チェックを // メソッド内でグローバル変数に値代入
するようなものです。 } 10. UnitTest実装時のCheckList
• 全てのパスを通過
• 全ての環境(変数やメモリ状態)の組み合わせ
• 全ての異常系
• 全ての状態遷移
• 例外送出
• NULLチェック
• 境界値
• 変換 - 逆変換 - diff
• Initializeが呼ばれる前に全メソッドを呼ぶテスト
• Finalizeが呼ばれた後に全メソッドを呼ぶテスト
• Initializeを呼んだ後にInitializeを呼ぶテスト 11. Test First Development
Test Driven Development
本番コードを書く前にユニットテストコードを書く
→ テストコードに駆動される開発
テストコードで境界値や全てのケースを網羅しようという意識になる
仕様が固まっていない部分は固めようとする
テスト容易な設計を考慮するようになる
テストを満たすように、突貫コードを書く
仕様破綻がないかチェックする
エラーケースをケアするコードを書く
リファクタリング 13. Dependency Injectionで
Real/Mock切りかえ
RealBar
SetFoo(InterfaceFoo* foo){ InterfaceFoo
m_foo = foo; Calc() 純粋仮想関数
}
m_foo->Calc(); RealFoo MockFoo
Calc()を実装 Calc()を実装
// 本番時はRealFoo
// テスト時はMockFoo
本番コード テストコード
RealMain(){ TestMain(){
RealBar bar; RealBar bar;
bar.SetFoo(new RealFoo()); bar.SetFoo(new McokFoo());
... ...
} }
本番コードにテストコードが含まれないスッキリした設計・実装! 15. サンプルコード
==== DI と Mock Object を利用した UnitTest Pattern ==== ■テストコード
// 実際は Mockpp を利用して、より柔軟にテスト条件に応じた値を返す Mock を作る
■ 本番コード class MockFoo : public IFoo {
Bar class は Foo class の演算結果を利用して処理をします。 int m_calc_value;
class IFoo : public IFoo { void SetCalcFoo(int value) {
int CalcFoo(); m_calc_value = value;
} } Mockpp を利用することで尐し楽をして Mock class を作成できる
ようになります。
class RealFoo { int CalcFoo() {
int CalcFoo() { return m_calc_value; このようにして Mock Class を一度作っておけば、テスト本体だけで
return 3; } テスト条件を記述できるようになり、テスト時も本番時もまったく
} } 同じオブジェクトを利用できるようになります。
} # リビルド不要、本番とテストでコードの食い違いが起こりえない
void test_main()
class RealBar { また、必ず依存関係の間にインタフェースが挟まるので、
{ MockFoo foo; それぞれのクラスを別々に開発することも容易になります。
IFoo *m_foo; RealBar bar; # 依存先の実体がなくてもコンパイルできるし、Mock を入れて
# 逐次テストができる
void SetFoo(IFoo* foo) { bar.SetFoo(&foo);
m_foo = foo; つまり、DI + MockObject で UnitTest というのは、このような
} foo.SetCalcValue(1); メリットを得るために、本番コードのアーキテクチャを変えるという
ASSERT(bar.CalcBar() == 2); extreme なテスト手法です。
// CalcFoo() の結果を 2倍する
int CalcBar() { foo.SetCalcValue(2);
return 2 * m_foo->CalcFoo(); ASSERT(bar.CalcBar() == 4);
} }
}
void main()
{
RealFoo foo;
RealBar bar;
// Setter Injection.
bar.SetFoo(&foo);
}
printf(quot;CalcBar = %d¥nquot;, bar.CalcBar()); (c)ひぐまさん
多謝!! 16. Javaの世界は一歩先を行く
• Seasar2 (DIコンテナFrameWork)
コンテナの中身を外部ファイルで自由に操作
.xml
コード
使用したいコンテナ(クラ
aのインスタンスは○○
ス)の名前だけしっていれ
bのインスタンスは△△
ばいい。
cのインスタンスは□□
そのインスタンスが何か ...
は意識しなくて良い。
DIコンテナ
本番・テストごとにxmlを切り替えてReal/Mockを指定する。
Real.xml Mock.xml TestA.xml
aのインスタンスはRealA aのインスタンスはMockA aのインスタンスはRealA
bのインスタンスはRealB bのインスタンスはMockB bのインスタンスはMockB
cのインスタンスはRealC cのインスタンスはMockC cのインスタンスはMockC 17. UnitTestPatterns
■The Simple-Test Pattern(シンプルテストパターン)
どのようなUnitTestを書け ■The Code-Path Pattern(コードパスパターン)
■The Parameter-Range Pattern(パラメータレンジパターン)
ばいいのかを形式化し、テ ■Data Driven Test Patterns(データ駆動テストパターン)
■The Simple-Test-Data Pattern(シンプルテストデータパターン)
ストファーストをプログラマ ■The Data-Transformation-Test Pattern(データ変換テストパターン)
■Data Transaction Patterns (データトランザクションパターン)
の習慣にすることを目指す ■The Simple-Data-I/O Pattern(シンプルデータI/Oパターン)
■The Constraint-Data Pattern(制約データパターン)
■The Rollback Pattern(ロールバックパターン)
webページ ■Collection Management Patterns(コレクション管理パターン)
■The Collection-Order Pattern(コレクションオーダリングパターン)
http://www.marcclifton.com/tabid/87/Default.aspx (英語) ■The Enumeration Pattern(列挙パターン)
■The Collection-Constraint Pattern(コレクション制約パターン)
http://igarashikuniaki.net/fswiki/wiki.cgi?page=UnitTestPatterns
■The Collection-Indexing Pattern(コレクションインデクシングパターン)
(日本語翻訳途中)
■Performance Patterns(パフォーマンスパターン)
■The Performance-Test Pattern(パフォーマンステストパターン)
■Process Patterns(プロセスパターン)
■The Process-Sequence Pattern(プロセスシーケンスパターン)
■The Process-State Pattern(プロセス状態パターン)
■The Process-Rule Pattern(プロセスルールパターン)
■Simulation Patterns(シミュレーションパターン)
■Mock-Object Pattern(モックオブジェクトパターン)
■The Service-Simulation Pattern(サービスシミュレーションパターン)
■The Bit-Error-Simulation Pattern(ビットエラーシミュレーションパターン)
テストしやすい設計をするために、テストファースト。
テストを設計前に書けば、テストまで考慮した設計が可能。 19. テストを育てる
テストが書かれている範囲はコントロール
下にあるということ。
テストを追加していくということは、
テストにより動作保証される範囲を増やし、
リスク把握できる部分を広げていくこと。
※NGでひっかかった回数
OK を記録するような仕組みが
あると面白いね。
OK
OK
現在、テストがカバーしているコードがどの辺なのか バグの場所が分かる
見える化してくれるツールがあるといいですね。 ドラゴンレーダーが欲しいです。 22. 参考文献
• はじめて学ぶソフトウェアのテスト技法
著:リー・コープランド ISBN : 4-8222-8251-1
これ1冊でプログラマレベルではテストマスター
• CPPUnitによる実践テスト技法
http://www.mikamama.com/CppUnitBook/draft/index.html (draft)
著:大月美佳 ISBN:4-7980-0571-1
ユニットテストの入門書。リファレンスとしても便利。
• 知識ゼロから学ぶソフトウェアテスト
著:高橋寿一 ISBN:4-7981-0709-3
SONYのテストエンジニアさんの著書。
実際の経験に基づくノウハウが多数。
• WEB+DB PRESS vol.35
著:和田卓人 ISBN:4-7741-2931-3
Java DIコンテナSeasar2を使ったユニットテストの記事など入門記事。