テスト駆動開発ハンズオン後編

1,643 views

Published on

jissen.swtest

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,643
On SlideShare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
0
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

テスト駆動開発ハンズオン後編

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

×