テスト駆動開発が向くシチュエーション

この記事は[ラクスパートナーズ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/rakus-partners) 25 日目の記事です。  
メリークリスマスです！🎄🎁

## はじめに
みなさんテスト駆動開発（以下、TDD）やってますか？  

- 聞いたことはあるけどやったことないなぁ
- テストから書くって本当に良いことあるの？
- やったけどテストコードの保守が大変なんだけど？

など色々思うところがあることと思います。  
私が参画中のチームで最近 TDD を実践してみて結構うまくハマったので「こういうシチュエーションは TDD よさげじゃね？」と個人的に思ったポイントをまとめてみたいと思います。

## この記事で扱わないこと
この記事では TDD の進め方は省略し、向いているシチュエーションの紹介に絞ります。

## TDD が向いていると感じたシチュエーション
ここに書いているものが全てではありません。
他にも TDD が向くシチュエーションはたくさんあるはずなので是非コメントで「こんなシチュエーションで TDD 使ってたら良い感じでした！」を教えてください。

### 1. 既存のビジネスロジックを別の言語で書き換える
いきなりあまりないシチュエーションを挙げてしまって恐縮です。  
参画中の案件では「既存システムのリアーキテクチャ」を行っており、その一環として使用言語を Scala から Go に変更しました。  
そうなると当然 Scala で実装していたロジックの一部を Go に置き換える必要があります。  
既存コードでテストコードを実装してあることが前提とはなりますが、使用言語を変更するときは TDD を使うとスムーズに進められます。  

まず、既存コードのテストコードを新しい言語で書き換えます。  
**この時テストコード内にテストケースが書かれている場合は TSV や CSV などの外部ファイルに切り出すことをオススメします。**  
テストケースを外部ファイルに切り出すことでテストコードの書き換え量が減るので言語変更が楽になります。  
あと、もし使えるのなら GitHub Copilot Chat などを使うとテストコードの言語変更はかなり簡単にできます。（Copilot Chat に「このテストコードを〇〇言語に書き換えて」と伝えるだけ）  

既存のロジックから大きな変更がない場合に限りますが、デバッガで既存ロジックと新規ロジックでテストを実行したときに処理結果の見比べながら実装を進められると効率的です。

### 2. 複数段階の中間データがある
次に中間データを何段階にも渡って作る場合も TDD が有効です。  
**中間データが作られる段階ごとにメソッドを切り分けられるようにテストコードを作成しましょう。**  
例えば「文字列のリスト（`List<String>`）から "3,000" 以上の数字を `Int` 型として抜き出してくる」という処理を行う場合を考えると、リストの各要素に対して
- スペース、カンマ等を除去
- 全角数字、漢数字を半角数字に変換
- 半角数字以外を除去
- 3,000 を超えるか判定

という感じに分けられると思います。  
このように中間データが作られる単位でテストを作成することで各段階の処理が正しく行われているかを常に確認しながら開発を進められます。

あとは
- 開発者以外のメンバーに各段階の処理内容を説明するときに、入力値と期待値をセットで説明できる（伝える側のメリット）
- 口頭で説明されなかったとしても、テストコードを読めば処理内容がすぐにわかるのでコードレビューなどが楽（受け取る側のメリット）

など副次的な効果も期待できます。

### 3. ビジネスロジックが複雑
プログラミングをしていると
- 複雑な正規表現
- 再帰を使った処理
- 処理中に状態が変化する

など「**人間の脳みその限界**」を感じることがあると思います。  
こういった場合も TDD が有効です。

これらの複雑な処理は場当たり的に実装しながら検証するとほぼ確実に
- 想定してなかったパターンの出現
- 修正したら別のパターンでバグ発生

などが起こり、賽の河原の小石積みが発生します。  
なので、最初に検証パターンを思いつく限り書き出しましょう。  
書き出したらテストコードを作成し、あとは TDD の流れに沿って実装していきます。  
テストが全て緑になったら完了です。  
検証パターンがしっかりと網羅されていればあとどれくらいで実装が完了するかの目処が立ちやすいのもメリットです。

### 4. 長期運用が見込まれるプロダクト
この項目に関しては TDD が向いてるというよりは「長期運用するならきちんとテストコードは作っておきましょうね」というニュアンスに近いです。

TDD で開発を行った場合、実装完了と同時に
- テストコードも作成されている
- プログラム全体が適度な大きさでメソッド分けされている

状態になるのでプロダクトコードが長期に渡って保守しやすい状態になります。  
TDD は初期コストが大きくなる開発手法ですが、後にかかる保守運用コストを抑えることで長期的に見れば初期コストは回収可能（なはず）です。  
もしくは「特にバグが出やすい部分やビジネスロジックが複雑な部分だけでも TDD を試す」などの工夫をすると TDD の効果を実感しやすいかもしれません。

### 5. メンバーの入れ替わりが多い
新しいメンバーが頻繁に参画するチームでの開発も TDD が向いているシチュエーションのひとつです。  
チームに新メンバーが参画した際、新メンバーはしばらくの間仕事の進め方、既存プログラムの仕様、ドキュメントが置かれている場所など色々なことが分からず仕事をしにくい期間があることと思います。  
こういったタイミングでは **「TDD + ペアプログラミング（モブプログラミング）」の合せ技**で実装を進めることで
- 新メンバーは既存システムのキャッチアップができる
- 既存メンバーはこれからどういった機能を実装したいか説明できる

など、オンボーディング用に新しく資料を作らなくても実務を通しながら自然にオンボーディングを進めることができ、オンボーディングコストの節約に繋がります。  
ただ、この方法は**参画したタイミングによって最初に知る知識がバラつく**ので一長一短あることはご認識ください。

## TDD が向かないシチュエーション
逆に TDD が向かないと思われるシチュエーションもあるので簡単に挙げてみたいと思います。

### 1. プロトタイプ開発で、後に破棄される可能性の高いコード
TDD は最初にテスト作成コストを支払って、その後長期に渡って運用することで本来保守運用コストとしてかかったであろう工数を回収する、という側面があります。  
プロトタイプを作るときのように
- 今後アーキテクチャ・仕様が頻繁に変わる可能性がある
- そもそもプロダクトとして生き残っていくかもわからない

などのシチュエーションでは「TDD の初期コスト > TDD で将来的に削減できるコスト」になってしまい、投資コストの回収ができない恐れがあります。

### 2. 要件定義が進行中 or そもそも機能要件が不明瞭なプロジェク
仕様が明確でない場合はそもそもテストを書きようがないので TDD を進める以前の話になります。  
ちゃんと仕様を詰めましょう。

## まとめ
- TDD は適したシチュエーションで使えば様々なコストを削減できる
- オンボーディングとの相性も良い
- が、何でも TDD やれば良いってものではない

|TDD が適している場合|TDD が適していない場合|
|---|---|
|長期的に保守が必要なプロジェクト|短期的な試験実装|
|複雑なビジネスロジックがある部分|仕様が不明確で頻繁に変わるプロジェクト|
|エラーが発生すると重大な影響を及ぼす部分|プロトタイプや実験的な開発|

以上、私が TDD に向いていると思ったシチュエーションでした！  
ハマれば快適な開発体験を得られるので是非手札の一枚として覚えておいてください。  
最初から全てのプロジェクトに TDD を適用するのは大変だと思うので、小規模な機能やサブプロジェクトで試し、その効果を体感してみてください。