Slide 1

Slide 1 text

TDD(テスト駆動開発)のフローの本質 と恩恵を引き出すための考え方 ~ユニットテストを活用し、テスト中心思考で質とスピードを両立する 礎に~

Slide 2

Slide 2 text

目的/背景 ● 前提として「TDDやりましょう」と言うためのものではない ○ 自身がTDDの通りにやってないのでTDDやりましょうなんて口が裂けても言えない笑 2 ● その一助になればと思い、自分自身という1具体ケースの紹介 ○ 自身がTDDをどう捉え、普段の開発(既存改修のシーン)に どう落とし込んでいるのか、何を意識しているのか+αな内容 ■ 2~3年位かけて普段の開発の中で薄く向き合い続けているうちに 気付けば形になっていた実践Tipsのような物 ● が、TDDの考え方を理解し、実践することができれば 「質とスピードの両立」を実現する土台を築く近道になるはず ○ 自身がこれをある程度実現するためのベースとなっているのがTDDの考え方のため

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

TDD(テスト駆動開発)とは 4 t-wadaのブログ【翻訳】テスト駆動開発の定義 https://t-wada.hatenablog.jp/entry/canon-tdd-by-kent-beck (尺の都合等で)本当は 削らない方が良さそうな ものも削っているため、 ご一読いただければ理解 深まるかと思います ※ 「リスト ➡ Red ➡ Green ➡ リファクタ」のフローの説明の内容 は 2024/3/8 に投稿された以下の記事をリスペクト しています

Slide 5

Slide 5 text

TDD(テスト駆動開発)とは 5 リスト ➡ Red ➡ Green ➡ リファクタ 1. テストリスト(Todoリスト)を書く 2. テストを1つ書く(Red) 3. テストを成功させる(Green) ○ ※ 新しいテストの必要性に気づいたらリストに追加 4. 必要に応じてリファクタリングを行う 5. テストリストが空になるまでステップ2に戻って繰り返す

Slide 6

Slide 6 text

TDDの要素 -「リスト」 6 ● (テスト) リストの位置づけ t-wadaさんの記事では… ○ 『あるシステムに振る舞いの変更が望まれているとき  新しい振る舞いにおいて期待される動作をリストアップ する』 ■ 振る舞いの分析(変更後の振る舞いが満たすべき様々な動作を 網羅的に考える) ● 大きなタスク (ゴール) を多数の小さなタスク (ゴール) に分解 ● ゴール(振る舞いの実現)への道筋を定めていく ● 1つずつ順番に取り組む (目の前の1つに集中する) 準備 タスク 最終 ゴール タスク タスク タスク タスク タスク タスク

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

