プログラマのためのテスト2

24
プログラマのためのテスト Kuniaki IGARASHI http://igarashikuniaki.net/tdiary/ [email protected] 2007.4.24

Upload: kuniaki-igarashi

Post on 24-May-2015

3.272 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: プログラマのためのテスト2

プログラマのためのテスト

Kuniaki IGARASHIhttp://igarashikuniaki.net/tdiary/

[email protected]

Page 2: プログラマのためのテスト2

UnitTest入門とUnitTestデザインパターン

Page 3: プログラマのためのテスト2

メソッド単位でコードを検証するテストコードを書き、

戻り値、副作用が妥当であることを確認するテスト

// テスト対象メソッド 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); // 結果を確認

UnitTest(C++/CPPUnit)のCode例

UnitTestユニットテスト

Green テスト成功 Red テスト失敗

Page 4: プログラマのためのテスト2

UnitTestの基本方針

• 確実にNGになるケースをまず書こうテストが実行されていることを確かめましょう

例:CPPUNIT_FAIL("message"); /// 必ず失敗し、messageを表示するテストテストできたらCPPUNIT_FAILを外してGreenにします。

• テストを書こう新規実装であれば仕様をコードに落としながら。既存実装の修正1. 既存のコードに合致するテストを書く。2. 修正後の仕様を満たすテストを書く。

• 本番コードを書こうテストがGreenになるように本番コードを書いていきます。テストで仕様抜け、テストコードパス抜けを見つけたらテストを書き足します。

• リファクタリング必要があれば、テストを成功のまま保ちつつリファクタリングを行います。

Green テスト成功Red テスト失敗

Red テスト失敗

Green テスト成功

Green テスト成功

Green テスト成功

Page 5: プログラマのためのテスト2

テスト基本形

// テスト対象メソッド 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に伝えるために、明示的にキャストする必要があります。

Page 6: プログラマのためのテスト2

例外送出のテスト

void YarinageUnitTest::test_Set(void){

Yarinage yarinage; Yari yari;

try{yarinage.Set(yari);

// NGパターンCPPUNIT_FAIL("何も飛んできませんでしたよ。");

} catch (Yari e) {// OKパターン槍が投げられました。

} catch (...){// NGパターンCPPUNIT_FAIL("槍じゃないものが飛んできましたよ。");

}}

または、特別なことをせず意図しないコードパスにCPPUNIT_FAILを入れてNGにすれば良い。

/// テスト対象 Yarinage::Set(Yari yari) /// 槍をセットされると槍型の例外を投げるメソッド。void YarinageUnitTest::test_Set(void){

Yarinage yarinage; // テスト対象クラスを作成Yari yari; // テストに使うクラスを作成

CPPUNIT_ASSERT_THROW(yarinage.Set(yari), Yari);// 飛ばないことを確認するには// CPPUNIT_ASSERT_NO_THROW();

}

CPPUNIT_ASSERT_THROW(実行,送出予期型);CPPUNIT_ASSERT_NO_THROW();

Page 7: プログラマのためのテスト2

全パス・全状態の網羅

直交表や全ペアといったテスト技法が助けになります→参考文献:「はじめて学ぶソフトウェアのテスト技法」

直行表

Page 8: プログラマのためのテスト2

Mock(Stub)

C++ヘッダファイルからMockを生成するMockMaker作りました。(C++ヘッダのパースがまだまだ全然ダメですが)http://igarashikuniaki.net/data/MockMaker/MockMaker.zip

制御可能なニセモノ下位モジュール

テスト対象モジュール

PaiCalcGetResult (1000000)

5分計算して結果を返す

PaiCalcMockGetResult (1000000)

てきとーな値ですぐに結果を返す返す値はテストコードから制御可能

テストコードホンモノコード

Calc::Calc(){ // コンストラクタ#ifndef UNITTEST

m_paiCalc = new PaiCalc(); // ホンモノ#else

m_paiCalc = new PaiCalcMock(); // ニセモノ#endif}

Page 9: プログラマのためのテスト2

メソッド呼び出しの確認

Foo(テスト対象モジュール)

PaiCalcMock

テストコード

g_is_ PaiCalcMock_GetResult _Invoked = false;foo.DoCalcPai(); // テスト実行CPPUNIT_ASSERT_EQUAL((bool)true,

g_is_ PaiCalcMock_GetResult _Invoked);

