Web制作、Web開発の歩き方

テスト駆動開発入門: コードの質を高める5ステップ

第2話:TDDの第一歩 - テストの書き方

(最終更新日:2023.2.21)

テスト駆動開発のイメージ
この記事は5分で読めます!
(絵が小さい場合はスマホを横に)

「テストから書こう!」

前回、テスト駆動開発の概要について説明した。 今回は、良いテストを書くための原則と、具体的な書き方について説明する。 良いテストケースを書くためには、いくつかの基本原則を理解し適用することが重要だ。 これらの原則は、テストケースが効果的で、メンテナンスが容易で、再利用可能であることを保証するために役立つ。 以下に、良いテストケースを書くための主要な原則を紹介する。


1.テストケースの構造

テスト駆動開発(TDD)のプロセスにおいて、テストケースを書くことは非常に重要な第一歩だ。 テストケースの構造は、効果的なテスト駆動開発を行うための基盤を提供する。 一般的に、テストケースは以下の三つの主要部分から構成されます:準備(Arrange)、実行(Act)、アサート(Assert)。 このアプローチはしばしば「AAAパターン」と呼ばれる。

■準備(Arrange)
テストケースのこの部分では、テストを実行するために必要なすべての前提条件を設定する。 これには、テストするオブジェクトのインスタンス化、テストに必要なデータの準備、依存関係の構成などが含まれる。 このステップの目的は、テストの実行環境を正確に構築することで、テストが特定の状況下で実行されることを保証する。

■実行(Act)
実行段階では、テスト対象のコード(通常は一つの関数またはメソッド)を実際に実行する。 このステップでは、準備段階で設定された条件下でコードがどのように振る舞うかを観察する。 テストを通じて、特定の入力に対する出力やコードの振る舞いを検証することが目的だ。

■アサート(Assert)
最後に、アサート段階では、実行段階の結果を期待された結果と比較する。 このステップでは、コードが正しく動作していることを確認するために、テストの結果が期待通りであるかどうかを検証する。 アサートは、テスト対象のコードが特定の要件を満たしていることを保証するための具体的なチェックだ。

このAAAパターンを用いることで、テストケースは明確で理解しやすく、再利用可能な構造を持つことができる。 テストケースをこのように構造化することで、開発者は何がテストされているのか、どのような条件でテストが行われているのか、 そしてテストの結果が期待通りであるべき理由が何であるかを簡単に識別できる。 このクリアな構造は、テストの保守性を高め、プロジェクトの他のメンバーがテストコードを理解しやすくする。

AAAパターンで確実に

2.良いテストケースを書くための原則

良いテストケースを書くためには、いくつかの基本原則を理解し適用することが重要だ。 これらの原則は、テストケースが効果的で、メンテナンスが容易で、再利用可能であることを保証するために役立つ。 以下に、良いテストケースを書くための主要な原則を紹介する。

■単一責任の原則
各テストケースは、一つの機能または条件のみをテストするべきだ。 これにより、テストが失敗した場合の問題の特定が容易になり、テストの意図が明確になる。

■明確な意図
テストケースは、何がテストされているのか、そしてなぜそのテストが重要なのかを明確にするべきだ。 テストの名前や構造を通じて、その目的を明確に伝えることが重要になる。

■TDDの普及
TDDは、XPやその他のアジャイル開発手法とともに、急速に普及した。 これらの手法が提唱する柔軟性、迅速なフィードバックループ、継続的な改善の重視は、変化の激しい市場環境において開発プロジェクトを成功に導く鍵とされた。 TDDはまた、開発者の間でのコミュニケーションとコラボレーションを促進し、より効果的なチームワークを実現する手段としても認識された。

■繰り返しを避ける
テストコード内での繰り返しは、メンテナンスの負担を増やす。 共通のセットアップルーチンやヘルパーメソッドを使用することで、コードの重複を減らし、テストのクリアさを保つことができる。

■信頼性
テストケースは一貫して正確な結果を出すべきだ。 テストの実行が外部要因によって変わる場合(例えば、ネットワークの遅延やデータベースの状態に依存する場合)、その信頼性は低下する。

■速度
テストは迅速に実行されるべきです。遅いテストは開発プロセスを遅らせ、テストの実行を妨げる可能性がある。 可能な限り、ユニットテストでは外部依存性を避け、必要ならモックオブジェクトを使用することが推奨される。

■独立性
各テストケースは、他のテストケースから独立するべきだ。 一つのテストの結果が他のテストに影響を与えないようにすることで、テストの信頼性を高め、 特定のテストケースが失敗した際のデバッグを容易にする。

これらの原則を適用することで、テストケースはその意図を明確に伝え、信頼性が高く、メンテナンスが容易で、再利用可能なものになる。 良いテストケースは、テスト駆動開発プロセスの効果を最大限に引き出し、ソフトウェア開発プロジェクト全体の成功に貢献する。

