単体テスト(ユニットテスト)とは、当該プログラム開発者の手によって一番最初に実行されるテストです。
単体テストの定義としては少々曖昧に聞こえるかもしれませんが、単体テスト工程で何を検証するかは各企業・各プロジェクトごとに異なるため、本サイトでは単体テストをこのように定義しています。
今日では「単体」という言葉の意味は薄れ、十分に名が体を表す形になっていません。
大事なのは「単体」であることではなく、まず初めにどのようなテストを行う必要があるのかということですので、言葉の定義にあまりこだわるべきではないでしょう。
単体テストの目的は、作成したプログラムが開発者の想定通りに動作するかどうかを確認することにあります。
本テスト工程が完了していないプログラムは単にコーディングをし終わっただけのものに過ぎず、そういった意味ではプログラムは本テスト工程を経て初めて製造が完了したと言えるかと思います。
参考: 単体テストのテスト項目の観点
単体テストで検出すべき障害
理想的には、単体テスト以降の検証工程(結合テスト、システムテストなど)でしか検出できない障害を除き、すべての障害は本工程で出し尽くされなければなりません。
したがって、プログラムロジックの論理矛盾や単純ミスといった障害はここで根絶されているべきです。
複雑なアルゴリズムやプログラムロジックは、モックやスタブを利用して小回りの利くテスト環境を構築し、本工程で十分に検証を行いましょう。
不具合の発覚が後工程に行けば行くほど調査・修正作業が困難になります。
参考: 単体テスト以降の検証工程においてのみ検出される障害(結合テストとは)
単体テストの位置づけ
ソフトウェアの開発工程を表す図としてよく利用される「V字モデル」を見てみましょう。
V字モデルでは、単体テストは詳細設計の検証であるように表されますが、実際には詳細設計書は十分にテスト項目を抽出できるほどの内容を持っているわけではありません。
特にアジャイル型の開発においては、V字モデルほど明確な設計工程が存在しない場合が多く、プロジェクトによって設計書の精度・粒度もまちまちです。
したがって単体テストが検証(照合・検算)の対象とするものは、設計書や仕様書などのドキュメントの他にソースコードそのものも含まれます。
このソースコードを元にしたテストはコードベースのテストとかホワイトボックステストと呼ばれます。
作ろうとしているプロダクトの SLA にもよりますが、一般的にはホワイトボックステストでは、あらかじめテスト項目を作成したり、検出した障害数を集計するようなことはしません。
実際にプログラムの開発を行った方ならわかると思いますが、プログラミングとはコーディングとデバッグを繰り返す試行錯誤の連続です。
特に難しい処理を実装する際は、作っては確認、作っては確認という作業を繰り返した経験をお持ちではないでしょうか。
どこまでが製造工程でどこからが単体テスト工程なのかを綺麗に線引きすることはとても困難です。
したがって単体テストは製造工程の一部という位置づけと考えた方がプログラム開発の実態をうまく表していると思います。
この辺り、テスト駆動開発【Test-Driven Development】(TDD)におけるテストファーストの考え方にリンクするものがあるかもしれません。
単体テストのやり方
ユニットテストの「ユニット」とはテスト可能な部品の単位であり、例えばプログラムがオブジェクト指向言語で作成された場合の「ユニット」はクラスやメソッドなどにあたります。
テストされるユニットは他のモジュールから分離し、テストドライバーやモック、スタブといったダミーモジュールと結合させ、当該ユニットを実行させることで検証を行います。
このテスト対象が依存しているコンポーネントの代用品のことをテストダブル【Test Double】といいます。
テストドライバーはテスト対象となるユニットを呼び出す上位モジュールの代用品です。
主に JUnit や PHPUnit といったテストツールを利用する形になるかと思いますが、この部分はテスト実行の起点となるため、テストの自動化を念頭に置いて実装方法を検討しておくと良いかと思います。
スタブは下位モジュールの代用品です。
下位モジュールが未完成でも代わりにスタブを呼び出すことでテストを可能とします。
スタブから返された値によって当該ユニットが正しい挙動をするかどうかを検証します。
モックも基本的にはスタブと同じですが、代替した下位モジュールを正しく利用しているかどうかを検証するためのテストモジュールです。
例えば当該ユニットが呼び出すメソッドの引数や呼び出し回数などを確認します。
ユニット(単体性)にこだわる必要はない
テストダブルは様々なテストパターンを実施する環境を容易に作り出すことができる反面、開発が進むにつれて肥大化し、機能改善や仕様変更に伴う修正コストが高くなっていく傾向があります。
このため、開発の時点で結合可能なコンポーネントやモジュール、ライブラリについては、あらかじめ結合した状態でテストを行い、テストパターンを作り出すことが困難な部分のみモックやスタブを利用する方法が良いでしょう。
例えば Web システムの開発においては、フレームワークやデータベースなどはそのまま本番環境と同等のものを利用してテストを行うケースが多いかと思います。
特にデータベースについては、SQL 文そのものの正しさをテストプログラム上から判断することは難しいので、単体テストでは少なくともデータベースと結合してテストを行う方が効率が良いでしょう。
このようにテストの実施の際に単体性にこだわる意味はあまりなく、「単体テスト」という呼び方自体も見直されつつあります。
Google ではテストの規模によってSmall、Medium、Large と呼んでいるようです。
本サイトでは単体テストは製造工程の一部であるという考え方なので、強いて呼び方にこだわるのであれば差し当たり「製造とともに行うテスト」とか「最初のテスト」という感じでしょうか(もちろん本サイトでは一般的に通用する「単体テスト」という呼び方を採用します)。
参考: Google Testing Blog – Test Sizes
単体テスト工程において結合され得るモジュール
・HTTP サーバ(Apache、Nginx など)
・フレームワーク(Ruby on Rails、Laravel など)
・データベース(MySQL、PostgreSQL など)
テストの実行は自動化し RT に利用する
今日では単体テストのテストプログラムはテストツールなどを利用して作成され、CI(継続的インテグレーション)環境の中でプログラムの変更が行われるたびに自動的に実行されることが理想とされています。
この検証工程はリグレッションテスト【Regression Test】(RT)と呼ばれ、デグレード防止のために必ず実施する必要があります。
特に頻繁なリビジョンアップが行われるアジャイル型の開発においては、常にデグレードのリスクと隣り合わせであるため、確実に RT が行われるテスト環境の構築が必須となります。
一般的にはデプロイをフックしてテストドライバーを自動実行するよう CI 環境を構築します。
データベースではテストケースごとにそれに見合うテーブルやレコードデータを作成し、保存しておきます。
こうしておくことで再度テストを実施する際にもすぐにテスト環境の再現ができます。
参考: 単体テストのテスト項目の観点
参考: 結合テストとは
単体テストは、ソフトウェア全体の品質に大きく影響する非常に重要な工程であるにもかかわらず、テストの実施内容についてはほぼ開発担当者に全権が委ねられており、本工程の作業品質を表す具体的なアウトプットもあまりないことから外側の人間からの評価が非常に困難になっているという問題をはらんでいます。
プロジェクトの中には不具合を多発させる開発者がいる一方で、まったく障害を出さない開発者もいて、なぜ人によってこんなに違いが出るのだろうという悩みを抱えるプロジェクトリーダーの方も多いのではないでしょうか。
これらの原因のほとんどは単体テストの品質の差によるもので、さらにテスト品質の差は各開発者のスキルにも依存するため、リーダー視点からはこの違いが非常に見えにくい状況となります。
前者(不具合多発者)よりも後者の方がテスト工程にかかる工数が少なかったり、どのようにテストしたかヒアリングをしても「適当にデバッグ文を入れてステップ実行した」みたいな回答が返ってきたりします。
結局のところ、後者は何を検証すべきかが開発段階から見えており、効率良くテストをしているだけなのです。
各開発者が同一水準のテスト品質を保つためには、単体テストの理解を深め、最終的にはテストの手順・手法が開発部内で統一されることを目指す必要があります。
コメント