TDDの要素 -「リスト」: 振る舞い、動作 is 何? 8 動作: ● 指定された条件に合致する データをCSV形式で返す 振る舞い: 1. 指定された条件に基づいて データを取得する 2. データをフォーマットする 3. ファイルデータに変換する 要望:CSV形式でデータをエクス ポートしたい public class ExportService { public byte[] exportData(ExportRequest request) { List data = fetchData(request); String formattedData = formatData(data); byte[] fileData = convertFile(formattedData); return fileData; } List fetchData(ExportRequest req) { // 動作1: 指定された条件に基づいてデータを取得 } String formatData(List data) { // 動作2: データをフォーマットする(CSV形式) } byte[] convertFile(String formattedData) { // 動作3: ファイルデータ(バイト列)に変換 } } イメージ例

Slide 9

Slide 9 text

TDDの要素 -「Red➡Green」 ● Red ➡ Green の位置づけ t-wadaさんの記事では… ○ テストを「ひとつだけ」書く ➡ テストを成功させる ■ テストを成功させる過程にリファクタを混ぜ込まない ■ テストコードを書いている途中に設計判断が始まるが、  それは主にインターフェイスの設計判断 ※ まずは動かし、それから正しくする。このやり方が結局は脳にも優しい 9 ● 1つの小さな「動作」の実装(1タスク)のみに集中する ○ ※「動作」 = 関数(メソッド) ○ インターフェースの設計判断 ≒ 関数の引数/戻り値の判断 ○ テストから書く=1タスク (ゴール) の達成条件を明確化

Slide 10

Slide 10 text

TDDの要素 -「Red➡Green」 10 ● 各動作に対して、テスト をしながら進めていく ● 段階的に振る舞いを組み 上げつつテストする public class ExportService { public byte[] exportData(ExportRequest request) { List data = fetchData(request); String formattedData = formatData(data); byte[] fileData = convertData(formattedData); return fileData; } List fetchData(ExportRequest req) { // 動作1: 指定された条件に基づいてデータを取得 } String formatData(List data) { // 動作2: データをフォーマット(CSV) } byte[] convertData(String formattedData) { // 動作3: データをバイト配列に変換 } } testConvertData() testFormattedData() testFetchData() testExportData()

Slide 11

Slide 11 text

TDDの要素 -「リファクタ」 11 ● リファクタの位置づけ t-wada さんの記事では… ○ 『ここまで来て、ようやく実装の設計判断を行える 』 ■ 『内部実装がどうあるべきかの判断は後から確保できる』  → 内部の設計を(行えるようになったら)行う工程 ※ この段階で必要以上にリファクタリングしてしまわないこと ● (材料が揃ってから) 全体を見直し (設計を) 洗練する機会 ○ 目の前の小さなゴールに集中する = 視野が狭くなりがち  → 視野を(全体に)広げ直す = 過集中防止 ○ 「動作の実装」と 「(内部)設計の洗練」を分け、適度に行う ■ ※設計の洗練に取り組む際、テストが元の動作を担保する

Slide 12

Slide 12 text

リスト ➡ Red ➡ Green ➡ リファクタ TDDのフローの本質 12 最終ゴールへの 道筋を整理/定義 単なる「動作」の 実装に集中 必要な分だけの設計の 洗練のみに集中 ● 小さなタスク (動作の実装 / 設計の洗練) に分解し 1つずつ順番に集中して取り組む ● 設計のフィードバックサイクルを構築して回す 本質 気付きがあればリスト更新

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

○ (計画立て) 1つの大きなゴールを多数の小さなゴールに分解 ○ (動作の実装) 目の前の小さな1つのゴールに集中 ○ (設計の洗練) 材料が揃ったら全体を見直し改善に集中 ■ リファクタ = 内部の質を上げる(テストが元の動作を担保 ) ● テスト = ツール (手段)  ※テストは目的になりえない ○ 最終ゴールに向けて自らが敷いていく「レール」というツール ○ 内部(設計やコード)の質を洗練するための「手段」 TDDのフローの本質まとめ(お堅め版) 15 本質: 小さく分解して1歩ずつ順番に取り組み、 設計の洗練 の (Feedback) サイクル を回すこと ただ、TDDは気軽さ(敷居を下げ、柔軟に取り組むこと)が大事だと思う…

Slide 16

Slide 16 text

テストは 「開発の進行方向を示し、コードの品質を保証する ための指針」になる(指針≒レール) ● 小さいステップに分けて ● コードが汚くてもいいからさっさと作ってテストで動す (小さな動くものを作る) ● これを繰り返して、少しずつ動く物を増やしていき ● ある程度形になったらテストを担保にコードを手直しして 綺麗に整える。 「TDDとは」をざっくり言い換えると 16 テストのメリット: 作ったコードを手軽に動かせる。何度でも

Slide 17

Slide 17 text

補足:TDDのフローがもたらす恩恵と与える機会 を考えてみた 17

Slide 18

Slide 18 text

TDDの本質の裏側にある意図 18 ● フロー状態(没頭)に入りやすく、かつ持続するための手引き ● (Feedback)サイクル = 設計の経験知を地道に積み上げる機会 ● 小さなタスク (動作の実装 / 設計の洗練) に分解し 1つずつ順番に集中して取り組む ● 設計のフィードバックサイクルを構築して回す 本質 なぜ「小さく分解」するのか?

Slide 19

Slide 19 text

TDDの本質の裏側にある意図 19 小さなタスク ( 動作の実装 / 設計の洗練 ) に分解 大きな泥団子に立ち向かう https://speakerdeck.com/masuda220/big-ball-of-mud?slide=13 より引用

Slide 20

Slide 20 text

TDDの本質の裏側にある意図 ● いきなり大きな物は倒せない ● 小さく容易な物から倒していけ ばいずれ倒せる 20 大 き な ゴ | ル 小さく分解する ドミノ倒し的な理論:

Slide 21

Slide 21 text

小さく分解することで繋がるもの ⇒ フロー状態 ● 明確な目標と即時のフィードバック ● 特定のタスクへの高度な集中 ● スキルとチャレンジのバランス ● 反射的自意識の喪失 ● 時間のゆがみや時間感覚の変化 ● タスクに対して自分でコントロールしている感覚と主体性 ● 行動と意識の統合 ● 自己目的的な体験(フロー状態に本質的なやりがいがある) https://asana.com/ja/resources/flow-state-work より引用 21 フロー状態 に入るための重要な8要素:

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

チャレンジ(挑戦)の機会=リファクタリング ● リファクタリング (分解して組み上げ直す) ○ = 挑戦 (Try & Error による経験知の積み重ね) ○ ※設計力の向上には経験知が必須(本を読むだけでは身に付かない) 23 TDDのフローに沿う   = 経験知を積み重ねる機会を明確に設ける ■ (何度でも)失敗できる状態にしてから取り組む ● 失敗したら大きな手戻りや負債が残るリスクがある ● 失敗しても容易にリトライできるよう準備して取り組む ○ テストを書く(+バックアップ取っておくと最初からやり直せる)

Slide 24

Slide 24 text

実践のためのユニットテストとの向き合い方 ~価値のあるユニットテストを残しましょう~ 24

Slide 25

Slide 25 text

価値のあるユニットテストとは? ※ここでの「価値」の定義: ● プロダクトコードを修正しても壊れにくいテスト ● 不良の発見にも繋がりやすいテスト ● ドキュメントとしても機能するテスト 25 ブラックボックステスト視点のユニットテスト ➡ 〇〇の入力に対して、✕✕の出力が得られる(IN/OUT)

Slide 26

Slide 26 text

ブラックボックステスト視点のユニットテスト ● ブラックボックステスト ○ 入力と出力に基づいてシステムの動作を検証する方法 ※ 入出力は、内部状態更新や副作用(DB更新等)も含む広義の方 26 モジュール (テスト対象) IN OUT (テスト対象の)内部構造を変更 してもテストは壊れにくい テストが仕様(IN/OUT)を表す = ドキュメントとして機能 仕様(IN/OUT)を意識することで 不良が摘出しやすい

Slide 27

Slide 27 text

TDDのサイクルの中でのテスト 27 ● BAD: ○ 内部ロジックに依存するテスト(ホワイトボックステスト)  ⇒ 頻繁にテストが壊れる ● GOOD: ○ 入出力 を確認するテスト ⇒ 壊れにくい ○ テストがよりレールとして機能する ■ 動作を実装する際、リファクタリングする際に、 テストが仕様を守るためのレール(指針)となる 振る舞い:〇〇の入力に対して、✕✕の出力をする 動作:振る舞いを複数の入出力の段階に分けたもの

Slide 28

Slide 28 text

不足を後からホワイトボックステストで補完 ● 内部の詳細なロジックやアルゴリズム ○ コードカバレッジ: ■ 分岐やループを網羅的にテスト ● C0(命令網羅)、C1(分岐網羅)、C2(条件網羅) ○ ロジックの正当性: ■ 複雑なアルゴリズムや内部ロジックの詳細動作確認 28 内部設計が安定した段階で行い テストを壊すことなく、細部の品質まで担保できる ブラックボックステストではカバーできない部分

Slide 29

Slide 29 text

入出力に着目するテストの具体例 ● データ駆動テスト(Data-Driven Test) ○ Java: JUnit5 の @ParameterizedTest ○ PHP: PHPUnit の @dataProvidor ○ Python: pytest の @pytest.mark.parametrize ○ Golang: TableDrivenTests (テーブル駆動テスト)  etc. 29 ● 補足:AAAパターン ※テストのフォーマット ○ Arrange:準備、 Act:実行、 Assert:検証 テストをよりドキュメントとして機能させる

Slide 30

Slide 30 text

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日未満までの期間を指定可能

Slide 31

Slide 31 text

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); }

Slide 32

Slide 32 text

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) } } ● なにを確認したいテストなのかを意識することが大事 ○ どんな入力データがありえるのかを押さえてテストを書く

