TDDトレーニングまとめ

どうもABAB↑↓BAです。今回の記事はElm成分ほぼ無いです。今回の記事では謎の集団Zeneloで僕が**毎週**受けているTDD(テスト駆動開発)トレーニングについて、基礎中の基礎である、TDDサイクルとベイビーステップについてのみお話したいと思います。

# TDDサイクルとベイビーステップ

まずテスト駆動開発とは、という前提の話をしていきましょう。テスト駆動開発では以下のサイクルを回しながら、開発を進めていくことになります。

Red -> Green -> Refactor

## Red

最初に落ちるテストを書くことになります。つまりこれはどういうことでしょうか？ 

実装コードについては**インターフェース**(実装が空だが明確に型が決まっており、テストから呼び出せる状態。)を実装します。コンパイルエラーではいけません。

```java
class FizzBuzz {
    public static String fizzbuzz(int num) {
       return null;
    }
}
```

続いてテストコードを書きます。このときは**要件を設計し定義すること**と等しいです。テストコードはこのとき決して実装に合わせて、**テストが通るコードを書いてはいけません。**

```java
public class FizzBuzzTest {
    @Test
    public void 数字の3はfizzである() {
        assertThat(FizzBuzz.fizzbuzz(3), is("fizz"));
    }
}
```

テストはもちろん落ちます。ここで重要なことは**Red**は次に実装するべきものの道を指し示し、**生きている設計**となります。

```
FizzBuzzTest > 数字の3はfizzである FAILED
    java.lang.AssertionError:
    Expected: is "fizz"
         but: was null
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at FizzBuzzTest.数字の3はfizzである(FizzBuzzTest.java:9)
```

## Green

Greenは、テストを通すような**最低限**の実装をします。このときテストコード(設計)に間違いが無ければ一切いじってはいけません。実装を指し示された道への最短距離を突っ走ります。例えば、いくら答えがわかっていても次のようなコードを書いてはいけません。なぜ答えを書いてはいけないかは追って説明をしていきます。

```java
public class FizzBuzz {

    public static String fizzbuzz(int num) {
        if(num % 3 == 0) {
            return "fizz";
        }
        else {
            return null;
        }
    }
}
```

最低限の実装とは、以下のような実装です。

```java
public class FizzBuzz {

    public static String fizzbuzz(int num) {
        return "fizz";
    }
}
```

## Refactor

特に何もなければスキップします。


## 再びRed

次のTDDサイクルになりました。やることは同じです。落ちるテストを書いて、次の道を指し示します。

```java
public class FizzBuzzTest {
    @Test
    public void 数字の3はfizzである() {
        assertThat(FizzBuzz.fizzbuzz(3), is("fizz"));
    }

    @Test
    public void 数字の5はbuzzである() {
        assertThat(FizzBuzz.fizzbuzz(5), is("buzz"));
    }
}
```

今はどんな数字が来ても"fizz"を返すマンなので間違いになります。

```
FizzBuzzTest > 数字の5はbuzzである FAILED
    java.lang.AssertionError:
    Expected: is "buzz"
         but: was "fizz"
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at FizzBuzzTest.数字の5はbuzzである(FizzBuzzTest.java:14)
```

## 2度目Green

もちろん先程のように、いきなり答えを書いてはいけません。最低限の実装だけを心がけます。頭を使ってはいけません。テンポよく次のサイクルに行くことを考えましょう。

```java 
public class FizzBuzz {

    public static String fizzbuzz(int num) {
        if(num == 3) {
            return "fizz";
        } else {
            return "buzz";
        }
    }
}
```

## 再び無視されるリファクタリング

まあまだ良さそうですね・・・。


## そろそろ分かってきたRed3

3以外の3の倍数をぶっ込んでみましょう。

