[tddjs]chap02 テスト駆動開発プロセス

テスト駆動開発の目的、目標

  • これから実装しようとしている機能の仕様をテストに書くことからはじまるイテレーションイテレーション中でコードのフィードバックを早く頻繁に得られるため設計がマトモになる
  • これまでの JavaScript のコードは解決の方向にむかって(やみくもに)コードを積み重ねていくようなスタイルが多かった(アドホックなコードの積み重ねのような感じ?)
  • テスト駆動(以下 TDD)では目標の定義からはじまる。仕様を満たすためのコードを追加していくので余計なコードを追加しにくい
  • TDD は単体テストに強く依存しているため、ほかの部分から切り離して単独のコンポーネントに力を注ぐスタイルになるため、コードの疎結合を保ち、単一責任の原則を守り、設計・実装がマトモになる。
  • TDD では設計のことをじっくり考えることになる。機能追加の際にはユースケースとして、解決しようとしている問題をテストとして記述するため、よく考えることになる

プロセス

TDD の反復的プロセスで、以下の4ステップをイテレーションの単位としている

  • テストを書く
  • テストを実施する。新しいテストが不合格になるのを確認する。
  • テストに合格するようにコードを書く。
  • リファクタリングして重複を取り除く。

イテレーションは短くすぐ終わる。今何をしているのか意識してよく考えること。何かすべきこと(機能が足りないとか修正が必要とか)を見つけたら TODO メモに書いておき、まずは今やっているイテレーションを終わらす。

テストを書く

  • 機能をえらび、その機能のためのテストを書くこと。
  • ひとつのテストで、ひとつの機能のアサーションを行う。
  • テストは実装しているインターフェースを記述するものであること。
  • テストの前提となる入力については、関数の入力だけでなく、グローバルスコープやオブジェクトの状態を含む

テストを実施する。新しいテストが不合格になるのを確認する。

  • テストを書いたらすぐに実行し不合格になるのを試す。コードがどう不合格になるのかわかって書いているはずだろうが、テスト自体のバグがないか確認になる
  • 単体テストは分岐ロジックを含んではならない
  • テスト間の依存はあってはならない。テストは高速で実行できるのが好ましい。
  • いきなりテストを書いて、実装を書いていない状態でテストを走らせると、コードについて新しい発見があることがある。言語ですでにその機能が実装されている、など。

テストを合格させる

  • 動作する一番単純なコードを書く(ハードコードでもかまわない)、リファクタリングの過程で整理していく
    • 小さなステップで書きはじめてよい(ただしハードコードは、テンポ良く先に進めるためのもの。ユースケースを追加していくと一般化された実装へのヒントを得られたりする)
  • テストを合格させるために必要なだけのコードを書くこと(追加された振る舞いは追加された要件として表現されなければならない
  • 明確な要件を背後に持たないコードは "水ぶくれ" でしかない。(水ぶくれは、場当たり的なコードで、明確な目的にむかって書かれていないようなコード、といった意味?)
  • YAGNI(必要になるまで機能を追加してはならない)、その必要性を具体的に示すユースケースがないのにコードベースに機能を追加するべきではない
    • メソッド引数を過度に柔軟にするなど、やりがち
    • インタフェースわかりにくくなりそうだし、柔軟なコードに対応するユースケースを見出せるのか? → 本当にいろんな引数を受け付ける必要あるのか?

リファクタリングして重複を取り除く。

  • テストがグリーンのまま、インタフェースを保ったまま、重複を取り除いていく
  • ユースケースを追加するたびに、ハードコードでの対応を追加していくと、そのなかから重複を見つけられるようになる
  • 重複はテストコードでもあるのでそちらもなんとかする(setup, teardown とかで共通化しメンテナンスしやすく)
  • インターフェース自体の改善の際にも重複を排除してあれば楽ができる

テスト駆動開発を円滑に進めるために

  • テストの実行
    • 高速で実行できること
    • 簡単に実行できること
    • でないとテストの実行をサボる
    • 自動テストを実行すると良い

テスト駆動開発の利点

  • 動作するコードが手にはいる(テストがある → 実装がある?これいつもよくわからない。その時点でその要件を満たす動作するコード、ということか?)
  • 独立したコンポーネントについて記述、開発していくため、疎結合なコードが書きやすい。
  • テストコードがその実装のクライアントコードとなるため、テストコードを追加していくとユースケースが増え密結合をみつけやすくなる
  • イテレーションがテストを書くところからはじまるため、コードについてよく考えることになり意識的な開発が促される
  • コードの実際の使用例から書き始めるので、誰も必要としない機能を導入する可能性は下がる。YAGNI
  • 生産性が上がる
    • F5 キーで手作業でテストしないから
    • テストでグリーンであれば想定する機能を満たしているので、びくびくせずリファクタリングできる