型がある言語で DDD してたらテストはいらないの？


前にとある DDD の座談会で「静的言語で DDD をしているならテストはいらない？」という質問が出て、思ったより多くの人が「そうだ / そうかも」の空気になって驚いたので、ちょっと整理してみることにした。  

結論から言うと、僕の持論は「もちろんコンパイルの恩恵はとてもあるんだけど、思考停止で全てが解決するわけではないし、学びも多いので、書いた方が良い」です。

## 条件分岐は型があってもバグになりやすい
例えば`else if`が増えるような修正は、型があるだけだと割と危険。

```java:Plan.java
public enum Plan {
    PLAN_1,
    PLAN_2,
    PLAN_3;
}
```

```java:Price.java
public class Price {
    private final int value;

    public static Price of(Plan plan) {
        if (plan == Plan.PLAN_1)
            return new Price(100);

        else if (plan == Plan.PLAN_2)
            return new Price(200);

        else
            return new Price(300);
    }
}
```

これに`PLAN_4`を足した。

値段は 400 円にしたかったけど、`else if`を書き足し忘れた。

```diff
public enum Plan {
    PLAN_1,
    PLAN_2,
    PLAN_3,
+   PLAN_4;
}
```

当然コンパイルは通り、`PLAN_4`は 300 円になる。

怖い。  
とても怖い。

### 余談
くだらない例だけど、冗談ではないよ。

`Plan`に応じて`値段`、`明細品目`、`メールの文面`、`システムコード`等々、`else if`を忘れずに書き足さないといけないプロダクトなんて珍しくないはず。  
**大規模でこの手の漏れを策なく防ぐのは割と無謀。**

### じゃあどうすんの
テストを書けば少しはマシになるはず。  

enum の全要素 ( `Plan.values()` ) とかをテストコードにうまく絡めていると、検知できたりする。

```groovy:PriceTest.groovy
// テストコードは groovy と spock を用いた

class PriceTest extends Specification {
    def "#plan -> #exp"() {
        expect:
        Price.of(plan) == new Price(exp)

        where:
        plan        || exp
        Plan.PLAN_1 || 100
        Plan.PLAN_2 || 200
        Plan.PLAN_3 || 300
    }

    def guard() {
        expect:
        Plan.values().length == 3
    }
}
```

シンプルだけど、`Plan`に依存してるテストに`== 3`が入っていると、`Plan`が増減した時に気づける。

`else`を値段判定に使わない、とかもアリ。

```java:Price.java
        else if (plan == Plan.PLAN_3)
            return new Price(300);
        
        else
            throw new RuntimeException("match error");
```

テストコードがあればこれでも気づける。

### 整理: ここが怖いよ
+ 例えば条件分岐の書き方によってはコンパイルが通ってもバグる可能性があるよ
+ 一番最初に書くときは簡単なので「まさかバグんないだろ〜」と思っても、**保守と追記で地獄を見るよ**

## 引数不正なんかは恩恵が得られる、かも
逆に、しっかりやればこんなバグはバッチリ防げる

例１

```java:MailService.java
public class MailService {
    public void send(String itemName, String userName) {
        String mailBody = MailBodyFactory.create(itemName, userName);
        ...
    }
}
```

例２

```java:FooService.java
public class FooService {
    FooRepository fooRepository;

    public void replace(Foo usingOne) {
        Foo newOne = Foo.newOne();
        
        fooRepository.replace(newOne, usingOne);
    }
}
```

どこが間違ってるかというと...

例１

```java:MailFactory.java
public class MailBodyFactory {
    public static String create(String userName, String itemName) {
        return String.format("%s 様 %s のお買い上げありがとうございます", userName, itemName);
    }
}
```

例２

```java:FooRepository.java
public interface FooRepository {
    void replace(Foo usingOne, Foo newOne);
}
```

引数の順番が逆でした。  

### また余談
「知るかよ！」って思った？  
でも大体の場合プロダクトコードの引数順なんて覚えてないし、気をつけながら実装するのは案外とても疲れる。  

