Your SlideShare is downloading. ×
プログラマのためのテスト2
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

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

3,111
views

Published on


0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
3,111
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
23
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. プログラマのためのテスト Kuniaki IGARASHI http://igarashikuniaki.net/tdiary/ igarashikuniaki@gmail.com 2007.4.24
  • 2. UnitTest入門と UnitTestデザインパターン
  • 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にすれば良い。 } }
  • 7. 全パス・全状態の網羅 直交表や全ペアといったテスト技法が助けになります →参考文献:「はじめて学ぶソフトウェアのテスト技法」 直行表
  • 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 本番コードを書く前にユニットテストコードを書く → テストコードに駆動される開発 テストコードで境界値や全てのケースを網羅しようという意識になる 仕様が固まっていない部分は固めようとする テスト容易な設計を考慮するようになる テストを満たすように、突貫コードを書く 仕様破綻がないかチェックする エラーケースをケアするコードを書く リファクタリング
  • 12. テスト容易な設計とはなんだ? Mockオブジェクトをどうやって置き換えるか? • #ifdef (前述) Foo::Foo(){ // コンストラクタ #ifndef UNITTEST m_bar = new Real(); // ホンモノ #else m_bar = new Mock(); // ニセモノ #endif } • DI(Dependency Injection:依存性の注入) (例: GoFのストラテジーdesign patternを利用)
  • 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()); ... ... } } 本番コードにテストコードが含まれないスッキリした設計・実装!
  • 14. テスト容易な設計とはなんだ? • 適切なモジュール化 テストを書きづらいコードはかかない こんなメソッドは嫌だ • 仕事内容が多岐 • 1000行
  • 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(ビットエラーシミュレーションパターン) テストしやすい設計をするために、テストファースト。 テストを設計前に書けば、テストまで考慮した設計が可能。
  • 18. UnitTestPatterns
  • 19. テストを育てる テストが書かれている範囲はコントロール 下にあるということ。 テストを追加していくということは、 テストにより動作保証される範囲を増やし、 リスク把握できる部分を広げていくこと。 ※NGでひっかかった回数 OK を記録するような仕組みが あると面白いね。 OK OK 現在、テストがカバーしているコードがどの辺なのか バグの場所が分かる 見える化してくれるツールがあるといいですね。 ドラゴンレーダーが欲しいです。
  • 20. UnitTestツール xUnit •JUnit (JAVA) •NUnit (C#) •CppUnit (C++) どの環境でもユニットテストツールは大抵あります。 CppUnit動作環境 VC++, g++(GCC)など。
  • 21. UnitTestのまとめ • テストにより駆動される開発 – 気づけなかったものに気づく – 発想を広げる • テスト容易性まで考慮した設計 – モジュール化重要 • テストを用いたリスク解析 テスト△はOKなので、△の範囲は問題無し テスト□がNGだったので、□の範囲内に問題有り
  • 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を使ったユニットテストの記事など入門記事。
  • 23. おしまい 質問があればお願いします。
  • 24. テストのゴールはいつだ? テストにかかった費用 + サポートにかかる費用 + メンテナンスにかかる費用 が最小値になるとき