Slide 33

Slide 33 text

普段の開発(既存改修)でTDDの恩恵を引き出す ために必要な考え方(自己流) 33

Slide 34

Slide 34 text

TDDなんだか難しい?(実体験) ● いざやってみようとした時にぶつかった壁 ○ テストから書く、なんだかやりづらい? ○ (普段の開発が既存改修中心だったので) 「既存改修」のシーンではどうやればいいか分からない 34 ● 度々ぶつかるかも ○ 小さな関数(動作)へどう分解するのが適切か分からない ※ 適切に行うにはある程度の経験知が必要で、経験を積み重ねるしかない が、その過程で自分なりの考え方やアプローチを持つことがきっと重要 既存改修のシーンで難しいと感じる理由と自己流の分解のアプローチを紹介

Slide 35

Slide 35 text

「既存改修」ではTDDなんだか難しい? ● TDDは「新規開発」を想定して考えられている 35 ➡ 「既存改修」でその機会は多くはない 成長期フェーズの普段の開発:「既存改修」が主 既存改修は? ● 既存設計との整合性を考慮しながら手を加える必要があるため ○ 既存の設計が複雑(クラスの依存関係が複雑)または制約が強い(設計の 柔軟性が制約される≒新規クラスを切りづらい等)とより一層難しさが増す 新規開発に比べて考える事が増える 新しい機能や大きめのまとまった処理の追加であれば、 その機能/処理の実装の範囲ならTDDやれるかもしれない…が