```java
public class FizzBuzzTest {
    @Test
    public void 数字の3はfizzである() {
        assertThat(FizzBuzz.fizzbuzz(3), is("fizz"));
    }

    @Test
    public void 数字の6はfizzである() {
        assertThat(FizzBuzz.fizzbuzz(6), is("fizz"));
    }

    @Test
    public void 数字の5はbuzzである() {
        assertThat(FizzBuzz.fizzbuzz(5), is("buzz"));
    }
}
```

else節に入って`"buzz"`を返すので、Redになります。

```
FizzBuzzTest > 数字の6はfizzである FAILED
    java.lang.AssertionError:
    Expected: is "fizz"
         but: was "buzz"
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at FizzBuzzTest.数字の6はfizzである(FizzBuzzTest.java:14)
```

## やっとこさ実装Green

最低限の実装を推し進めようと、すると以下のような意地悪なコードを延々と書くこともできますが、ここは要件に立ち返って実装をしましょう。

```java
public class FizzBuzz {

    public static String fizzbuzz(int num) {
        // 流石にこれはいじわる！
        if(num == 3 || num == 6){
            return "fizz";
        } else {
            return "buzz";
        }
    }
}
```

はい。これが素直な実装ですね！ なぜここでいきなり、この実装をしてはいけないかをネタバラシしましょう。numが3のケースでこの実装をしたとしましょう。そうしたらnumが6の場合のテストを書くでしょうか？ 人間は基本的に楽な方へ楽な方へ心理が働くので、おそらく書かない場合が想定されるでしょう。今回はFizzBuzzがお題なので、「間違えないだろうこんなもの。」という認識でいますが人間は間違いを犯す生き物で、普段の簡単な実装も検証をサボってミスを犯しがちです。

```java 
public class FizzBuzz {

    public static String fizzbuzz(int num) {
        if(num % 3 == 0) {
            return "fizz";
        } else {
            return "buzz";
        }
    }
}
```

一旦ここでテストのサイクルを止めて、まとめたいと思います。ここでの重要なポイントは、テストのサイクルをテストファースト(Redファースト)から始めることにより、実装の先行を防ぎテストによって実装がカバーされている保証を得るということ、テストが不足してしまうこと。そしてベイビーステップをすることで、テストファーストと同じ問題を解決することと、敢えて一度に対峙する問題の粒度を小さくして実装のテンポを上げること(脳みそを使わないこと)が非常に重要でした。今までTDDをしたことがない人にとっては、マインドセットの切り替えが非常に大事で、そのため**トレーニングを日々積むこと**が大切です。練習をせずにいきなり活躍するスポーツ選手などはいません。簡単な例からTDDのサイクルを守ることと、ベイビーステップの感覚を掴むところからやってみてください。

# テストの早さと量

TDDは遅いかどうか、ということがよく議題として挙げられます。テストを書く分、開発スピードは遅くなってしまうのでしょうか？ それでは、先程のFizzBuzzの例で考えてみましょう。FizzBuzzを一気に実装します。そのまま納品としますか？おそらくしないでしょう。UIを経由するかCLIなのか要件はわかりませんが、おそらく実行して確認をするでしょう。

`printf`デバッグしますか？ もしするのであれば、おそらく以下のような手順で確認するのではないでしょうか？

- 3がfizzであること
- 次に5がbuzzであること
- 立ち返って 6がfizzであること

あれあれ？ 何か見覚えがありますね・・・。そうです。TDDとは、実際に自分がアプリケーションを確認するフローと同じなのです。違いは何でしょうか？ 実行確認はその場限りの検証でしかありません。コードに変更が起きたときには、その確認は無意味になりますし確認したのは「あなた」だけになります。これが実プロダクトのような機能が複雑に絡み合ったものを想像してみましょう。TDDしますか？ 手動確認しますか？

テストの量については基本的に、自分の不安が無くなるまで書き続けます。その上でテストコードで重複コードが大きくなってくるでしょう。そのときは適切なリファクタリングやParameterizedテストなどを試すと良いでしょう。