🚀

TDDってなんだ?実施所感(feat.UnitTest)

2023/07/05に公開

TDD(Test-Driven Development)とは?

最初にテストコードを作成し、実際のプロダクションコードを後から作成する開発方法を指します。

私たちがTDDに執着する理由

  1. 私は人間だ。 要件の追加や変更によってソースコードを修正し、不安を感じることは避けたいです。
  2. 私は普通の人だ。 難しいアルゴリズムや実装の難易度が高い問題に直面した場合、テストコードなしでプログラミングを試みると、天才でない限りかなりの時間がかかります。TDDを通じてテストケースを一つずつ追加しながらプログラミングを進めると、大きな問題なく開発が行われます。
  3. 私はプログラミングが好きだ。 自動化されたテストを通じてフィードバックを受け取るサイクルは最初は遅いかもしれませんが、反復回数が増えるにつれて指数関数的に速くなります。フィードバックが速いほど、バグを見つけるタイミングが早くなり、より多くのトライアンドエラーに時間を割くことができます。

TDDの原則を守ることで、最初から完璧な設計をするのではなく、段階的に設計を改善していくことができます。 そして、段階的に設計を改善していくことで、変化に迅速に対応する方法を身につけ、オーバーエンジニアリングを防ぐことができるようになります。

TDD Cycle

  1. 失敗するテストを実装する。
  2. テストが成功するようにプロダクションコードを実装する。
  3. プロダクションコードとテストコードをリファクタリングする。

TDDのメリット

  1. 変化への恐れを減らしてくれる(リファクタリングが容易である)。
  2. デバッグ時間を短縮してくれる。
  3. 機能する文書の役割を果たす。
  4. TDDを行うと自然にテストカバレッジが高くなる。

  1. オーバーエンジニアリングの防止


6. 設計に対するフィードバックが迅速

TDDの誤解

TDD(テスト駆動開発)はソフトウェア開発手法であり、多くの利点を提供しますが、しばしば誤解されることがあります。以下にいくつかの誤解を示します。

  • 「テストコードを作成するのに時間がかかる」:初めの段階では、テストコードの作成に追加の時間と労力が必要になる場合があります。しかし、TDDは効果的なデバッグ、メンテナンス、および設計の改善を通じて、開発プロセスでの時間を節約し、将来的に発生する可能性のある問題を事前に防止するのに役立ちます。
  • 「テストコードは複雑でメンテナンスが困難」:TDDで作成するテストコードは簡潔かつ明確に書かれるべきです。これにより、テストの目的や期待される結果を明確に理解し、コードの欠陥を特定し修正するのに役立ちます。テストコードはプロダクションコードと同じレベルのメンテナンスが必要であり、テストコード自体もリファクタリングされて改善されることがあります。
  • 「テストコードは全体的な開発スピードを低下させる」:初めの段階ではテストコードの作成に時間がかかるかもしれませんが、TDDはバグの迅速な発見と修正を支援し、開発スピードを向上させることができます。また、テストコードはコードの予想される動作を明確に定義し、機能の理解を支援する役割を果たすため、将来の変更に対する安定性と信頼性を向上させるのに役立ちます。
    TDDの核心は、テストを開発プロセスの中心要素とし、テストを通じてコードの動作を検証し、問題を素早く特定することです。これにより、コードの品質向上とメンテナンスコストの削減を実現できます。

TDDを失敗する人が行うテスト

  • テストケースが、コードが目指す価値や機能をテストするのではなく、その機能をどのように実装しているかをテストします。

  • 結果的に、テストケースと実装の結合度が高くなります。

  • implementationをリファクタリングを行うと、すべてのテストが壊れてしまいます。

結局、私たちは実装ではなく、設計、つまりインターフェースをテストする必要があります。

このようにすると、実装された具体的な実装をいくらリファクタリングしても、テストケースはインターフェースをテストするため、テストケースは壊れません。

実際TDDを使ってみた!

学習環境

使用言語はJavaScriptで、テストフレームワークはJestです。
作成したいアプリケーションはFizz Buzz 問題をベースにしたいと思います。
仕様はシンプルです。

入力エリアに 3 の倍数を入力したら、出力エリアに "fizz" を表示する
入力エリアに 5 の倍数を入力したら、出力エリアに "buzz" を表示する
ただし、入力エリアに 3 と 5 の倍数を入力したら、出力エリアに "fizzbuzz" を表示する
それ以外の数字を入力したら、そのまま出力エリアに表示する

初心者の視点でTDDを学習することが目的なので、できるだけシンプルに仕様を設定しました。
仕様は開発を進めながら修正される可能性があります。それが少し現実的であり、TDDが要件の変更にどのように対応するかを見るのも良いと判断しました。

