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

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

on

  • 4,906 views

 

Statistics

Views

Total Views
4,906
Views on SlideShare
4,897
Embed Views
9

Actions

Likes
0
Downloads
23
Comments
0

2 Embeds 9

http://www.slideshare.net 8
http://webcache.googleusercontent.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

  • プログラマのためのテスト Kuniaki IGARASHI http://igarashikuniaki.net/tdiary/ igarashikuniaki@gmail.com 2007.4.24
  • UnitTest入門と UnitTestデザインパターン
  • 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 テスト失敗
  • UnitTestの基本方針 • 確実にNGになるケースをまず書こう テストが実行されていることを確かめましょう Red テスト失敗 例:CPPUNIT_FAIL(quot;messagequot;); /// 必ず失敗し、messageを表示するテスト テストできたらCPPUNIT_FAILを外してGreenにします。 Green テスト成功 • テストを書こう 新規実装であれば仕様をコードに落としながら。 既存実装の修正 1. 既存のコードに合致するテストを書く。 Green テスト成功 2. 修正後の仕様を満たすテストを書く。 Red テスト失敗 • 本番コードを書こう テストがGreenになるように本番コードを書いていきます。 Green テスト成功 テストで仕様抜け、テストコードパス抜けを見つけたらテストを書き足します。 • リファクタリング 必要があれば、テストを成功のまま保ちつつリファクタリングを行います。 Green テスト成功
  • テスト基本形 // テスト対象メソッド 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に伝えるために、明示的にキャストする必要があります。
  • 例外送出のテスト /// テスト対象 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にすれば良い。 } }
  • 全パス・全状態の網羅 直交表や全ペアといったテスト技法が助けになります →参考文献:「はじめて学ぶソフトウェアのテスト技法」 直行表
  • 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
  • メソッド呼び出しの確認 テスト対象モジュール テストコード 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 テスタで導通チェックを // メソッド内でグローバル変数に値代入 するようなものです。 }
  • UnitTest実装時のCheckList • 全てのパスを通過 • 全ての環境(変数やメモリ状態)の組み合わせ • 全ての異常系 • 全ての状態遷移 • 例外送出 • NULLチェック • 境界値 • 変換 - 逆変換 - diff • Initializeが呼ばれる前に全メソッドを呼ぶテスト • Finalizeが呼ばれた後に全メソッドを呼ぶテスト • Initializeを呼んだ後にInitializeを呼ぶテスト
  • Test First Development Test Driven Development 本番コードを書く前にユニットテストコードを書く → テストコードに駆動される開発 テストコードで境界値や全てのケースを網羅しようという意識になる 仕様が固まっていない部分は固めようとする テスト容易な設計を考慮するようになる テストを満たすように、突貫コードを書く 仕様破綻がないかチェックする エラーケースをケアするコードを書く リファクタリング
  • テスト容易な設計とはなんだ? Mockオブジェクトをどうやって置き換えるか? • #ifdef (前述) Foo::Foo(){ // コンストラクタ #ifndef UNITTEST m_bar = new Real(); // ホンモノ #else m_bar = new Mock(); // ニセモノ #endif } • DI(Dependency Injection:依存性の注入) (例: GoFのストラテジーdesign patternを利用)
  • 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()); ... ... } } 本番コードにテストコードが含まれないスッキリした設計・実装!
  • テスト容易な設計とはなんだ? • 適切なモジュール化 テストを書きづらいコードはかかない こんなメソッドは嫌だ • 仕事内容が多岐 • 1000行
  • サンプルコード ==== 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)ひぐまさん 多謝!!
  • 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
  • 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(ビットエラーシミュレーションパターン) テストしやすい設計をするために、テストファースト。 テストを設計前に書けば、テストまで考慮した設計が可能。
  • UnitTestPatterns
  • テストを育てる テストが書かれている範囲はコントロール 下にあるということ。 テストを追加していくということは、 テストにより動作保証される範囲を増やし、 リスク把握できる部分を広げていくこと。 ※NGでひっかかった回数 OK を記録するような仕組みが あると面白いね。 OK OK 現在、テストがカバーしているコードがどの辺なのか バグの場所が分かる 見える化してくれるツールがあるといいですね。 ドラゴンレーダーが欲しいです。
  • UnitTestツール xUnit •JUnit (JAVA) •NUnit (C#) •CppUnit (C++) どの環境でもユニットテストツールは大抵あります。 CppUnit動作環境 VC++, g++(GCC)など。
  • UnitTestのまとめ • テストにより駆動される開発 – 気づけなかったものに気づく – 発想を広げる • テスト容易性まで考慮した設計 – モジュール化重要 • テストを用いたリスク解析 テスト△はOKなので、△の範囲は問題無し テスト□がNGだったので、□の範囲内に問題有り
  • 参考文献 • はじめて学ぶソフトウェアのテスト技法 著:リー・コープランド 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を使ったユニットテストの記事など入門記事。
  • おしまい 質問があればお願いします。
  • テストのゴールはいつだ? テストにかかった費用 + サポートにかかる費用 + メンテナンスにかかる費用 が最小値になるとき