Slide 36

Slide 36 text

「既存改修」ではTDDなんだか難しい? 36 既存改修の分類 1. ちょっとした変更を入れるような修正 ○ 数行程度の修正(ちょっとした条件分岐追加や処理の差込みなど) 2. まとまった処理(小~大)を追加するような修正 ○ 10行位の小さなもの~ 3. 1 と 2 のハイブリット ○ 既存改修の多くがこのパターン ■ 具体例:小さな新規の処理を追加する、 それを差し込むために既存に数行程度の修正を入れる ➡ アプローチを少し工夫する必要がある

Slide 37

Slide 37 text

既存改修のイメージ 37 XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 全体としてどうなるべきか を捉えた上で まとまった処理を追加 ソースコード群 ちょっとした変更を入れる この2つを分けて捉える (改修部分のリストアップ ) 1つ1つの「まとまった処理」 を段階的に組み上げていく  ➡ 必要な所に差し込む

Slide 38

Slide 38 text

ちょっとした変更を入れるような修正 ● 既存の処理をテストで担保してから修正(デグレ防止) ○ テストを書くのが難しい場合は、手を入れる周辺を (意味のある単位で)関数に外出しテスト可能にする ○ 小さくインクリメンタルに変更を加える、を繰り返す 38 ● 補足:バグ(プログラム不良)修正の場合 ○ バグ = 考慮漏れ・予期しない入力であることが多い ○ 最初にバグを再現するテストを追加(あるべき状態を意識でき  その失敗ケースをテストとして残すことができる) ■ = 知識(入力として〇〇がありえる)として残る 「テストから書く」が非常に有効

Slide 39

Slide 39 text

新規開発=まっさらな状態 まとまった処理を追加するような修正 ● 分解して組み上げていく ○ ただ、ここでTDDやりづらい問題にぶつかる(かもしれない) ■ 既にあるコードの中で、この作業をどこでどう行えば良いのか… 39 XXXXXX XXXXXX XXXXXX XXXXXX XXXXXX どっちが やりやすい? 何かを考えるの に使っていいと 渡された紙 ● 自分が自由にできる領域を確保し、そこに必要な処理を実装する ○ 新しいクラスを切る ○ 既存クラスの最下部などに(改行を入れて)スペースを作る TDDやりづらいへの克服の糸口 まっさらな領域を確保 して前提条件を揃える

Slide 40

Slide 40 text

実践の型(自己流) ● 水平分解 ○ まとまった処理を動作(小さい関数)へ 分解(動作を1つずつ実装)することで 段階的に1つの処理を作り上げる 40 ➡ どちらもテストから書いてません ● 垂直分解 ○ まとまった処理に対して正常系・異常系 などのケースごとに実装を進めることで 段階的に作り上げる (後から動作へ分解) ※勝手に命名

Slide 41

Slide 41 text

前置き:テストから書くがやりにくいと感じる理由 ● テストを書くためにはテスト対象の関数(メソッド)の定義が必須 ○ 一発で関数のインターフェース決めきれるか問題 ○ さっさと動くものを作るのが大事 ■ (小規模ならできなくもないが) 一発で決めるの難しい ■ 実装を進める中で気付くことはままある ● そこに悩んで足踏みするのは勿体ない ● 悩む位ならコードを書いて考えた方が速い ● 関数のインターフェースを変えるとテストも直さないといけない ○ テストとソースを行き来(作業のスイッチング) ■ 集中力が削がれる、最悪何を考えてたか飛ぶ ● 頭の中にあるイメージ(コード)をさっさと書き出したい 41 (個人の感想) 「テストから書く」が難しいシーンは存在する。柔軟性が大事

