どこまでユニットテストを書くべきかについて、現時点・現在地での考察

# はじめに

最近私が所属しているチームで、新たに追加したメソッドにユニットテストを書くべきか、書くならどこまで、どう書くべきかという議論が起こりました。

そのことについて私個人・現時点・現在地でどうすればよいのかを考えたので、言語化しておこうと思います。


# 先に現時点・現在地での結論
チームが持っているソースコードと、今いるメンバーによります（そりゃそうだ）。

少なくとも弊チームでは、書けるところには全部書くつもりで臨むべきだと思っています。


# 前提
Javaで記載されたソースコードににおけるユニットテストに関する議論です。
- Java8
- JUnit5

## 弊チームの現状
- 弊チームが担当している製品、その中のサブシステムのソースコードには、ユニットテストが書かれていない箇所のほうが多い
- この1年でユニットテストを書き始めたメンバーが多数
    - 私自身も[ユニットテストを書き始めて](https://qiita.com/moromi25/items/9358c716cb7d70fde31a
)から1年ちょっと経ったところ
    - 最近[こんなこと](https://qiita.com/moromi25/items/61a8ae8750bec3078c10)もやらかした…


- ここ2ヶ月で日常的にユニットテストを書いているチームから弊チームに1人メンバーが異動してきた



# 今回の議題
下記のようなテストコードに、テストを書くべきか？というもの。
```java:追加したテスト対象の製品コード（イメージ）
 public boolean useThisOption(Session session) {
    try {
        int providedOption = OptionsProvider.getInt(session, "options_name");
        if (providedOption == 1) {
            return true;
        } catch (Exception e) {
            throw e;
        }
    }
    return false;
 }
```
```java:OptionsProvider.java（イメージ）
public static String getInt(final Session session, String key) {
    String val = getFromCache(sesion, key);
    if (val == null) {
        val = getString(key);
    }
    return Integer.parseInt(val);
}
```
一見なんのことはないメソッドなのですが、
- `OptionsProvider.getInt`がstaticメソッド
- `OptionsProvider.getInt`はサーバー起動後初めて`options_name`を取得するときのみDBアクセスをする
    - 取得したデータをセッション情報として保持する
- Sessionはセッション情報管理用オブジェクト（ユーザーがサービスログイン時に生成され、ログアウトとともに破棄される）

このあたりが簡単にはテストコードを書かせてくれない要因になっていました。

担当開発メンバーはテスト実装にあたって`OptionsProvider.getInt`部分のモック化に悩んでおり、チームのモブワークの時間に相談を持ってきました。その中で今回の議論が起こりました。


## テスト不要派の意見
基本的にはテストコードは書いていくべきだとは思っているが、今回のメソッドに対しては追加しなくてもいいのでは。という意見。[^基本的には書くべき]
- モック化にモック化を重ねることになり、何をテストしたいのかがわかりづらくなる
- テストコードを読むときに、本当にテストしたい部分に対してモック化のためのソースコードが多くて読むのが大変
- テストを書こうともがく時間に対して書き上がるテストコードの内容が薄い[^テスト経験の浅さ]

[^基本的には書くべき]:テスト不要派のメンバーも、基本的にユニットテストは書くべき、という姿勢です。また、不要派のメンバーは現チームの中では最もテストコードを書いている1人です。今回はその前提があったうえで不要という意見でした。

[^テスト経験の浅さ]:もう少しメンバーのユニットテスト力や製品ソースコードのテスト容易性が上がってくることでこの時間は減ってくるはずですが、まだまだ時間がかかるのが現状です。

## テスト必要派の意見
ユニットテストを書かないという選択肢はそもそもない。ソースコードを書いたら当然テストもセットで書くべき。という意見。
- テストしたいのは後半のtrue/falseを返す部分。`OptionsProvider.getInt`しているあたりは今回追加した分ではないので、モック化で問題ない
- モック化などの初期化処理はsetUpメソッドなどにまとめたり、専用のUtilクラスなどを作れば可読性はそこまで損なわれない

## 私の意見
### 今回追加したメソッドについて
少なくともユニットテストは書くべきだと感じます。

理由は以下の通りです。
- テストはもともとある`OptionsProvider.getInt`の担保がしたいわけではなく、そのあとの分岐のテストや仕様を残すが目的（必要派の1つ目の意見に同意）
- 今回追加したメソッドに関する機能の仕様的には0と1しか入りえないが、サービスダウンの回避のため、1だったらtrue、それ以外をfalseにしている（つまり0未満／2以上の値や数値以外が入った場合でもfalseを返す）。これが意図的であることをテストコードを通じて明確にするべき


### どこまでユニットテストを書くべきかについて
現時点では、自分が追加したメソッド、if文の分岐などに対して、すべてユニットテストを書いてみようとするべきだと思います。
「ここはどうやったらテストが書けるか」を試行錯誤して、とにかく書いてみるべきではないかな、と。

この模索を経てユニットテストの書き方のバリエーションの引き出しを作ることもできますし、
どんなソースコードだとテストが書きやすいか、書きにくいかを感じ取ることができるようになるはずです。

テストを書かないという判断を下せるようになるのはその後だし、判断の結果不要なのであればそのときに消せばいいのではないかなと思います。

ユニットテストが有効な箇所を見極め、体系的・戦略的に考えて書くのが理想だと思います。
製品全体の品質を担保するために、ユニットテストだけでなく結合テストやE2Eなども効果的に組み合わせながらアプリケーション全体のテストの自動化を進めていくべきだとは理解しています。

ただ、テストを書くべきか書かざるべきかの判断ができるほど、製品コードが整然としていませんし、自分たちの力もついていません。

製品コードの複雑性については、自分たちだけでなんとかできる領域と、他チームの力を借りる／チーム間で足並みを揃える必要がある箇所があります。[^チームが担当するソースコード間の結合度]
この点についても少しずつ改善していくべきところではあります。

一方、ユニットテストで可能な限りテストを実行する方法を模索することは、個人レベルですぐに始められる部分で、研鑽を詰める部分だと思います。


[^チームが担当するソースコード間の結合度]:現状、同プロダクトの中に複数のサブシステムが存在し、そのサブシステム間で密結合になっている部分も少なくありません。

# おわりに
こういう議論が、メンバー同士で巻き起こっていること自体がすごくうれしかったですし、楽しかったです。
1年前はそもそもテストコードを書くか？という議論すら起こらなかったと思います。

もっとこういう議論をチーム内で繰り広げてほしいし、今回議論に参加していなかったメンバーももっと喧々諤々やっちゃってほしいなと思いました。

と、えらそうなことを言いつつ、私も終盤までは議論を聴いていました。
役職上チームのマネージャーという立場にいるため、自分の意見が“鶴の一声”にならないかなと心配してしまったためです。
そうならないように注意はしなきゃいけないと思いつつも、そんなこと気にしないような関係性を作っていきたいなと思いました。これはまた別のお話ですね。