Foo::DoCalcPai() {m_paiCalc->GetResult (1000000);

// Test時はMockが呼び出される}

PaiCalcMock::GetResult (1000000){g_is_ PaiCalcMock_GetResult _Invoked = true// メソッド内でグローバル変数に値代入

}

テスト対象モジュールFoo::DoCalcPai()内でPaiCalc::GetResult()が呼び出されるか確認するテスト

テスタで導通チェックをするようなものです。

Page 10: プログラマのためのテスト2

UnitTest実装時のCheckList

• 全てのパスを通過• 全ての環境(変数やメモリ状態)の組み合わせ• 全ての異常系• 全ての状態遷移• 例外送出• NULLチェック• 境界値• 変換 -逆変換 - diff• Initializeが呼ばれる前に全メソッドを呼ぶテスト• Finalizeが呼ばれた後に全メソッドを呼ぶテスト• Initializeを呼んだ後にInitializeを呼ぶテスト

Page 11: プログラマのためのテスト2

Test First DevelopmentTest Driven Development本番コードを書く前にユニットテストコードを書く→ テストコードに駆動される開発

テストコードで境界値や全てのケースを網羅しようという意識になる

仕様が固まっていない部分は固めようとする

テスト容易な設計を考慮するようになる

テストを満たすように、突貫コードを書く

仕様破綻がないかチェックする

エラーケースをケアするコードを書く

リファクタリング

Page 12: プログラマのためのテスト2

テスト容易な設計とはなんだ?

Mockオブジェクトをどうやって置き換えるか?

• #ifdef (前述)

• DI(Dependency Injection:依存性の注入)

(例: GoFのストラテジーdesign patternを利用)

Foo::Foo(){ // コンストラクタ#ifndef UNITTEST

m_bar = new Real(); // ホンモノ#else

m_bar = new Mock(); // ニセモノ#endif}

Page 13: プログラマのためのテスト2

Dependency InjectionでReal/Mock切りかえ

InterfaceFooCalc()純粋仮想関数

RealFooCalc()を実装

MockFooCalc()を実装

RealBarSetFoo(InterfaceFoo* foo){

m_foo = foo;}

m_foo->Calc();// 本番時はRealFoo// テスト時はMockFoo

本番コードRealMain(){

RealBar bar;bar.SetFoo(new RealFoo());...

}

テストコードTestMain(){

RealBar bar;bar.SetFoo(new McokFoo());...

}

本番コードにテストコードが含まれないスッキリした設計・実装!

Page 14: プログラマのためのテスト2

テスト容易な設計とはなんだ?

• 適切なモジュール化

テストを書きづらいコードはかかない

こんなメソッドは嫌だ

• 仕事内容が多岐

• 1000行

Page 15: プログラマのためのテスト2

==== DI とMock Object を利用した UnitTest Pattern ====

■本番コードBar class は Foo class の演算結果を利用して処理をします。

class IFoo : public IFoo {int CalcFoo();

}

class RealFoo {int CalcFoo() {return 3;

}}

class RealBar{

IFoo *m_foo;

void SetFoo(IFoo* foo) {m_foo = foo;

}

// CalcFoo() の結果を 2倍するint CalcBar() {return 2 * m_foo->CalcFoo();

}}

void main(){RealFoo foo;RealBar bar;

// Setter Injection. bar.SetFoo(&foo);printf("CalcBar = %d¥n", bar.CalcBar());

}

■テストコード// 実際はMockppを利用して、より柔軟にテスト条件に応じた値を返す Mock を作るclass MockFoo : public IFoo {

int m_calc_value;

void SetCalcFoo(int value) {m_calc_value = value;

}

int CalcFoo() {return m_calc_value;

}}

void test_main(){

MockFoo foo;RealBar bar;

bar.SetFoo(&foo);

foo.SetCalcValue(1);ASSERT(bar.CalcBar() == 2);

foo.SetCalcValue(2);ASSERT(bar.CalcBar() == 4);

}

Mockppを利用することで尐し楽をして Mock class を作成できるようになります。