Slide 42

Slide 42 text

コードから書いて、後からテストでも問題ない 42 TDDの フロー ● ゴール:テストが成功する ○ 順序:テストを書く ➡ 関数を実装する ○ 達成証明:テストが成功すること (期待される振る舞い/動作が正しく実現できているか 確認) 自己流 ● ゴール:関数を実装する  ※関数の列挙をTodoリストに代用 ○ 順序:関数を実装する ➡ テストを書く ○ 達成証明:テストが成功すること (期待される振る舞い/動作が正しく実現できているか 確認) 何を確認するかがブレなければ同等の効果は引き出せる そのために意識していること:入出力(どんなケースがありえるか) ➡ 振る舞い/動作の適切な把握に繋がり、設計を洗練することに繋がる

Slide 43

Slide 43 text

戻りまして、実践の型(自己流) ● 水平分解(動作に分解) ○ どう分解するのが適切か 最初から見える範囲までは こちら 43 public class ExportService { public byte[] exportData( ExportRequest request) { fetchData(); formatData(); convertData(); } void fetchData() { } void formatData() { } void convertData() { } } ※関数の列挙をTodoリストに代用 まとまった処理へのアプローチ: ● 垂直分解(ケースで分解) ○ どう分解するのが適切か 分からない部分はこちら ○ 個々の動作にどんなケース があるかの把握はこちら 水平分解+垂直分解の合わせ技

Slide 44

Slide 44 text

水平分解 ※関数分解により、設計の自由度をある程度確保 しつつ、テスト可能な単位で開発を進める考え方 44 関数1 関数2 関数3 処 理 入力 出力 大 元 の 関 数 入力 出力 入力 出力 入力 出力 小さいパーツを関数として列挙 ● 1つの処理を小さな関数に分解 ● 1つの小さな関数の実装&テスト(*) ○ これを繰り返す ● 最後に大元の関数の実装&テスト 迷う位なら A で行い、最後に不要なテストは (その役目を終えたと考え)容赦なく捨てる ※ただし、仕様理解促進に繋がるなら残す A. 可視性を上げて個別にテスト B. 大元の関数でまとめてテスト (*)個別のテストはどちらか判断して実施 大きな入出力を複数の入出力の段階に分ける

Slide 45

Slide 45 text

水平分解の前提:適切に分解できること ● 分解できるところまでで良い ○ 分解できたところまでで、そのまま実装してみる ※ 下手に分解すると手戻りの元になりかねないため ○ 悩んで手が止まるのが一番勿体ない ※ 自分の中の敷居を少しでも低くして手を動かすことが大事 45 どう分解すればいいか分からない時は? ● できるところまで分解して実装を進める際 ○ 軸となる物(例:正常系ワンパス)を用意し、 それを元に他のケースを考えながら実装していく (テストで入出力を押さえながら)

Slide 46

Slide 46 text

垂直分解 46 異 常 ① 異 常 ② 正 常 ① 正 常 ② 正 常 ③ ● 他にどんな入力がありえるか考える ➜ 具体の軸があるため考えやすい ● 他の正常系を作る ● テストを書く    (正常②③…) ● (繰り返す) 処 理 関数内をケース毎に分けて段階的実装 ● どんな入力の場合はエラーとなる 必要があるか考える ● 異常系を実装する ● テストを書く    (異常①②…) ● (繰り返す) ● 正常系ワンパス作る ➜ 1つの具体的な軸ができる ● テストを書く     (正常①) P h a s e 1 P h a s e 2 P h a s e 3

Slide 47

Slide 47 text

● 仕上がったら、最初に分解できなかった塊を具体的なコードを 見ながらどう分解すれば良いかを考える(水平分解) ○ テストを担保にリファクタリング(関数/メソッド分割のみ) ● その後に再度垂直分解を行い、どんなケースがあるかを把握する 垂直分解のあとに水平分解 47 ○ 振る舞いの分析(振る舞いを構成する動作の洗い出し) ○ 設計の洗練に繋がる ■ (小さく分解するから)より整理され、理解しやすく、 保守しやすい構造に組み上げられる この 分解過程が 必要なら 繰り返す ● (分解したものの動作を保証もしてくれるが) ● 分解を手助けしてくれる ● 分解したものへの理解を補助してくれる テストは

