See the Elephant

1992生まれのプログラマが書くエンジニアブログ

自戒を込めてt_wadaさんの"50分でわかるテスト駆動開発"を見てみた

テスト駆動開発の第一人者

3年目も半分終わったからt_wadaさんのTDD解説動画をみてみた。

https://channel9.msdn.com/Events/de-code/2017/DO03

1年目から直々にペアプロでTDDを教えていただいたので、改めてプログラマになる上で自戒を込めて動画を見返した。

まさに、隣でペアプロをしてもらっているような感覚をもてる内容であった。

ほぼ文字起こしであり、内容は動画を見ればわかるので読まなくてもいいです。

理解度をさらに深めるための復習として。

途中でてくる動画秒数はみて欲しい図を表している。

それでは書いていこう。

テスト駆動開発とは何か

kent beck テスト駆動開発入門, xp

いい本はいい書き出しから始まる

「動作する綺麗なコード」がTDDのゴールだ
  • 動作する <-> 動作しない
  • 綺麗な <-> 汚い

動作する綺麗なコードに到る道は2つある

  • 綺麗で動かないコードから動くコードに
  • 汚くて動くコードから綺麗なコードに

伝統的なソフトウェアの世界, 学問的な世界では

  • 綺麗で動かないコードから動くコードに

するルートが正しい道だと言われていた

何らかの完璧主義や虚栄心を含んでいた。 だが、日々の開発スケジュールには締め切りがある。

設計ばかり考えずにコードを書き始める。 コードを書き始めた時点で色々なものが見え始める。

  • 細部について不足した設計
  • 考え抜いた結果、綺麗だが複雑すぎる設計
  • 綺麗な設計のまま実装したが、遅くて使い物にならない設計

現代において、動くコードを書き始めた瞬間にたくさんのFBが見える。 その時に設計の正しさがわかる。

ある時に勝ちパターンだったものは、時を経ると自動的にアップデートされて陳腐化する。 今日では、勝ちパターンは陳腐化していて

動かしてみないとわからないことがおおい

では、今日の

  • 汚くて動くコードから綺麗なコードに

のほうが筋が良さそうに見える。 先ほどの例では動くコードにする時に罠があった

ではどこに罠があるのか

一つは

堕落 動いたからいいじゃん。コードが汚くても

焦り 作るものが多いから、時間がない

恐れ せっかく動くところまで来たのに綺麗にするためにコードに手を入れないといけない 綺麗にしていく過程で動かなくなってしまっては意味がない。だからコードに触れるな。

つまり, TDDは恐れを克服する方法である。

TDDはサイクル

RED, GREEN, REFACTORINGのサイクル

  1. やるべきことを簡単なtodoリストで管理する
  2. todo listから1こをピックアップしテストを書く
  3. 優先度が高いもの
  4. テストが簡単な末端からやる
  5. テストを失敗させる(RED)
  6. テストを通過するための最小限のコードを実装する。
  7. 2で書いたテストを成功させる(GREEN)
  8. テストが通るままでリファクタリングを行う
  9. 1-6を繰り返す

サイクルを回転し続けること

  • Green: 汚くて動かない -> 動くコードに
  • Refactoring: 汚くて動くコードを-> 綺麗に
  • Red: もう一個テストを書き失敗する

RED, GREEN, REFACTORINGのサイクルが揺らぐとTDDは揺らいでいく

このサイクルはREFACTORINGから揺らぐことが多い。

  • 時間がない
  • あとでやろう

リファクタリングのISSUEは放置される。

なぜなら、ビジネスオーナーからすると外側から見て振る舞いが変わらないから。 中長期的には変更が小さくなるため価値がある。

しかし、この価値をビジネスオーナーに説明することはエゴに見えて非常に難しい。 放置していくと掃除が大変になる。

1日掃除をしようとすると説得が必要になり、心が折れる

REFACTORINGは非常に傷つきやすく、放置されやすい。

TDDでは、REFACTORINGを独立したタスクではなく、サイクルの中に混ぜてしまうことを提案した。

