コラム

  • 2021.06.03
  • 開発
  • #テスト実行
  • #テスト設計
  • #ノウハウ
  • #基礎知識
  • #開発手法

テスト駆動開発(TDD)とは?目的や種類、メリット・デメリットまで徹底解説

2000年代初頭に提唱されたことがきっかけで1つの開発手法として取り入れられるようになった、テスト駆動開発(TDD)について取り上げてみたいと思います。

テスト駆動開発(TDD)とは?

テスト駆動開発(Test-Driven Development)の略称としてTDDと呼ばれています。名称的にテスターが行うテストのように見えてしまうため、誤解を避けるために「ふるまい駆動開発(Behavior Driven Development )」と呼ばれることもあります。
簡単にご説明すると、テストファーストな開発手法の1つで、従来行われてきた開発プロセス≒“ウォーターフォール型”に代表されるような「設計->実装->テスト」のような直線を描くようなスタイルに対し、「テスト->実装->リファクタリング」を何回も繰り返してプロダクトを成長させていくような開発手法です。
主に、「レッド・グリーン・リファクタリング」から構成されています。

1.レッド:動作しない、おそらく最初のうちはコンパイルも通らないテストを1つ書く。
2.グリーン:そのテストを迅速に動作させる。このステップでは罪を犯してもよい。
3.リファクタリング:テストを通すために発生した重複をすべて除去する。

(引用元)『テスト駆動開発入門』 | Kent Beck 著 | 和田 卓人 訳

この手の内容で著名な書籍「テスト駆動開発入門」(Kent Beck 著 | 和田 卓人 訳)では上記のように記載がありますが、わかりやすくご説明すると以下になります。

1.レッド:まずは仕様に対して失敗する(エラーになる)テストコードを書きます。
2.グリーン:1.をもとに成功する(パスする)コードを書きます。
3.リファクタリング:できたものに対して余分なものをそぎ落としたりし、きれいに整えていきます。

具体的な内容については後述していきたいと思います。

テスト駆動開発のイメージ

図:テスト駆動開発におけるサイクルを図にしたもの

テスト駆動開発の目的

そもそも、なぜこのようなテスト駆動開発を行うのでしょうか。「テスト駆動開発入門」では、

「動作するきれいなコード」、ロン・ジェフリーズのこの簡潔な言葉は、TDD(テスト駆動開発)の目標である。動作するきれいなコードは、あらゆる理由で価値がある。

(引用元)『テスト駆動開発入門』 | Kent Beck 著 | 和田 卓人 訳

と書いてあり、きれいな動くソースコードを書くことを目的としています。
このきれいなコードを書いていくには、一般的に2つの方法がありますが、テスト駆動開発では後者になります。
1.きれいなコードを書きながら動作するように直していく
2.動作するコードを書きながらきれいに直していく
「動作」していなければ製品としての価値はないというところに1つ理由があります。
また、フィードバックが早ければ早いほど修正は容易なものになります。そのため、まずは動くように書き、フィードバックを得て素早く修正していきます。

テスト駆動開発の種類

テスト駆動開発には4種類のテストがあります。

●ユニットテスト
●統合テスト
●回帰テスト
●受入テスト

基本的には、ユニットテストと統合テストの両方が連携してプログラムを標準的に保つ必要があり、テストの大部分を占めることになります。
ユニットテストでは優れた設計者は、設計したコードが失敗したかだけではなく、なぜどのように失敗したのか、なぜ重要なのかを考えます。
統合テストでは、システムを適切に保持するルートや方法など、プロセス全体をどのように実行するかについての豊富な知識が必要になり、基本的にそれぞれの単体テストに合格する特定のコードが最終結果に向かって連携して機能するかどうかを確認しています。

テスト駆動開発のデメリット

開発工数が膨らんでうまくいかないイメージ画像

テスト駆動開発を行うにあたって知っておくべきデメリットとしては以下があります。

●仕様が変わることでテストコードを書き換える必要が出てくるため、メンテナンスが大変です。例えば、仕様変更に伴ってテストケースを考えるときに、考慮する内容が増えたり、テストコード自体が増えたりもします。
●上記の影響で開発時間が膨らみ、開発コストが膨らんでいきます。特にテストコードを書くのに時間がかかるため、開発初期段階での負担が大きくなる傾向があります。
●いままでと開発手法から大きく変わるため、時間や労力が必要になります。意図したとおりのテストコードをつくるには、ある程度の経験と慣れが必要になってきます。
●動作が決まっていない処理を事前にテストコードを書いて実装することは不可能です。(当然のことですが)また、セキュリティソフトウェアや並列処理、GUI(グラフィカルユーザーインターフェース)を扱うものなど、テストがやりにくい場合なども存在するため、万能とは言い切れません。

テスト駆動開発の失敗例

いくつかテスト駆動開発を行っていくうえでありがちな失敗例について、Matheus Rodrigues氏がBLOGにて22項目のアンチパターンを解説しているのですが、最大のアンチパターンについてご説明します。

嘘つき (The Liar)
すべてのユニットテストが、すべてのテストケースをパスしているように見えるが、意図されたテストがなされていないことが発見される。