Slide 48

Slide 48 text

既存改修の全体イメージまとめ ● 修正後の姿を捉え、修正箇所を特定 ● まとまった処理の実装 ○ 水平分解+垂直分解 ○ テストを用いて段階的実装 ● ちょっとした修正 ○ まとまった処理の追加 ○ テスト(場合にもよるが周辺 をテスト可能な単位に外だし) 48 どのタイミングでテストを行う かを見通しながら進めていく XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX 段階的に実装&テストしながら設計 を洗練して期待される物を作る

Slide 49

Slide 49 text

いつ、何を、どうテストするかをベースに考える テストコードにはテストの意図を込めよう #vstat https://speakerdeck.com/nihonbuson/tesutokodonihatesutofalseyi-tu-woip-meyou?slide=40 より部分引用 49 テスト 分析 テスト 設計 テスト 実装 テスト 実行 何をテスト するか どうテスト するか テスト 分析 テスト 設計 いつやるか テストを軸に考える (これがきっとテストファーストの真意)

Slide 50

Slide 50 text

まとめ(TDDの恩恵を引き出す考え方) 50 『TDDは分析技法であり、設計技法であり、実際には開発のすべてのアクティビティを 構造化する技法』 by Kent Beck ● 段階的に小さく要素分解していく ○ テストがこれを補助してくれる ○ 意識するのは入出力 (どんなケースがありえるか) ※ データ駆動テストも適宜活用 ● 分解して始めて集約や一元化(リファクタリング)ができる ● 「テストから書く」に捕らわれない 分解しながら、いつ/何を/どう テストするか見通しながら進める ● 振る舞いの分析 ● 設計の洗練 に繋がる

Slide 51

Slide 51 text

おまけ:レイヤードアーキテクチャから捉える 「振る舞い」と「動作」 51

Slide 52

Slide 52 text

レイヤードアーキテクチャ(DDDベース) 52 ユースケースを実現する場所 ● ユースケースとは ○ ユーザが実現して欲しいシナリオ ○ 外部仕様の中核となるもの UserInterface 層 Application 層 (ユースケース層) Domain 層 Infrastructure 層 ビ ジ ネ ス ロ ジ ッ ク 層 クライアントとの入出力をする層 データベースとの入出力をする層 ルールや制約などのドメイン知識の 実現をする層

Slide 53

Slide 53 text

ユースケース と 振る舞い の関係 53 システム 振る舞い ○ 外部から見た動き 1:Nの関係 「動作」はきっと二分できる ・ 業務領域に関するもの ・ それ以外(技術的な要素等)    例:データの変換 外部 仕様 ユ | ス ケ | ス ユ | ス ケ | ス 振る舞い 動作 動作 動作 振る舞い 動作 動作 動作 振る舞い 動作 動作 動作 ユ | ス ケ | ス ユースケース ○ (ユーザ等の)利用シナリオ

Slide 54

Slide 54 text

レイヤードアーキテクチャ(DDDベース) 54 UserInterface 層 Application 層 (Use Case 層) Domain 層 Infrastructure 層 ビ ジ ネ ス ロ ジ ッ ク 層 適切な配置をするためにも「動作」 の単位に適切に分解する事が必要 ユースケース ユースケース 振る舞い 動作 動作 振る舞い 動作 動作 動作 動作 振る舞い 動作 動作 動作 動作 (表側) (裏側)

Slide 55

Slide 55 text

3層アーキテクチャ(ドメイン層不要) 55 例:(単純な)データ操作中心のアプリ 動作:1つの具体的な処理 ・データのバリデーション/変換 ・(あれば)特定のビジネスルールに基づく処理 UserInterface 層 Business Logic 層 ※複雑になりがち  ⇒ 肥大化/神クラス Infrastructure 層 ユースケース ユースケース 振る舞い 振る舞い 動作 動作 動作 振る舞い (表側) 動 作 動作 動作 動作 動作 動作 (裏側) 振る舞い:ユースケースを実現するため      に必要な一連の動作の集合? 動作(必要なら振る舞い)をクラス化