大規模にやるのが難しいのであれば、常に小さいリファクタリングをやり続けることを提唱した。

繊細な存在を無視しないための工夫。

TDDの大きいサイクル

RED, GREEN, REFACTORINGのサイクル(Unit Test)よりも半径の大きいサイクル(Acceptance Test)が存在する。

22:25

Acceptance Testの回転は大体1日おき。ビジネスレベルで書かれたテストが回っている。

22:43

その外側にさらに大きいテストサイクルが回っている。

demo

  • FizzBuzz問題をTDDでやる
  • 1つ1つの問題を要素に分解してTodo listにする

TDDはサイクル でやった1である

30:08

テスト駆動開発において 「予想通り落ちることはとても大事」 テストメソッド名は日本語で書く. テストコードは仕様書だから日本語で書いていきたい.

テストファーストなので具体的に仕様が決まらないと書けない. つまり、具体的にかけない場合は問題設定が大きすぎる より詳細なケースを考えて、それをテスト名にしたテストを書く

概ね以下の手順でやる

 // 前準備
 // 実行
 // 検証

ゴールから書くことで逆算して設計していくことができる。 つまり、アサーションを書くところから書いていく。

さも既に機能があるかのように書いていき、アサーションを完成させる。 ゴール、しかも使う側から決めてしまう。

IDEは構文エラーがある時にquick fixを提案してくる。 この機能を使って必要なクラスやメソッド、初期化を終わらせていく。 つまり、ゴールを描いてから肉付けをやっていく。

1番最初は考えることが多い。クラス名、メソッド名、引数を考える必要がある。

そしてテストを回す。当然失敗する。

実装が足りない場合は、「テストが通る最小限のコード」を実装する。

当然のコードを書いて成功する必要がある。

当然のコードを書いて赤色にならなければ、テストコードに問題がある。

テストコードにバグがある時はどうするか?

なんらかの間違いがある。

テストコードのテストは、実装側でやらせる。 実装側に機械的に間違いを入れる。

ミューテーションテスティング テスト項目が組合せ爆発する。

仮実装(失敗する実装)を行うことで、テストコードのテストを行うことができる。 - テストの失敗が、テストコードの正しさを証明する

別のデータ、視点のテストケースを足してみる。 三角測量と呼ばれる。

テストケースを増やすか、アサーションを増やすか。 オススメのやり方はテストケースを増やすこと。 1テスト1アサーション

アサーションがテストにいっぱいあると、どのアサーションが落ちたかわかりづらい。 アンチパターン: アサーションルーレットパターン

テストケースが失敗するので妥当な実装を書きGreenにする。 汚い部分をリファクタリングする。

実は、三角測量をやっていくと3箇所以上重複が生まれる。 ここでテストコードのリファクタリングをやっていく。

そして、テストコードのリファクタリングが終わったら、実コードもリファクタする。

実装が目に見えている場合は、その通りにかけばいい。 何も臆病な進め方がTDDではない。

明白な実装と呼ぶ。

仕様を完全に満たさないテストコードと実コードを残しても、完全な仕様が失われる。 (todoリストが失われることになる)

  1. 仕様を表す長いテスト名をつけることもベターなやり方
  2. テストコードを入れ子にすること
  3. 入れ子のクラスや内部関数を定義して、テストケースの興味領域を表現することができる

テストファーストでやっていくと不安を解消するためのテストが増えていく。

TDDのテストケースの数が各ネームスペースで異なる場合、対称性が異なる。

テストは増やすことは簡単だが、減らすことは非常に難しい。 足した人以外は減らすことが非常に難しい。

テストのメンテナンスコストがかかるため、知識が失われる前に必要最小限までテストコードを減らす。

TDDのスキル

  • 問題を小さく分割する
  • 歩幅を調整する
    • テスト -> 仮実装 -> 三角測量 -> 実装
    • テスト -> 仮実装 -> 実装
    • テスト -> 明白な実装
  • テストの構造化とリファクタリング