Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
テスト駆動開発入門 ハンズオン講座(後編) Jissen.swtest 2011/6/11 井芹
Slide 2
Slide 2 text
おさらい 1. 最初にテストを書いて実行 (RED) 2. テストをパスするまでコード を実装(GREEN) 3. コードをきれいにする (REFACTOR) これを繰り返しインクリメンタルに実装を進める RED GREEN Refactor
Slide 3
Slide 3 text
おさらい(壇上実践) • うるう年判定関数(グレゴリオ暦) – 西暦年が4で割り切れる年は閏年 – ただし、西暦年が100で割り切れる年は平年 – ただし、西暦年が400で割り切れる年は閏年
Slide 4
Slide 4 text
今回の概要 • TDDの周辺分野について – 今回は「TDDの効果拡大」をテーマに考え方やア プローチを紹介します
Slide 5
Slide 5 text
概要 • テストコードの運用 • テストコード運用上の問題 • テストによるコードの資産化 • テストコードのドキュメント化 • レガシーコードでのTDD
Slide 6
Slide 6 text
TDDの テストコードの運用
Slide 7
Slide 7 text
テストコードの運用 • TDDでのテストの持続的効果 – 単体テスト容易性の確保 – 実装仕様の確保(Test as Documantation) – 自動化された回帰テスト環境の構築 • 継続運用の課題 – 単体テストとしての整理(前篇) – 運用環境の構築 – テストコードのメンテナンス
Slide 8
Slide 8 text
テストコードの運用フェーズ • 実装作業中、継続的かつこまめに – 常時実行される回帰テスト – 何度も何度も実行できるように自動化が協力に 推進される
Slide 9
Slide 9 text
継続的インテグレーション(CI) • 自動化されたインテグレーションを継続的に実行 1. インテグレーションを統合・自動化 • 単体テストを統合 2. インテグレーションの実行を自動化 • インテグレーション用サーバを用意(Hudson有名) • 統合したインテグレーションを継続的に自動実行する – コミット時等。ナイトビルドよりもっと頻繁に – 継続的ですばやいフィードバックを実現
Slide 10
Slide 10 text
単体テスト運用環境 作業用PC 構成管理サーバ DB関連テスト ストレステスト 規格バリデーション コミット 更新・ 自動実行 更新・ 自動実行 更新・ 自動実行 制約のあるテストを分散運用することで SlowTest問題や高コストを回避する
Slide 11
Slide 11 text
実行の分散方針 • TDDのテストは軽快に • 「実行に0.1sもかかる単体テストは、遅い単体テストである」 (レガシーコード改善ガイド) • Slow Test問題:時間のかかるテストのせいでTDDの効率が落ちる • 重い/制約のあるテストはサーバ側、自動化へ – 時間がかかる/特定環境依存/高コスト • TDDでは必要なテストのみ実行すればよい – 全テスト実行はサーバ側へ
Slide 12
Slide 12 text
テストコードの運用まとめ • TDDのテストの継続的効果 • 単体テスト運用環境の軸 • 構成管理システムと単体テスト • 継続的インテグレーション • テスト負荷の分散
Slide 13
Slide 13 text
テストコード運用上の問題
Slide 14
Slide 14 text
テストコード運用上の問題 • TDDのテストは一般的に継続利用されるため: – メンテナンスしないと品質悪化 – 保守性が悪いとレガシー”テスト”コードとして開発 の足を引っ張ってくる
Slide 15
Slide 15 text
Fragile Test • 製品コードの変更に弱いテスト – 些細なコード変更で大量のテストが失敗 – 仕様や機能とは無関係な変更でテストが失敗 • リファクタリングやCover & Modifyのコストを 増大させる(TDDはリファクタリングを推進するはずなのに・・・) • TDDでのエントロピー問題
Slide 16
Slide 16 text
Fragile Test void test_1() { Hoge hoge = new Hoge(…); … } void test_2() { Hoge hoge = new Hoge(…); … } void test_3() { Hoge hoge = new Hoge(…); … } …. void test_100() { Hoge hoge = new Hoge(…); … } void test_101() { Hoge hoge = new Hoge(…); … } void test_102() { Hoge hoge = new Hoge(…); … } テストメソッドがHogeクラスに過依存 Hogeクラスのコンストラクタが変更されたら大量のテスト失敗が発生
Slide 17
Slide 17 text
Fragile Test対策 • 3つの方針 – テスト対象への過依存を避ける – テストの変更可能性に基づいてテストを設計する – テストの保守性に基づいてテストコードを設計する
Slide 18
Slide 18 text
テストのテスト対象への 過依存を避ける • テストにおけるテスト対象への4つの過依存 – Interfaceへの過依存 – Behaviorへの過依存 – Dataへの過依存 – Contextへの過依存
Slide 19
Slide 19 text
テストのテスト対象への 過依存を避ける • 対策:過剰な依存部を取り除く – 重複するテスト対象呼び出しはないか? →重複を関数やクラスでまとめる – テスト対象の内部に過剰に依存していないか (リフレクションなどで無理に内部にアクセスしていないか) →パブリックなインターフェースでアクセスする →問題があればモジュール設計を再検討する – 一部の過依存がまわりを巻き込んでいないか →依存部をラッピングする
Slide 20
Slide 20 text
対策例:Creation Method public void testHoge_first() { Piyo piyo = new Piyo(1, 2, 3); ... } public void testHoge_second() { Piyo piyo = new Piyo(4, 5, 6); ... }
Slide 21
Slide 21 text
Creation Method public void testHoge_first() { Piyo piyo = createUniquePiyo(); ... } public void testHoge_second() { Piyo piyo = createUniquePiyo(); ... } public Piyo createUniquePiyo() { return new Piyo(generateValue(), generateValue(), generateValue()); } 「Customer」という製品コードのへの依存部が削減された
Slide 22
Slide 22 text
テストの変更可能性に 基づいてテストを設計する • 単体テスト設計のアプローチ – 仕様ベース • 仕様分析によって、仕様保障を目的とするテストを設 計する – 構造ベース • 構造分析によって、構造を網羅するようにテストを設 計する – 経験ベース • エラー推測、経験を元にテストを設計する
Slide 23
Slide 23 text
単体テスト設計の アプローチの扱い • 仕様ベースのテスト設計を重視 不足を構造ベースのテスト設計で補う – 構造はリファクタリングで変化する • 構造への過依存はFragile Testとなり制約 に – 構造ベースで網羅的に設計したテストはナマモノ
Slide 24
Slide 24 text
アプローチの扱い int hoge(int input) { return input * 2; } Int型は仕様か、構造的な制約か
Slide 25
Slide 25 text
構造の変更可能性 • 構造ベースでも変更可能性に程度がある – 「仕様としての構造」 – 暫定実装 構造の変更可能性 大 小 暫定実装 内部メンバ ライブラリ モジュール等 のインタフェース 規格化された構造
Slide 26
Slide 26 text
構造の変更可能性 • 構造の変更可能性の2軸 – 時間軸方向の変更可能性 – 構造軸方向の変更可能性 • Ex)時間軸方向の変更可能性 • 短期(一時的) – 暫定使用、実装過程での仮実装など • 中期 – 機能、各部モジュールなど • 長期 – 公的規格、標準規格、根幹的なアーキテクチャなど
Slide 27
Slide 27 text
時間軸方向の変更可能性 • 長期的に安定するものから網羅性を高める – Ex)規格仕様は作りこんでV&V手段として使えるよ うにする • 「SQLiteのテストコードは4567万8000行。本体のコード は6万7000行」 – 一時実装のテストコードも一時実装 • 作りこむとFragile Testの原因となる
Slide 28
Slide 28 text
アーキテクチャ設計による 変更可能性の管理 • TDDはアーキテクチャ設計で支える – 外部設計 • テスト容易性を阻害するDOCを局所化する – 内部設計 • テスト容易性を確保する • TDDのインプットとなる各モジュールの仕様を定義する • 変更可能性対策をアーキテクチャに盛り込む • Ex)タイミング設計:単体テストを阻害しないように
Slide 29
Slide 29 text
テストコード運用上の問題 • Fragile Test • 変更可能性の観点 • TDDとアーキテクチャ設計
Slide 30
Slide 30 text
テストによるコードの資産化
Slide 31
Slide 31 text
TDDによる実装アプローチの変化 • TDDで書かれたコードは全体にわたって – 単体テストが確保される – 単体テスト容易性が確保される • 自動化された回帰テスト環境が Cover & Modifyのアプローチを実現する
Slide 32
Slide 32 text
Cover & Modify • 手順 – 1 パスする回帰テストを確保する – 2 テストが成功する状態を保ちつつ、コードを変 更する
Slide 33
Slide 33 text
Edit & Pray • 「変更して動かしてみる」 • Cover & Modifyの対比となる実装アプローチ 世の中で一般的 • 手順 – 1 コードを変更する – 2 うまく動くように祈る
Slide 34
Slide 34 text
Cover & Modify • 「テストで保護(Cover)して修正(Modify)する」 • テストで修正・変更の影響範囲を絞り込む – コードの変更作業が安全に – 保守開発で推奨される実装アプローチ
Slide 35
Slide 35 text
Cover & Modify[実演] • Case 4 で1234を返す
Slide 36
Slide 36 text
Cover & Modify例 • リファクタリング – 1 機能を保護するテストを確保する – 2 テストがパスする状態を維持しながら、コード を変更する
Slide 37
Slide 37 text
Cover & Modify例 • TDDによる機能変更 – 1 変更対象の回帰テストを書く – 2 TDDのサイクルへ • 変更機能のテストを書く(Red) • 実装する(Green)
Slide 38
Slide 38 text
Cover & Modify[課題] • うるう年判定(前回の課題) • 追加仕様: – 負の値が入力された場合はfalseを返す
Slide 39
Slide 39 text
TDDとCover & Modify • TDDとCover & Modifyは親和性が高い – コードをテストに対して最適化されるため、コード のテスト容易性が高まる • テストで保護しやすくなる – テストコードが確保される – そもそもCover & ModifyがTDDのようなもの
Slide 40
Slide 40 text
コードの資産化 • TDDはコード資産化効果を促進する – TDDによりCover & Modifyを実現 • コードの保守性が大きく改善 • コードの資産化が促進される
Slide 41
Slide 41 text
コードの資産化まとめ • Edit & Pray • Cover & Modify – リファクタリング – 機能追加 • TDDのコード資産化効果
Slide 42
Slide 42 text
テストコードのドキュメント化
Slide 43
Slide 43 text
テストコードのドキュメント化 • 「テストコード=実装仕様」という思想 – Test as Documantation – TDDでのテスト設計で目指される理想の1つ – テストコードを動く実装仕様書として活用する – バグ出し、品質保証ではなく、仕様記述という目 的でテスト設計を行う
Slide 44
Slide 44 text
Example Driven Development • EDD。実例駆動開発。TDDの1種 • EDDでのテスト=テスト対象の使い方の実例 • テストファーストが苦手な人のためのプラク ティスとして有効 • 手順: – 最初に実装コードの使い方の実例を考える – 実例をテストで表現する – 以後はTDDのサイクルへ
Slide 45
Slide 45 text
Example Driven Development [課題] • 演算器 – 整数を入力できる – 入力した整数の計算結果を出力できる
Slide 46
Slide 46 text
Behavior Driven Development • BDD。ビヘイビア駆動開発。TDDの派生物 • BDDのテスト=テスト対象のふるまい仕様 – ふるまい仕様でテストを語れるようにする →テストファーストにユーザやドメイン専門家を巻 き込めるようにする。それらからの要求取得→開 発リリースのサイクルを高速化する
Slide 47
Slide 47 text
BDDフレームワーク • 自動テストフレームワークの一種 • xUnitの設計に基づくものが多いが、命名や 構造がBDDの思想に合わされている • 仕様記述のためのDSLを提供するものもある
Slide 48
Slide 48 text
BDDフレームワーク • JUnit – assertEquals("hoge name", hoge.getName()); – assertEquals(16, hoge.getAge()); • JDave(BDDフレームワーク) – specify(hoge.getName(), must.equal("hoge name")); – specify(hoge.getAge(), must.equal(16));
Slide 49
Slide 49 text
Behavior Driven Development [課題] • 演算ライブラリ
Slide 50
Slide 50 text
ドキュメントとしてのテストコード • Characterization test • Test All-at-Onceによるフィーチャ分析
Slide 51
Slide 51 text
Characterization test(仕様化テスト) • コードのふるまいや用例を、パスするテストと して表現する – 仕様表現、コードの理解が目的 – 満たすべき仕様として回帰テストとして作用する
Slide 52
Slide 52 text
Characterization test(仕様化テスト) • Characterization testによるコード解析 – 1 適宜の入出力で解析対象のテストを書く(最 初は失敗させる) – 2 テストがパスするまで入出力の値を調整する • テスト失敗したら期待値を実行値に置き換える • 例外が発生したら例外テストに置き換える – 目的が達成されるまでこれを繰り返し、テストを 継ぎ足していく
Slide 53
Slide 53 text
Characterization Test[課題] • 前回の課題のCharacterization Testを用意す る
Slide 54
Slide 54 text
Test All-at-Onceによるフィーチャ分析 • 実装対象に求められるフィーチャをテストメ ソッドとしてすべて洗い出す – テストメソッドはスケルトン。かつignore設定 – TDDの中で1つ1つignore指定除去&スケルトン実 装をすすめ、最終的にTDDのテストコードがすべ てのスケルトンを内包するようにする – 最終的に、洗い出したスケルトンセットが整合性 の取れたフィーチャ実装となる
Slide 55
Slide 55 text
Test All-at-Onceによるフィーチャ分析 [実演] • うるう年判定関数で実施
Slide 56
Slide 56 text
テストコードのドキュメント化まとめ • TDDとテストコードのドキュメント化 • EDD/BDD • Characterlization Test • Test All-at-Onceによるフィーチャ分析
Slide 57
Slide 57 text
レガシーコードでのTDD
Slide 58
Slide 58 text
レガシーコードでのTDD • 単体テスト容易性が低いコードではTDD実行 前にコードの修正が必要 – Cover & Modifyから外れた修正 – コードを汚くしてしまう修正
Slide 59
Slide 59 text
通常のTDD 1. テストを書く 2. テストをパスするコードを書く 3. リファクタリングする
Slide 60
Slide 60 text
レガシーコードでのTDD 1. (よく考える) 2. 依存性を排除する 1. 変更点を洗い出す 2. テストを書く場所を見つける 3. 依存性を取り除く 3. テストを書く 4. テストをパスするコードを書く 5. リファクタリングする
Slide 61
Slide 61 text
依存性の排除 • リスクを許容するとしても、リスクを抑える – 低リスクなツール支援が使えるなら活用 – 低リスクな変更手段があるなら活用 • private→protected • finalを削除する – 上位のテストで補完 • リスクにある変更は慎重に考える – “よく考える” – スクラッチリファクタリングなどでイメージを固める
Slide 62
Slide 62 text
スクラッチリファクタリング • テストや制約を一切無視して自由にリファクタ リングする – テストは記述しない – リファクタリング結果は使い捨て • 目指している結果が妥当かどうか、実際に コードを書いて評価する
Slide 63
Slide 63 text
依存性の排除例 (コンストラクタのパラメータ化) public Hoge { private MissileCtrl missileCtrl; public Hoge() { missileCtrl = new MissileCtrl (): } public void piyo() { …. missileCtrl.発射(); …. missileCtrl.発射2(); …. } …. }
Slide 64
Slide 64 text
依存性の排除 (コンストラクタのパラメータ化) public Hoge { private MissileCtrl missileCtrl; public Hoge(MissileCtrl missileCtrl) { this.missileCtrl = missileCtrl; } public Hoge() { thils(new MissaileCtlr()); } public void piyo() { missileCtrl.発射(); } …. } Hoge(new FakeMissileCtrl());
Slide 65
Slide 65 text
レガシーコードでのTDDまとめ • レガシーコードでのTDDのステップ • 依存性の排除 – コンストラクタのパラメータ化 • スクラッチリファクタリング
Slide 66
Slide 66 text
レガシーコードでのTDD[課題] • 前回課題のBookList改変版
Slide 67
Slide 67 text
最後のまとめ • 取りあえず覚えてもらいたい要点 – 継続的インテグレーション – Fragile Test – Cover & Modify – レガシーコードでのTDD – テストコード=ドキュメントとする考え方