よく見るとテストすると主張しているものをテストしていません。クラス/メソッド名にちなんで名前をつけることができますが、実際には違う別のクラス/メソッドをテストしていることがあります。そうならないために、テスト名を実装時に一致する名前に更新する必要があります。

巨人(The Giant)
正当にオブジェクトをテストできているものの、1つのユニットテストが数千行からなる大量のテストケースを有している。これはテスト対象のオブジェクトが“God Object”であると示唆している。

“God Object”とは神のみぞ知る状態で何もかもを成すことができるレベルを指しており、1つのクラスにテストコードが異常にたくさん羅列されていたりします。クラスが大きい≒影響範囲も大きくなりメンテナンスも大変なものになります。オブジェクトを適切な粒度で分けて書いていく必要があります。

実行に時間がかかるテスト(Slowpoke)
テスト駆動開発では「レッド->グリーン->リファクタリング」のサイクルをスムーズに回していく必要があるが、1つのObjectに時間がかかってしまうテストのケースがある。1つだけなら遅いテストかもしれないが、時間の経過とともにどんどん遅くなっていく。

テストが遅くなる原因としては、テストの過剰な設定です。テストへのSetupが多すぎて遅くなります。また、テストのなかに隠れた依存関係があり、DBにアクセスしようとしているために速度の低下を招くことがあります。
単体テストを高速に実行する必要があるため、回避策としては、メソッドのセットアップ時により小さなメソッドに分割して管理する必要があります。隠れた依存関係を見つけたときも、独自のクラスに分離し、見えるようにします。

テスト駆動開発のメリット

テスト駆動開発を行うことでどういったメリットがあるのかというと、以下が代表的なものになります。

機能を実装するために必要な最低限のコードを作成していきますので、実装がシンプルになります。シンプルであるがゆえに不具合が少なくなります。また、QA段階で確認していたような部分なども開発段階で不具合を多数発見し修正していくことができるため、結果的に品質が上がっていきます。

テストコードを書いていくためには、そのプロダクトの仕様を理解する必要があり、必然的に仕様への理解が深まっていきます。また、仕様齟齬に気づくこともあり、コードを書いているときに気づくようなことにはなりにくくなります。

追加のコーディングやリファクタリングを行う際に、プログラムを壊していないか確認しやすくなります。簡単にテストができるようになればエンジニアも安心して作業することができ、心理的負担が下がっていきます。

テスト駆動開発のサイクル

開発のサイクルイメージ

基本的なテスト駆動開発(TDD)のサイクルについて詳しく説明していきます。

主なステップ:
1.失敗するテストコードを書く
2.成功するコードを書く
3.リファクタリングする

1.失敗するテストコードを書く

まずは、実装したい機能に対して実現するコードが書かれていない場合でも、その機能のテストコードから先に書いていきます。パスするテストではなく、“必ず失敗する”テストを書き、実行します。
実装する前にテストコードを実行しますので、当たり前ですが実装されていないプロダクトコードについてのテストコードなので失敗します。これが、「失敗するテストを書く」ということです。
この工程を“レッド(Red)”と呼ぶこともありますが、テストツールを使うときにテストが失敗すると赤色でエラー表示されることから、“レッド(Red)”と呼ばれるようになったようです。

2.成功するコードを書く

テストコードが書けたら機能の実装を行っていきます。全段階で失敗するテストを書いていることや、十分に仕様への理解も進んできていることもあり、テストにパスするコードを書いていきます。なお、ここでは完璧なコードを書くことを目的とはしていないので、テスト条件をすべてクリアするだけの“最小限“のコードを書いてもらいます。最初の段階では固定値を返すなどから始まりますが、サイクルを繰り返すごとに徐々にロジックを追加していきます。
この工程ではあくまで要求を満たしたコードが正しくテストをパスできるかを確認する作業となり、チェックされる機能外に余分なコードを書かないことが期待されます。
この工程を“グリーン(Green)”と呼ぶことがありますが、今度は反対でエラーがなくなることでテストツールを使用しても緑色(Green)のバーが伸びていくことからきているようです。

3.リファクタリングする

1つ前の工程で書いたコードをテストにパスできる状態を維持しつつきれいにしていく作業を行います。例えば、抽象化できるところを抽象化したり、変数名を直したり、メソッド化したりなど、拡張性や保守性を改善する工程が該当します。
ここで気をつけるのが、「テストをパスしたからこのままで良い」ではないということです。汚いコードをそのままにしておくと、後で収拾がつかなくなり、修正負担が増えて手に負えなくなっていきます。したがって、1つ前の工程を達成したら、その都度リファクタリングを行っていくようにします。

リファクタリングが完了しても実装が完了していなければ、最初の工程に戻り、新しいテストコードを追加していき、実装が完了するまで繰り返していきます。

まとめ

ここまでテスト駆動開発についていろいろと説明してきましたが、テスト駆動開発を利用することによるデメリットやメリットを少しは感じていただけたのではないかと思います。
慣れるまでは大変かもしれませんが、テストコードを書くことでシステムへの理解が深まり、無駄のないコーディングを行うことができ、不具合の早期発見や開発者の負担軽減などが実現可能でもあります。
大きなシステムであるほど、大きな効果が得られると思いますので、実際のシステム開発でも積極的にテスト駆動開発を取り入れてみてはいかがでしょうか。

関連サービス