Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDDのフローの本質と恩恵を引き出すための考え方

SYM
September 06, 2024

 TDDのフローの本質と恩恵を引き出すための考え方

TDD(テスト駆動開発)のフローの本質と恩恵を引き出すための考え方(スライド50枚+α)
※社内勉強のために作ったベース版を少し手直しした物

3年位、成長期フェーズの日々の開発(既存改修)の中で、薄く向き合い続け
気付けば形になっていた実践Tipsのようなものをまとめました。

「質とスピードの両立」をある程度実現するベースとなっているものがTDDの考え方なため、
TDDをどう捉え、普段の開発(既存改修のシーン)にどう落とし込んでいるのか、何を意識しているのか+αな内容

SYM

September 06, 2024
Tweet

More Decks by SYM

Other Decks in Programming

Transcript

  1. 目的/背景 • 前提として「TDDやりましょう」と言うためのものではない ◦ 自身がTDDの通りにやってないのでTDDやりましょうなんて口が裂けても言えない笑 2 • その一助になればと思い、自分自身という1具体ケースの紹介 ◦ 自身がTDDをどう捉え、普段の開発(既存改修のシーン)に

    どう落とし込んでいるのか、何を意識しているのか+αな内容 ▪ 2~3年位かけて普段の開発の中で薄く向き合い続けているうちに 気付けば形になっていた実践Tipsのような物 • が、TDDの考え方を理解し、実践することができれば 「質とスピードの両立」を実現する土台を築く近道になるはず ◦ 自身がこれをある程度実現するためのベースとなっているのがTDDの考え方のため
  2. Contents 1. TDDとは  ※ Raccoon Tech Connect #4 (3分LT) の内容改訂版 ◦

    TDDのフローの説明とその本質(自分なりの捉え方) ◦ 「振る舞い」と「動作」について 2. 補足:TDDのフローがもたらす恩恵と与える機会を考えてみた 3. 実践のために必要なユニットテストに対する考え方 ◦ どんなテストを意識するのが良いか ◦ 最たる例 4. 普段の開発(既存改修)でTDDの恩恵を引き出すための考え方 ◦ 「既存改修」でTDDがやりづらいと感じる理由 ◦ 「既存改修」の修正は大きく2種類に分類できる ▪ それぞれへのアプローチ ▪ TDDがやりづらいへの克服の糸口 ▪ 実践の型(自己流) ▪ 「テストから書く」に拘らなくて良い理由 ◦ テストファーストの本質 5. おまけ:アーキテクチャから見る「振る舞い」「動作」の位置づけ 3
  3. TDD(テスト駆動開発)とは 5 リスト ➡ Red ➡ Green ➡ リファクタ 1.

    テストリスト(Todoリスト)を書く 2. テストを1つ書く(Red) 3. テストを成功させる(Green) ◦ ※ 新しいテストの必要性に気づいたらリストに追加 4. 必要に応じてリファクタリングを行う 5. テストリストが空になるまでステップ2に戻って繰り返す
  4. TDDの要素 -「リスト」 6 • (テスト) リストの位置づけ t-wadaさんの記事では… ◦ 『あるシステムに振る舞いの変更が望まれているとき  新しい振る舞いにおいて期待される動作をリストアップ する』

    ▪ 振る舞いの分析(変更後の振る舞いが満たすべき様々な動作を 網羅的に考える) • 大きなタスク (ゴール) を多数の小さなタスク (ゴール) に分解 • ゴール(振る舞いの実現)への道筋を定めていく • 1つずつ順番に取り組む (目の前の1つに集中する) 準備 タスク 最終 ゴール タスク タスク タスク タスク タスク タスク
  5. TDDの要素 -「リスト」: 振る舞い、動作 is 何? 7 (外部)仕様 = 何をするか(要件) •

    ユーザや外部システムから見たシステム の全体の動き 振る舞い = (要件を)どう実現するか • 特定の条件や入力に対するシステム の反応、開発者視点での動き • (特定のケースにてシステムが) 〇〇の入力に対して ✕✕を出力する 動作 • システム内部の処理やアクション、 実行されるロジック • 具体イメージ:小さな関数/メソッド 外部仕様 動作1 動作2 動作3 振る舞い 動作1 動作2 動作3 振る舞い 動作1 動作2 動作3 振る舞い システム
  6. TDDの要素 -「リスト」: 振る舞い、動作 is 何? 8 動作: • 指定された条件に合致する データをCSV形式で返す

    振る舞い: 1. 指定された条件に基づいて データを取得する 2. データをフォーマットする 3. ファイルデータに変換する 要望:CSV形式でデータをエクス ポートしたい public class ExportService { public byte[] exportData(ExportRequest request) { List<Data> data = fetchData(request); String formattedData = formatData(data); byte[] fileData = convertFile(formattedData); return fileData; } List<Data> fetchData(ExportRequest req) { // 動作1: 指定された条件に基づいてデータを取得 } String formatData(List<Data> data) { // 動作2: データをフォーマットする(CSV形式) } byte[] convertFile(String formattedData) { // 動作3: ファイルデータ(バイト列)に変換 } } イメージ例
  7. TDDの要素 -「Red➡Green」 • Red ➡ Green の位置づけ t-wadaさんの記事では… ◦ テストを「ひとつだけ」書く ➡

    テストを成功させる ▪ テストを成功させる過程にリファクタを混ぜ込まない ▪ テストコードを書いている途中に設計判断が始まるが、  それは主にインターフェイスの設計判断 ※ まずは動かし、それから正しくする。このやり方が結局は脳にも優しい 9 • 1つの小さな「動作」の実装(1タスク)のみに集中する ◦ ※「動作」 = 関数(メソッド) ◦ インターフェースの設計判断 ≒ 関数の引数/戻り値の判断 ◦ テストから書く=1タスク (ゴール) の達成条件を明確化
  8. TDDの要素 -「Red➡Green」 10 • 各動作に対して、テスト をしながら進めていく • 段階的に振る舞いを組み 上げつつテストする public

    class ExportService { public byte[] exportData(ExportRequest request) { List<Data> data = fetchData(request); String formattedData = formatData(data); byte[] fileData = convertData(formattedData); return fileData; } List<Data> fetchData(ExportRequest req) { // 動作1: 指定された条件に基づいてデータを取得 } String formatData(List<Data> data) { // 動作2: データをフォーマット(CSV) } byte[] convertData(String formattedData) { // 動作3: データをバイト配列に変換 } } testConvertData() testFormattedData() testFetchData() testExportData()
  9. TDDの要素 -「リファクタ」 11 • リファクタの位置づけ t-wada さんの記事では… ◦ 『ここまで来て、ようやく実装の設計判断を行える 』 ▪

    『内部実装がどうあるべきかの判断は後から確保できる』  → 内部の設計を(行えるようになったら)行う工程 ※ この段階で必要以上にリファクタリングしてしまわないこと • (材料が揃ってから) 全体を見直し (設計を) 洗練する機会 ◦ 目の前の小さなゴールに集中する = 視野が狭くなりがち  → 視野を(全体に)広げ直す = 過集中防止 ◦ 「動作の実装」と 「(内部)設計の洗練」を分け、適度に行う ▪ ※設計の洗練に取り組む際、テストが元の動作を担保する
  10. リスト ➡ Red ➡ Green ➡ リファクタ TDDのフローの本質 12 最終ゴールへの

    道筋を整理/定義 単なる「動作」の 実装に集中 必要な分だけの設計の 洗練のみに集中 • 小さなタスク (動作の実装 / 設計の洗練) に分解し 1つずつ順番に集中して取り組む • 設計のフィードバックサイクルを構築して回す 本質 気付きがあればリスト更新
  11. TDDのフローの本質 13 PLAN CHECK DO ACTION PDCA cycle Todoリスト作成/更新 ・振る舞い分析

    ・動作を列挙 ≒タスク分解/整理 1つのタスクに着手 ・1つのゴール(動作  の実装)のみに集中 (内部)設計を見直す ・設計の洗練/改善  が行えるか考える ・Todoリスト見直し (内部)設計の洗練 ・リファクタリング =内部の質を改善 Feedback 設計の洗練
  12. TDDのフローの本質 14 PLAN CHECK DO ACTION PDCA cycle Todoリスト作成/更新 ・振る舞い分析

    ・動作を列挙 ≒タスク分解/整理 1つのタスクに着手 ・1つのゴール(動作  の実装)のみに集中 (内部)設計を見直す ・設計の洗練/改善  が行えるか考える ・Todoリスト見直し (内部)設計の洗練 ・リファクタリング =内部の質を改善 Feedback 設計の洗練 GOAL テスト
  13. ◦ (計画立て) 1つの大きなゴールを多数の小さなゴールに分解 ◦ (動作の実装) 目の前の小さな1つのゴールに集中 ◦ (設計の洗練) 材料が揃ったら全体を見直し改善に集中 ▪

    リファクタ = 内部の質を上げる(テストが元の動作を担保 ) • テスト = ツール (手段)  ※テストは目的になりえない ◦ 最終ゴールに向けて自らが敷いていく「レール」というツール ◦ 内部(設計やコード)の質を洗練するための「手段」 TDDのフローの本質まとめ(お堅め版) 15 本質: 小さく分解して1歩ずつ順番に取り組み、 設計の洗練 の (Feedback) サイクル を回すこと ただ、TDDは気軽さ(敷居を下げ、柔軟に取り組むこと)が大事だと思う…
  14. テストは 「開発の進行方向を示し、コードの品質を保証する ための指針」になる(指針≒レール) • 小さいステップに分けて • コードが汚くてもいいからさっさと作ってテストで動す (小さな動くものを作る) • これを繰り返して、少しずつ動く物を増やしていき

    • ある程度形になったらテストを担保にコードを手直しして 綺麗に整える。 「TDDとは」をざっくり言い換えると 16 テストのメリット: 作ったコードを手軽に動かせる。何度でも
  15. TDDの本質の裏側にある意図 18 • フロー状態(没頭)に入りやすく、かつ持続するための手引き • (Feedback)サイクル = 設計の経験知を地道に積み上げる機会 • 小さなタスク

    (動作の実装 / 設計の洗練) に分解し 1つずつ順番に集中して取り組む • 設計のフィードバックサイクルを構築して回す 本質 なぜ「小さく分解」するのか?
  16. 小さく分解することで繋がるもの ⇒ フロー状態 • 明確な目標と即時のフィードバック • 特定のタスクへの高度な集中 • スキルとチャレンジのバランス •

    反射的自意識の喪失 • 時間のゆがみや時間感覚の変化 • タスクに対して自分でコントロールしている感覚と主体性 • 行動と意識の統合 • 自己目的的な体験(フロー状態に本質的なやりがいがある) https://asana.com/ja/resources/flow-state-work より引用 21 フロー状態 に入るための重要な8要素:
  17. 22 1. 目標設定と フィード バック 明確な目標とフィードバック 各テストが明確な短期目標となり、テストが成功 したかの即時フィードバックが得られる タスクに対して自分でコント ロールしている感覚と主体性

    自分が開発の進行を完全にコントロールしている 感覚が得られる 2. 集中と没頭 特定のタスクへの高度な集中 1つのテストに都度集中することで、他のことに 気を散らさずに作業に没頭できる 反射的自意識の喪失 反復的なプロセスに没頭することで自己批判や不 安から解放される 3. 行動と意識 の融合 行動と意識の統合 テストを書いてそれを通すという明確な行動と、 その行動に対する意識が一体化する 時間のゆがみや時間感覚の変化 作業に没頭するあまり、時間の経過を忘れる 4. 成長と達成 感 スキルとチャレンジのバランス テストを通して徐々に難易度を上げながら進める ため、スキルに適したチャレンジを提供 自己目的的な体験 (フロー状態 に本質的なやりがいがある) テストが成功する度に得られる達成感が、作業を 自己目的化する。その過程を楽しめる フ ロ | 状 態 と T D D の 関 連 by ChatGPT
  18. チャレンジ(挑戦)の機会=リファクタリング • リファクタリング (分解して組み上げ直す) ◦ = 挑戦 (Try & Error

    による経験知の積み重ね) ◦ ※設計力の向上には経験知が必須(本を読むだけでは身に付かない) 23 TDDのフローに沿う   = 経験知を積み重ねる機会を明確に設ける ▪ (何度でも)失敗できる状態にしてから取り組む • 失敗したら大きな手戻りや負債が残るリスクがある • 失敗しても容易にリトライできるよう準備して取り組む ◦ テストを書く(+バックアップ取っておくと最初からやり直せる)
  19. ブラックボックステスト視点のユニットテスト • ブラックボックステスト ◦ 入力と出力に基づいてシステムの動作を検証する方法 ※ 入出力は、内部状態更新や副作用(DB更新等)も含む広義の方 26 モジュール (テスト対象)

    IN OUT (テスト対象の)内部構造を変更 してもテストは壊れにくい テストが仕様(IN/OUT)を表す = ドキュメントとして機能 仕様(IN/OUT)を意識することで 不良が摘出しやすい
  20. TDDのサイクルの中でのテスト 27 • BAD: ◦ 内部ロジックに依存するテスト(ホワイトボックステスト)  ⇒ 頻繁にテストが壊れる • GOOD:

    ◦ 入出力 を確認するテスト ⇒ 壊れにくい ◦ テストがよりレールとして機能する ▪ 動作を実装する際、リファクタリングする際に、 テストが仕様を守るためのレール(指針)となる 振る舞い:〇〇の入力に対して、✕✕の出力をする 動作:振る舞いを複数の入出力の段階に分けたもの
  21. 不足を後からホワイトボックステストで補完 • 内部の詳細なロジックやアルゴリズム ◦ コードカバレッジ: ▪ 分岐やループを網羅的にテスト • C0(命令網羅)、C1(分岐網羅)、C2(条件網羅) ◦

    ロジックの正当性: ▪ 複雑なアルゴリズムや内部ロジックの詳細動作確認 28 内部設計が安定した段階で行い テストを壊すことなく、細部の品質まで担保できる ブラックボックステストではカバーできない部分
  22. 入出力に着目するテストの具体例 • データ駆動テスト(Data-Driven Test) ◦ Java: JUnit5 の @ParameterizedTest ◦

    PHP: PHPUnit の @dataProvidor ◦ Python: pytest の @pytest.mark.parametrize ◦ Golang: TableDrivenTests (テーブル駆動テスト)  etc. 29 • 補足:AAAパターン ※テストのフォーマット ◦ Arrange:準備、 Act:実行、 Assert:検証 テストをよりドキュメントとして機能させる
  23. 30 import java.time.Duration; import java.time.ZonedDateTime; public class DateTimeRange { private

    final ZonedDateTime startTime; private final ZonedDateTime endTime; DateTimeRange(ZonedDateTime startTime, ZonedDateTime endTime) { if (startTime.isAfter(endTime)) { throw new IllegalArgumentException("~"); } Duration duration = Duration.between(startTime, endTime); if (duration.toDays() >= 1) { throw new IllegalArgumentException("~"); } this.startTime = startTime; this.endTime = endTime; } } 時間幅(開始/終了時刻) を持つクラス  ・開始時刻 < 終了時刻  ・1日未満までの期間を指定可能
  24. 31 import static org.assertj.core.api.Assertions.assertThat; class DateTimeRangeTest { @ParameterizedTest @CsvSource({ "2024-01-01T00:00:00Z,

    2024-01-01T12:00:00Z" , "2024-01-01T00:00:00Z, 2024-01-01T00:00:01Z" , "2024-01-01T00:00:00Z, 2024-01-01T23:59:59Z" , "2024-01-01T12:00:00Z, 2024-01-02T11:59:59Z" }) void testDateTimeRange _valid(String start, String end) { // Arrange (準備) ZonedDateTime startTime = ZonedDateTime.parse(start); ZonedDateTime endTime = ZonedDateTime.parse(end); // Act ( 実行) DateTimeRange timeRange = new DateTimeRange(startTime, endTime); // Assert ( 検証) assertThat(timeRange.getStartTime()).isEqualTo(startTime); assertThat(timeRange.getEndTime()).isEqualTo(endTime); }
  25. 32 @ParameterizedTest @CsvSource({ "2024-01-01T12:00:00Z, 2024-01-01T11:00:00Z" , "2024-01-01T12:00:00Z, 2024-01-01T11:59:59Z" , "2024-01-01T00:00:00Z,

    2024-01-01T00:00:00Z" "2024-01-01T00:00:00Z, 2024-01-02T00:00:00Z" }) void testDateTimeRange _invalid(String start, String end) { ZonedDateTime startTime = ZonedDateTime.parse(start); ZonedDateTime endTime = ZonedDateTime.parse(end); assertThatThrownBy (() -> new DateTimeRange(startTime, endTime)) . isInstanceOf(IllegalArgumentException .class) } } • なにを確認したいテストなのかを意識することが大事 ◦ どんな入力データがありえるのかを押さえてテストを書く
  26. TDDなんだか難しい?(実体験) • いざやってみようとした時にぶつかった壁 ◦ テストから書く、なんだかやりづらい? ◦ (普段の開発が既存改修中心だったので) 「既存改修」のシーンではどうやればいいか分からない 34 •

    度々ぶつかるかも ◦ 小さな関数(動作)へどう分解するのが適切か分からない ※ 適切に行うにはある程度の経験知が必要で、経験を積み重ねるしかない が、その過程で自分なりの考え方やアプローチを持つことがきっと重要 既存改修のシーンで難しいと感じる理由と自己流の分解のアプローチを紹介
  27. 「既存改修」ではTDDなんだか難しい? • TDDは「新規開発」を想定して考えられている 35 ➡ 「既存改修」でその機会は多くはない 成長期フェーズの普段の開発:「既存改修」が主 既存改修は? • 既存設計との整合性を考慮しながら手を加える必要があるため

    ◦ 既存の設計が複雑(クラスの依存関係が複雑)または制約が強い(設計の 柔軟性が制約される≒新規クラスを切りづらい等)とより一層難しさが増す 新規開発に比べて考える事が増える 新しい機能や大きめのまとまった処理の追加であれば、 その機能/処理の実装の範囲ならTDDやれるかもしれない…が
  28. 「既存改修」ではTDDなんだか難しい? 36 既存改修の分類 1. ちょっとした変更を入れるような修正 ◦ 数行程度の修正(ちょっとした条件分岐追加や処理の差込みなど) 2. まとまった処理(小~大)を追加するような修正 ◦

    10行位の小さなもの~ 3. 1 と 2 のハイブリット ◦ 既存改修の多くがこのパターン ▪ 具体例:小さな新規の処理を追加する、 それを差し込むために既存に数行程度の修正を入れる ➡ アプローチを少し工夫する必要がある
  29. 既存改修のイメージ 37 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX

    全体としてどうなるべきか を捉えた上で まとまった処理を追加 ソースコード群 ちょっとした変更を入れる この2つを分けて捉える (改修部分のリストアップ ) 1つ1つの「まとまった処理」 を段階的に組み上げていく  ➡ 必要な所に差し込む
  30. ちょっとした変更を入れるような修正 • 既存の処理をテストで担保してから修正(デグレ防止) ◦ テストを書くのが難しい場合は、手を入れる周辺を (意味のある単位で)関数に外出しテスト可能にする ◦ 小さくインクリメンタルに変更を加える、を繰り返す 38 •

    補足:バグ(プログラム不良)修正の場合 ◦ バグ = 考慮漏れ・予期しない入力であることが多い ◦ 最初にバグを再現するテストを追加(あるべき状態を意識でき  その失敗ケースをテストとして残すことができる) ▪ = 知識(入力として〇〇がありえる)として残る 「テストから書く」が非常に有効
  31. 新規開発=まっさらな状態 まとまった処理を追加するような修正 • 分解して組み上げていく ◦ ただ、ここでTDDやりづらい問題にぶつかる(かもしれない) ▪ 既にあるコードの中で、この作業をどこでどう行えば良いのか… 39 XXXXXX

    XXXXXX XXXXXX XXXXXX XXXXXX どっちが やりやすい? 何かを考えるの に使っていいと 渡された紙 • 自分が自由にできる領域を確保し、そこに必要な処理を実装する ◦ 新しいクラスを切る ◦ 既存クラスの最下部などに(改行を入れて)スペースを作る TDDやりづらいへの克服の糸口 まっさらな領域を確保 して前提条件を揃える
  32. 実践の型(自己流) • 水平分解 ◦ まとまった処理を動作(小さい関数)へ 分解(動作を1つずつ実装)することで 段階的に1つの処理を作り上げる 40 ➡ どちらもテストから書いてません

    • 垂直分解 ◦ まとまった処理に対して正常系・異常系 などのケースごとに実装を進めることで 段階的に作り上げる (後から動作へ分解) ※勝手に命名
  33. 前置き:テストから書くがやりにくいと感じる理由 • テストを書くためにはテスト対象の関数(メソッド)の定義が必須 ◦ 一発で関数のインターフェース決めきれるか問題 ◦ さっさと動くものを作るのが大事 ▪ (小規模ならできなくもないが) 一発で決めるの難しい

    ▪ 実装を進める中で気付くことはままある • そこに悩んで足踏みするのは勿体ない • 悩む位ならコードを書いて考えた方が速い • 関数のインターフェースを変えるとテストも直さないといけない ◦ テストとソースを行き来(作業のスイッチング) ▪ 集中力が削がれる、最悪何を考えてたか飛ぶ • 頭の中にあるイメージ(コード)をさっさと書き出したい 41 (個人の感想) 「テストから書く」が難しいシーンは存在する。柔軟性が大事
  34. コードから書いて、後からテストでも問題ない 42 TDDの フロー • ゴール:テストが成功する ◦ 順序:テストを書く ➡ 関数を実装する

    ◦ 達成証明:テストが成功すること (期待される振る舞い/動作が正しく実現できているか 確認) 自己流 • ゴール:関数を実装する  ※関数の列挙をTodoリストに代用 ◦ 順序:関数を実装する ➡ テストを書く ◦ 達成証明:テストが成功すること (期待される振る舞い/動作が正しく実現できているか 確認) 何を確認するかがブレなければ同等の効果は引き出せる そのために意識していること:入出力(どんなケースがありえるか) ➡ 振る舞い/動作の適切な把握に繋がり、設計を洗練することに繋がる
  35. 戻りまして、実践の型(自己流) • 水平分解(動作に分解) ◦ どう分解するのが適切か 最初から見える範囲までは こちら 43 public class

    ExportService { public byte[] exportData( ExportRequest request) { fetchData(); formatData(); convertData(); } void fetchData() { } void formatData() { } void convertData() { } } ※関数の列挙をTodoリストに代用 まとまった処理へのアプローチ: • 垂直分解(ケースで分解) ◦ どう分解するのが適切か 分からない部分はこちら ◦ 個々の動作にどんなケース があるかの把握はこちら 水平分解+垂直分解の合わせ技
  36. 水平分解 ※関数分解により、設計の自由度をある程度確保 しつつ、テスト可能な単位で開発を進める考え方 44 関数1 関数2 関数3 処 理 入力

    出力 大 元 の 関 数 入力 出力 入力 出力 入力 出力 小さいパーツを関数として列挙 • 1つの処理を小さな関数に分解 • 1つの小さな関数の実装&テスト(*) ◦ これを繰り返す • 最後に大元の関数の実装&テスト 迷う位なら A で行い、最後に不要なテストは (その役目を終えたと考え)容赦なく捨てる ※ただし、仕様理解促進に繋がるなら残す A. 可視性を上げて個別にテスト B. 大元の関数でまとめてテスト (*)個別のテストはどちらか判断して実施 大きな入出力を複数の入出力の段階に分ける
  37. 水平分解の前提:適切に分解できること • 分解できるところまでで良い ◦ 分解できたところまでで、そのまま実装してみる ※ 下手に分解すると手戻りの元になりかねないため ◦ 悩んで手が止まるのが一番勿体ない ※

    自分の中の敷居を少しでも低くして手を動かすことが大事 45 どう分解すればいいか分からない時は? • できるところまで分解して実装を進める際 ◦ 軸となる物(例:正常系ワンパス)を用意し、 それを元に他のケースを考えながら実装していく (テストで入出力を押さえながら)
  38. 垂直分解 46 異 常 ① 異 常 ② 正 常

    ① 正 常 ② 正 常 ③ • 他にどんな入力がありえるか考える ➜ 具体の軸があるため考えやすい • 他の正常系を作る • テストを書く    (正常②③…) • (繰り返す) 処 理 関数内をケース毎に分けて段階的実装 • どんな入力の場合はエラーとなる 必要があるか考える • 異常系を実装する • テストを書く    (異常①②…) • (繰り返す) • 正常系ワンパス作る ➜ 1つの具体的な軸ができる • テストを書く     (正常①) P h a s e 1 P h a s e 2 P h a s e 3
  39. • 仕上がったら、最初に分解できなかった塊を具体的なコードを 見ながらどう分解すれば良いかを考える(水平分解) ◦ テストを担保にリファクタリング(関数/メソッド分割のみ) • その後に再度垂直分解を行い、どんなケースがあるかを把握する 垂直分解のあとに水平分解 47 ◦

    振る舞いの分析(振る舞いを構成する動作の洗い出し) ◦ 設計の洗練に繋がる ▪ (小さく分解するから)より整理され、理解しやすく、 保守しやすい構造に組み上げられる この 分解過程が 必要なら 繰り返す • (分解したものの動作を保証もしてくれるが) • 分解を手助けしてくれる • 分解したものへの理解を補助してくれる テストは
  40. 既存改修の全体イメージまとめ • 修正後の姿を捉え、修正箇所を特定 • まとまった処理の実装 ◦ 水平分解+垂直分解 ◦ テストを用いて段階的実装 •

    ちょっとした修正 ◦ まとまった処理の追加 ◦ テスト(場合にもよるが周辺 をテスト可能な単位に外だし) 48 どのタイミングでテストを行う かを見通しながら進めていく XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 段階的に実装&テストしながら設計 を洗練して期待される物を作る
  41. いつ、何を、どうテストするかをベースに考える テストコードにはテストの意図を込めよう #vstat https://speakerdeck.com/nihonbuson/tesutokodonihatesutofalseyi-tu-woip-meyou?slide=40 より部分引用 49 テスト 分析 テスト 設計 テスト 実装

    テスト 実行 何をテスト するか どうテスト するか テスト 分析 テスト 設計 いつやるか テストを軸に考える (これがきっとテストファーストの真意)
  42. まとめ(TDDの恩恵を引き出す考え方) 50 『TDDは分析技法であり、設計技法であり、実際には開発のすべてのアクティビティを 構造化する技法』 by Kent Beck • 段階的に小さく要素分解していく ◦

    テストがこれを補助してくれる ◦ 意識するのは入出力 (どんなケースがありえるか) ※ データ駆動テストも適宜活用 • 分解して始めて集約や一元化(リファクタリング)ができる • 「テストから書く」に捕らわれない 分解しながら、いつ/何を/どう テストするか見通しながら進める • 振る舞いの分析 • 設計の洗練 に繋がる
  43. レイヤードアーキテクチャ(DDDベース) 52 ユースケースを実現する場所 • ユースケースとは ◦ ユーザが実現して欲しいシナリオ ◦ 外部仕様の中核となるもの UserInterface

    層 Application 層 (ユースケース層) Domain 層 Infrastructure 層 ビ ジ ネ ス ロ ジ ッ ク 層 クライアントとの入出力をする層 データベースとの入出力をする層 ルールや制約などのドメイン知識の 実現をする層
  44. ユースケース と 振る舞い の関係 53 システム 振る舞い ◦ 外部から見た動き 1:Nの関係

    「動作」はきっと二分できる ・ 業務領域に関するもの ・ それ以外(技術的な要素等)    例:データの変換 外部 仕様 ユ | ス ケ | ス ユ | ス ケ | ス 振る舞い 動作 動作 動作 振る舞い 動作 動作 動作 振る舞い 動作 動作 動作 ユ | ス ケ | ス ユースケース ◦ (ユーザ等の)利用シナリオ
  45. レイヤードアーキテクチャ(DDDベース) 54 UserInterface 層 Application 層 (Use Case 層) Domain

    層 Infrastructure 層 ビ ジ ネ ス ロ ジ ッ ク 層 適切な配置をするためにも「動作」 の単位に適切に分解する事が必要 ユースケース ユースケース 振る舞い 動作 動作 振る舞い 動作 動作 動作 動作 振る舞い 動作 動作 動作 動作 (表側) (裏側)
  46. 3層アーキテクチャ(ドメイン層不要) 55 例:(単純な)データ操作中心のアプリ 動作:1つの具体的な処理 ・データのバリデーション/変換 ・(あれば)特定のビジネスルールに基づく処理 UserInterface 層 Business Logic

    層 ※複雑になりがち  ⇒ 肥大化/神クラス Infrastructure 層 ユースケース ユースケース 振る舞い 振る舞い 動作 動作 動作 振る舞い (表側) 動 作 動作 動作 動作 動作 動作 (裏側) 振る舞い:ユースケースを実現するため      に必要な一連の動作の集合? 動作(必要なら振る舞い)をクラス化