現在参画している案件のバックエンド開発でテストを導入する必要がありましたが、テストに関する知見を持つ人が自分を含めいなかった事と、テストを導入する熱意が一番あるのが自分だった為、先行して学習を進めました。
その際使用した書籍がテスト駆動開発(Amazonリンク)です。
この本で学べることが多かった為、メモを残すべくこの記事を残します。
また、この記事はTDDの解説をするものでは無く、どちらかというと自分が実践してみてどうだったか、という点をまとめています。
なぜテストを導入する熱量が高かったのか
そもそもなぜ自分のテストに対する熱量が高かったのか、それは以下が理由です。
- 参画前に携わっていたプロジェクトで、テストを導入したいと思う場面が結構あった
- エンジニアとして、テストを書く技術はスタンダードであること
- 転職活動をしている時、テストを書いた経験についてよく聞かれた為、次のステップアップ時にはテストを当たり前に書けている状態にしたかった
つまるところ、2年ほど前からテストを書きたい、書けるようにならなくてはいけないという気持ちが強くなっていました。
その中で、今年の2月まで担当していたプロジェクトでテストを書いているとデバッグが楽になっただろうなと感じることがありました。
そのプロジェクトが始まった当初は、テストの学習コストに見合った恩恵があるか分からなかった為、導入しませんでした。
ただ、プロジェクトが進み、開発中のアプリケーションが複雑になればなるほど、新規機能の実装による他機能の影響が大きくなり、手作業での確認に限界がありました。
とはいえ、リリースが差し迫った状況で、その段階でテストを導入することが難しく、次のプロジェクトでは絶対に導入するぞという気持ちになりました。
テストに関する書籍を探しているところ、ネット上での評価が良かった「テスト駆動開発」と出会いました。
テスト駆動開発とは?
この書籍を読む前は、先にテストをたくさん書いてから実装するイメージでした。
そうでは無く、TDDは以下のサイクルで実装を進めていきます。
- 小さいテストを追加する
- 全てのテストを動かし、失敗があることを確認する
- 変更を行う
- 再び全てのテストを動かし、全て成功することを確認する
- リファクタリングを行い、重複を除去する
テスト駆動開発から実際に引用して解説します。
本書では債券ポートフォリオ管理システムの実装をハンズオンで進めている為、それを一部紹介します。
金額(通貨単位あたりの額)に数値(通貨単位数)を掛け、金額を得る機能を実装したい時、まずは以下のテストを書いてみる。
※パッケージ名、import文省略
public class MoneyTest {
@test
public void testMultiplication() {
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
}
}
上記のテストはコンパイルすらできません。
今、4つのエラーがあります。
- Dollarクラスが無い
- コンストラクタが無い
- timesメソッドが無い
- amountフィールドが無い
上記のコンパイルエラーを解決するべく、Dollarクラスを実装します。
※パッケージ名、import文省略
class Dollar {
int amount;
Dollar(int amount) {
}
void times(int multiplier) {
}
}
これでテストを走らせすることができるようになったので実行すると...
レッドバーが現れ、結果が10となることを期待しているところに0が返ってきていることでテストが失敗しています。
テストを通すための最小限の変更は以下のようになる。
※パッケージ名、import文省略
class Dollar {
int amount = 10; // 直接10で初期化してしまう。
Dollar(int amount) {
}
void times(int multiplier) {
}
}
この状態でテストを実行すると、今度はグリーンバーが出た。
これでサイクルの1から4まで実行したところですね。
ですが、これで実際の用件を満たせている実装では無いため、リファクタリングをします。
以下、最終的なソースです。
class Dollar {
int amount;
Dollar(int amount) {
this.amount = amount;
}
void times(int multiplier) {
amount *= multiplier;
}
}
書籍では、もっと小さいステップで最終的なソースまで実装しています。
途中、数値をベタ書きで書いてテストを通す場面がありましたが、ベテランであればあるほど許し難い行動かもしれません。
ですが、本書では以下のように述べています。
なんでもいいからとにかく動かすという考え方は、優れたエンジニアリングのルールに反するように感じるし、特にベテランの開発者にとっては、審美的にも価値観的にも受け入れ難いかもしれない。それでも、素早いグリーンはすべての罪を赦す。ただし、赦されるのはほんの短い時間だけだ。
ここら辺は、書籍の第28章グリーンバーのパターンを参照してみてください。
ということで、上記のサイクルを回しながら実装していくのがTDDだと認識しています。
実践してみて
書籍を読みつつ、APIの実装を進めてみました。
APIの内容として、Opensearchから取得した値を加工し、JSON形式で返すものです。
以下、必要条件
- Opensearchからデータを取得し、加工した値を返す関数
- 上記関数から取得した値を包括し、ステータスなどの情報を追加して返すAPIの実装
TDDで実装するにあたり、以下の手順で進めてみました。
- Opensearchに接続する関数、返り値をモック化
- Opensearchからの返り値が正しいか確認するテストを追加
- テストを実行し、失敗を確認
- ファイルを作成し、Opensearchに接続部分の関数を追加
- テストを実行し、グリーンバーを確認
- Opensearchから取得した値を加工する関数のテストを追加
- 失敗、レッドバー確認
- 作成したファイルに加工する関数を追加
- グリーバーを確認
...省略
上記の手順で、3歩進んで2歩下がるような感じで進めました。
テスト駆動で開発していくことで、自然と結合度が低いコードになりました。
また、テストを書くことで開発中の不安が少なくなり、精神衛生上とてもよかったです。
「テスト駆動開発」を読むべき理由
上記で紹介したテスト駆動開発は、書籍の情報のほんの一部にすぎません。
この本を通してテスト駆動とはなんなのか、著者であるKent Beckとハンズオン形式で学ぶことができます。
そして今はテストを書くことが当たり前になっているので、リーダブルコードに並ぶ必読書と言っても過言では無いように感じます。
もちろん今後全てのコーディングをテスト駆動で書くのは難しいかもしれません、ですが、一度TDDで書いた経験があれば、テストしやすいコード、しにくいコードがわかるのでTDDでの実装するしないに関わらず学べることは多いと思います。
また、デザインパターンの紹介があるため、プログラミングのスキルを上げる知識を得ることができます。
今の自分に足りない情報
実際にTDDを実践し、いくつか悩むポイントがありました。
- どこまでモック化する必要があるか
- 必要なテストを網羅できているか
特に、書いたコードに対するテストが足りていない気がしています。
この辺は他のテストに関する書籍で勉強したいです。
統括
まだ一周しかできてないのでもう一周したいです。
テストを書くのが楽しいので、ひとまずはTDDでコーディングしていきたいです。