このようにしてMock Class を一度作っておけば、テスト本体だけでテスト条件を記述できるようになり、テスト時も本番時もまったく同じオブジェクトを利用できるようになります。# リビルド不要、本番とテストでコードの食い違いが起こりえない

また、必ず依存関係の間にインタフェースが挟まるので、それぞれのクラスを別々に開発することも容易になります。# 依存先の実体がなくてもコンパイルできるし、Mock を入れて# 逐次テストができる

つまり、DI + MockObjectで UnitTestというのは、このようなメリットを得るために、本番コードのアーキテクチャを変えるというextreme なテスト手法です。

サンプルコード

(c)ひぐまさん多謝!!

Page 16: プログラマのためのテスト2

Javaの世界は一歩先を行く

• Seasar2 (DIコンテナFrameWork)

コンテナの中身を外部ファイルで自由に操作.xml

aのインスタンスは○○bのインスタンスは△△cのインスタンスは□□...

DIコンテナ

コード使用したいコンテナ(クラス)の名前だけしっていればいい。

そのインスタンスが何かは意識しなくて良い。

Real.xmlaのインスタンスはRealAbのインスタンスはRealBcのインスタンスはRealC

Mock.xmlaのインスタンスはMockAbのインスタンスはMockBcのインスタンスはMockC

TestA.xmlaのインスタンスはRealAbのインスタンスはMockBcのインスタンスはMockC

本番・テストごとにxmlを切り替えてReal/Mockを指定する。

Page 17: プログラマのためのテスト2

UnitTestPatterns

どのようなUnitTestを書け

ばいいのかを形式化し、テストファーストをプログラマの習慣にすることを目指すwebページ

http://www.marcclifton.com/tabid/87/Default.aspx (英語)

http://igarashikuniaki.net/fswiki/wiki.cgi?page=UnitTestPatterns

(日本語翻訳途中)

テストしやすい設計をするために、テストファースト。テストを設計前に書けば、テストまで考慮した設計が可能。

■The Simple-Test Pattern(シンプルテストパターン)■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(ロールバックパターン)■Collection Management Patterns(コレクション管理パターン)■The Collection-Order Pattern(コレクションオーダリングパターン)■The Enumeration Pattern(列挙パターン)■The Collection-Constraint Pattern(コレクション制約パターン)■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(ビットエラーシミュレーションパターン)

Page 18: プログラマのためのテスト2

UnitTestPatterns

Page 19: プログラマのためのテスト2

テストを育てる

テストが書かれている範囲はコントロール下にあるということ。

テストを追加していくということは、テストにより動作保証される範囲を増やし、リスク把握できる部分を広げていくこと。

OK

OK

OK

バグの場所が分かるドラゴンレーダーが欲しいです。

現在、テストがカバーしているコードがどの辺なのか見える化してくれるツールがあるといいですね。

※NGでひっかかった回数

を記録するような仕組みがあると面白いね。

Page 20: プログラマのためのテスト2

UnitTestツール

xUnit•JUnit (JAVA)•NUnit (C#)•CppUnit (C++)

どの環境でもユニットテストツールは大抵あります。

CppUnit動作環境VC++, g++(GCC)など。

Page 21: プログラマのためのテスト2

UnitTestのまとめ

• テストにより駆動される開発

–気づけなかったものに気づく

–発想を広げる

• テスト容易性まで考慮した設計

–モジュール化重要

• テストを用いたリスク解析テスト△はOKなので、△の範囲は問題無し

テスト□がNGだったので、□の範囲内に問題有り

Page 22: プログラマのためのテスト2

参考文献

• はじめて学ぶソフトウェアのテスト技法著:リー・コープランド 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-3SONYのテストエンジニアさんの著書。実際の経験に基づくノウハウが多数。

• WEB+DB PRESS vol.35著:和田卓人 ISBN:4-7741-2931-3Java DIコンテナSeasar2を使ったユニットテストの記事など入門記事。

Page 23: プログラマのためのテスト2

おしまい

質問があればお願いします。

Page 24: プログラマのためのテスト2

テストのゴールはいつだ?

テストにかかった費用+

サポートにかかる費用+

メンテナンスにかかる費用

が最小値になるとき