実装およびテストの過程は、この記事に含めると内容が非常に膨大になるため、参考にした記事や動画のリンクを提供します。

これを参考にして実際にTDDを試してみてください。

所感

  1. テストが開発を主導する
    継続的なコードの整理
    開発プロセス中にリファクタリングを並行して行える
    コードが汚くなることを小まめに整理しながら開発することができた

  2. 迅速なフィードバック
    自分が作ったコードが正しいかどうかをすぐに知ることができた

  3. テストをパスする範囲だけコードを作成
    不要なコードを事前に書かない

テストコードの利点は、誰もが知っている通り、コードを変更しても機能が正しく動作するかを素早く確認できるという点です。

それ以外にも、個人的にかなり良い副作用を感じる部分は、開発者として自己効力感を感じることができるという点です。

学生時代に数学の問題集を解いて採点するとき、丸が一つずつ増えるごとに気分が良かった記憶がありますが、すっきりと合格したテスト結果を見ると、なんだか爽快で気分が良くなるようです。

開発は勉強すればするほど、自分自身が不十分だと感じることも多いですが、こうして些細なことかもしれませんが、自分が作ったコードがテストに合格したという喜びを与えてくれることが最大の利点ではないかと思います。

Unitテストとは?

ユニットテスト(Unit Test)は、1つのモジュールを基準として独立して実施される最小の単位のテストです。

ここでのモジュールは、アプリケーション内で機能する1つの機能やメソッドとして理解することができます。
  • 例えば、ウェブアプリケーションでのログインメソッドに対する独立したテストが1つのユニットテストとなり得ます。
  • つまり、ユニットテストはアプリケーションを構成する1つの機能が正しく動作するかを独立してテストするものであり、"ある機能が実行されるとある結果が得られる"程度のテストを行います。

単体テスト(Unitテスト)とTDD何が違う?

多くの人が混同しているが、「単体テストを作る」と「TDDをする」とは異なる考え方であると考える必要があります。
わずかな単体テストを作成して実行しただけで、自分がTDDを守っていると勘違いしていたのではないかと振り返ることになります。

単体テストは文字通り小さな単位のテスト関数を意味し、TDDは一つのプログラミングの習慣やパターンを指しています。TDDが身につくまで反復練習をする必要があります。TDDを守りながら進めると、リファクタリングが必須的に行われることになり、その過程で自然にオブジェクト指向設計やクリーンコードについて考えるようになるでしょう。

ユニットテストの目的

  • バグの発見:ユニットテストにより、コードの欠陥やエラーを特定することができます。小規模なテストケースを実行して、予測される結果と実際の結果を比較し、予期しない動作やバグを発見することができます。
  • コード品質の維持:ユニットテストは、コードの一貫性と品質を維持するための支援をします。小さな単位のテストケースを作成することで、コードを改善し、より堅牢で拡張可能なコードを作成することができます。
  • リファクタリングのサポート:ユニットテストは、コードのリファクタリングをより安全かつ信頼性のある方法で実行するのに役立ちます。コードを修正または再構築する際に、テストケースを実行して既存の動作が正しく維持されているかを確認することができます。
  • ドキュメンテーション:ユニットテストは、コードの動作に関するドキュメンテーションの役割も果たします。テストケースを作成することで、コードの予想される動作や機能要件を明示的に表現するため、コードの使用方法や機能を明確に理解することができます。
    ユニットテストは、ソフトウェア開発の生産性と品質向上のために広く使用される手法の一つです。

おわりに

TDD導入をためらう理由の一つは、
初期に発生する時間コストがかなり大きいと考えられるためです。
私自身もTDDを実施する際、初期に絶対的な時間がかかりましたが、
結局、時間コストを初期に使うか後半に使うかの観点から見ると、
やはり初期に使う方が遥かに少ないという結論に至りました。

では、テストを実装体をすべて実装した後に書いてもいいのではないかと疑問に思うかもしれませんが、  
それでは先述した設計の側面や協力の利点をすべて失うことになりますし、  
一気にテストケースを書くということは、100%成功するHappyケースだけを作成するだけです。  
即座にテストケースを作成するのに追加の労力が必要だと思うかもしれませんが、  
全体の開発サイクルを考えると、このようなテストケースは生産性を向上させます。

さらに、協力に関しても役割の分担が容易になります。
例えば、aとbを入力した場合にxを返すというテストケース、つまりインターフェースを作成し、
その実装を他の人に任せることができます。この例だけでも、
本当に一緒にコーディングする喜びを感じるのではないでしょうか?こうして作成されたテストケースは、
結局のところコードの品質を高め、信頼性のある製品を作ることにつながります。

それでは、皆でTDDを始めましょう。

Reference

Discussion