さらに、こういうのは下手に動くだけにとてもタチが悪い。

### じゃあどうすんの
`String`は乱用しないで`UserName`と`ItemName`のクラスを作ろう。  
そしたら絶対に逆にできない。

`Foo`の方はドメインクラスを作ってみたみたいだけど、例えば状態ごとにわけてみよう。

```diff
public interface FooRepository {
-   void replace(Foo usingOne, Foo newOne);
+   void replace(UsingFoo usingFoo, NewFoo newFoo);
}
```

これで絶対に逆にならない。  
（状態別クラスについては勢いで一緒にこんな記事を書いて見ました → [全部のステータスを１クラスで表現する神エンティティをやめよう！](https://qiita.com/suzuki-hoge/items/d3ea5940898d85bbc03e)）

大体の目安だけど、**変数名で説明を頑張ってる箇所は、型にしてしまった方が良い**と思う。

赤線になってくれた方がよっぽど楽だし、エディタも補完候補とかを賢く考えてくれる。

ちなみに、似た様な話は以前も書いたので興味があればそちらも見てみてください。
[同じテーブルの値でも違うクラスを用意すると良い感じ](https://qiita.com/suzuki-hoge/items/b8eb2fe9fbe551cfede4)

### 整理: ここが怖いよ
+ 型があると言っても、全部`String`じゃあ恩恵を全然享受できない
+ ドメインクラスを作ってみても、それがちゃんと設計されていなければ恩恵は大きくならない

## テストがあると使いづらさに気づく、かも
例えばこんなコード

```java:CampaignCode.java
public enum CampaignCode {
    CODE_A, CODE_B;

    public static CampaignCode create() {
        if (LocalDateTime.now().getDayOfMonth() < 15) {
            return CODE_A;
        } else {
            return CODE_B;
        }
    }
}
```

テストコードを書こうとすると`now`が邪魔で「あれ、テスト書けないぞ...??」ってなるはずなんだ。

これはつまり作ったプロダクトも`now`でしか動かないので、他の部分のテストを邪魔したり、モッキングや結合試験とかで融通が効かなかったりする。

**テストコードは最初にそのメソッドを使う**ので、こういう「あれ？このメソッド使いづらいな？」みたいなのに気づくチャンスに、結構なる。

（この例は`now`は引数で渡した方が良い）

## まとめ
+ 静的言語で DDD をやっているからと言って、全てのバグが抜けるわけではないよ
  + 条件分岐の例とか
  + 他にすぐ思いつくのだと配列操作とか（前のどこかの処理が間違ってて index out of range が出るとか、よくあるよね）
  + 同じく前処理のどっかがおかしくて`Optional.get`したら`empty`だった、とか
  + イテレータのカーソルが思った位置にないとか
  + java でたまに見るのは`Stream`の終端操作を２回しちゃうとか
+ ドメインクラスを作ってるからと言って、思考停止で型の恩恵が得られるわけではないよ
  + `Foo using`の例とか
+ テストコードは作った処理の使いづらさとかに気づく大事なチャンス
  + 品質面では当然
  + すぐ学習と経験のフィードバックが得られるという意味でも、とても大事
+ （忘れてたのでさらっと追記）リファクタリングを促進するためにも、テストはあった方が良い
  + モデルのアップデートをかけてドメイン層をどんどん直せるためにも、テストコードは大事
  + ただし不必要に書きすぎると逆にリファクタリングを阻害することにもなる

## おわりに
例えば enum の件なんて言語によっては警告やコンパイルエラーが出るんだけど、会場の大きな流れとしては「コンパイルはテストコードを不要にするか」って感じだと思ったので、思い立ってアンチテーゼとして書いて見ました。

本記事はあくまで僕個人の持論です。

最近聞いたある人の言葉を真似るなら「テストいらないって聞いたんだけどマジ？」って思った人は、まずは素直にテストを書きなさい。
と僕は思います。おしまい。