TDDでコードを書くようになってから1年経った

はじめに

昨年テスト駆動開発の本を読んでから基本的に TDD でコードを書くようにしていましたが、TDD でコードを書くようになってから自分のコーディングがどう変わったのかを書きました。また、TDD に馴染みのない方向けにはこちらのスライドがいい感じにまとまってました。

前提

Java と Server-side Kotlin + JUnit 4 or 5 で TDD を実践したので、似たような構成であれば参考になるかもしれません。

TDD やる前

だいたい以下のようなサイクルでコードを書いていました。

1.ある程度コード書く 10~20行ぐらい(まだ機能は完成していない状態)→ 2.テストコード書く→ 3.pass or fail → 4.ある程度動くことを担保したのでコードの続き書く→ 1に戻る  

これも TDD に近いコーディングスタイルだと思っていましたが、本を読んで TDD を始めてからは上記のサイクルを変更しました。

現在はTDDのサイクルに従っている

TDD のマントラレッド、グリーン、リファクタリング とあるように、まずは落ちるテストを書いて、テストが通るようにしてからリファクタリングする というサイクルを繰り返します。 現在自分が行なっている TDD のサイクルは TDD の原則に従っていますが、TDD にある程度慣れてきた現在では、サイクルの感覚を少し長くしたり、長すぎると思ったらまた短くしたりといったように、実装する機能の難易度や大きさに合わせてサイクルの粒度を調整しています。難しい機能ほど細かいサイクルで頻繁に確認したほうが確実に出来上がってきていることを確認できるのでおすすめです。決まりきったテンプレート的な処理を実装する際は TDD を適用する必要もない場合があるので (例えば O/R Mapper でただ insert するだけの処理など、ビジネスロジック的に考える余地のないような機能) その際は TDD を使用していません。

コード書く前にまずは実現したいことをリストアップする

自分が TDD でコードを書くにあたって、まずはどんなことがやりたいのかを脳内やホワイトボードやソースコードへの // TODO コメントを使用して記載します。粒度は荒く、〜取得処理, 〜登録処理 といったような、だいたい1メソッドに収まる10~20行程度のやりたいことをリストアップして、リストの上から順番にコーディングします。途中で追加したり実装途中で不要なことに気が付いたら// TODO を追加/削除します。

こんな感じでソース内に TODO を列挙する
// TODO ◯◯コントローラ作成
// TODO ~取得処理作成
// TODO ~更新処理作成
// TODO ~登録処理作成

※ ここで1つの // TODO が1メソッドに収まる場合もあればそうでない場合もあるので、メソッドが大きくなりすぎたら適宜リファクタリングして処理を分割します。

テストクラスを最初に作る

IntelliJ IDEA なら command + shift + T でテストクラス作れるので、最初に必ずテストクラスを作成します。これはめちゃくちゃ大事です。

エディタを2分割する

プロダクションコードとテストコードでエディタを分割して表示します。画面切り替えなどでプロダクションコードとテストコードを行き来すると、脳内でコンテキストの切り替えが発生して脳のリソースを無駄遣いすることになるので、左右か上下にコードを並べて表示させます。最近のモニタは横に長いので、自分は左右に表示させる派です。4K のモニタを使うとまた違うかもしれないです。余談になりますが、自宅では MacBook Pro 15inch + FullHD ゲーミングモニタ2枚、職場では MacBook Pro 13inch FullHD モニタ1枚 という環境です。可能であれば 4K モニタ2枚を会社に支給してもらいましょう。

まずはレッドにする

メソッドを作成し、まずは落ちるテストケースをテストコードを記述します。まだメソッドを作成していないのであれば、存在しないメソッドをテストコードから実行してコンパイルエラーの状態を作ります。この辺りはどちらでも構わないと思っています。無事テストが落ちる (コンパイルエラーになる) ことを確認したらプロダクションコードに戻ります。

グリーンにする

なんでもいいのでテストが通るコードに修正します。テストが通ったらリファクタリングのステップに移ります。

リファクタリングする

グリーンにするために書いたコードを綺麗にしていきます。ハードコードした変数を定数にしたり、enum クラスを作成したり、メソッド分割したりなど、テストしやすさを重視した粒度にします。メソッドの行数的には10~20行程度になるかと思います。が、行数よりも重視するのは、プロダクションコードが容易にテストコードをかける構造になっているかをもっとも重視するようにしています。そもそも、テストが書きやすい構造になっていないとテストコードを書くコストが高くなり、数ヶ月後にコードを見返したときに処理の全容を把握することが非常に困難になります。複雑なコードベースに対してテストを追加した場合、全容を把握するのが困難になるため、実装上の抜けや漏れを把握しきれずにバグが潜在します。ですので、複雑なコードだとバグを産みやすくなってしまいます。

レッド、グリーン、リファクタリングを繰り返す

あとはひたすらレッド、グリーン、リファクタリングを繰り返してコーディングします。TDD より前の方法でコードを書いていたときは、テストを通すのが結構大変なことがあったので、テストを書くことの心理的負荷が高く後回しにしがちだったのですが、TDD でテストコードを書くハードルがグッと下がったので、心理的負荷がかなり低くなり、テストコードを書くのが楽しくなりました。結果的に、シンプルなコードを保ちつつコードの意図を反映したテストコードがセットでついてくるので、ある程度まとめて処理を書いてからテストを書くよりも TDD を実践したほうが品質が高くなり、テストが通らなくて悩む時間が減るのでコスト的にも安上がりになった感じがあります。

まとめ

TDD はプロダクションコードとテストコードを頻繁に行き来し、テストを通すために書いた雑なコードを綺麗な状態にリファクタリングすることにより、コードが洗練された状態になります。コードを書く上で大切な自分の書いたコードが意図した通りになっているかを確認するための非常に優れた技法です。また、テストを通すことが強制されるため、必然的にテストしやすいシンプルなコードになります。ですが、TDD はあくまでもコーディングを補助するための技法であり、TDD で書いたから業務的にも正しい仕様になっているかはまた別問題なので、あくまでもコードが自分の意図通りの挙動になっているかを確認するために TDD でコーディングするという考えで用いるのが正しい使い方になります。自分は今後も TDD でやっていく所存です。