テストも原則が大事

3.テストフレームワークの紹介

テスト駆動開発(TDD)を効果的に実践するためには、適切なテストフレームワークの選択が重要だ。 テストフレームワークは、テストケースの作成、実行、および結果の報告を容易にするツールとライブラリのセットを提供する。 ここでは、いくつかの人気のあるテストフレームワークを紹介する。

JUnit (Java)
JUnitは、Javaプログラミング言語用のオープンソースのテストフレームワークだ。 シンプルで、かつ強力なアサーションメソッドを提供し、テストのセットアップとクリーンアップのための注釈をサポートする。 JUnitはJava開発者にとってデファクトスタンダードなテストフレームワークであり、EclipseやIntelliJ IDEAなどのIDEと簡単に統合できる。

PyTest (Python)
PyTestは、Python用の非常に強力なテストフレームワークだ。 シンプルなテストケースから複雑な機能テストまで、幅広いニーズに対応している。 PyTestは、簡潔な構文と豊富なプラグインエコシステムが特徴で、カスタムマーカーを使用してテストの分類、 フィクスチャを使用したテストデータのセットアップ、パラメータ化テストなど、高度なテストシナリオをサポートする。

NUnit (.NET)
NUnitは、.NETプラットフォーム用のテストフレームワークで、JUnitに触発されている。 ただし、NETの特性を活かした機能が多く含まれています。 属性を使用したテストの定義、テストケースのパラメータ化、セットアップとティアダウンメソッドのサポートなど、 .NET開発者にとって直感的に使用できる機能を提供している。

Jest (JavaScript)
Jestは、Facebookによって開発された、JavaScriptで書かれたデリケートなプログラミング作業に適したテストフレームワークだ。 Reactなどのプロジェクトで広く使われており、その人気はJavaScriptコミュニティ全体に広がっている。 Jestは、そのシンプルな構成、スナップショットテスト機能、および豊富なアサーションAPIにより、開発者にとって魅力的な選択肢となっている。

これらのテストフレームワークは、それぞれ異なるプログラミング言語と環境に特化しているが、共通の目的を持っている。 それは、開発者が品質の高いソフトウェアを効率的に開発できるようにサポートすることだ。 適切なテストフレームワークを選択することで、TDDプロセスが容易になり、ソフトウェア開発の品質と速度が向上する。

4.実践: シンプルな機能のテストを書く

テスト駆動開発(TDD)の実践を通じて、シンプルな機能のテストを書くプロセスを具体的な例を用いて説明する。 この例では、PythonとPyTestフレームワークを使用して、簡単な加算関数のテストを作成する方法を示す。

1: 要件の定義
まず、テストする機能の要件を定義します。例として、「二つの数を加算する関数」を実装することにしよう。 この関数は、二つの引数を受け取り、その和を返す必要がある。

2: テストケースの作成
次に、この加算関数のためのテストケースを書く。 TDDに従い、まだ加算関数が実装されていないことを前提としている。 PyTestを使用すると、テストケースは次のようになる。 このテストケースでは、add関数に2と3を引数として渡し、結果が5になることを期待している。 この時点でadd関数は定義されていないため、テストを実行すると失敗する。

まずは失敗するテストを書く

4: 機能の実装
テストが失敗したことを確認したら、テストをパスするための最小限のコードを書く。 以下は足し算をするために必要な最低限のコードになる。

そして機能を実装する

5. テストの再実行とリファクタリング
実装後、テストを再実行して、今度は成功することを確認する。 テストが成功したら、必要に応じてコードのリファクタリングを行う。 このシンプルな例ではリファクタリングの必要はほとんどないが、 実際のプロジェクトでは、コードの可読性を向上させたり、他の関数との統合を考慮したりする場合がある。

この例では、非常に基本的な加算機能をテストしたが、TDDのプロセスはどんな規模や複雑さの機能にも適用可能だ。 重要なのは、機能を実装する前にテストを書き、そのテストを基に開発を進めるというTDDの基本的なサイクルを守ることである。

5.まとめ

今回はテスト駆動開発における具体的なテストの書き方について解説した。 今回くらいの簡単なコードだと威力をいまいち感じずらいかもしれないが、もう少し複雑なロジックになってくると、 1つ1つ機能を区切って、段階的にテストを行いながら、ロジックを組み立てられる有用性を感じられるだろう。 ここで覚えるべきことは、機能を実装する前にテストを書き、そのテストを基に開発を進めるというTDDの基本的なサイクルを守ることだ。 まずは、ここだけ頭に入れておこう。


▼参考図書、サイト

Pytestを使ってみる 豆蔵デベロッパーサイト
テスト駆動開発(TDD)でコードを書く Zenn