このページでは、単体テストについて解説を行なっていきます。
Contents
単体テストとは
では、まずは単体テストが「どんなテストなのか?」について解説していきます。
単体テスト
まず、ソフトウェアに対するテストは、バグを見つける、そのバグに対する修正の効果を確認する、さらには仕様の妥当性を検証する、といった目的で行われます。
そして、単体テストとは、システム全体ではなく、モジュール単体や関数単体に対して実施するテストのことを言います。システムは複数のモジュールや関数が結合された状態で構成されることになりますが、その一部分のみに対して行うテストとなります。
どの単位で単体テストを行うかは、会社の方針やプロジェクトによって異なります。
スポンサーリンク
結合テスト・システムテスト
単体テストがモジュールや関数単体に対して行うテストであるのに対し、複数のモジュールや関数を結合した状態で実施するテストを結合テストと呼びます。
さらに、実際にユーザーに利用する形態で実施するテストをシステムテストと呼びます。システムテストの場合は、全モジュール・全関数を結合した状態で実施することになり、さらに実際に利用される環境上でソフトウェアを動作させてテストを行います。
例えば組み込み製品であれば、ハードウェアも製品として出来上がった状態のものを揃え、そのハードウェア上で動作させてソフトウェアのテストを行うことになります。
逆に結合テストや単体テストの場合は、実際に利用される環境上でテストが行われるとは限りません。例えば PC や仮想環境上で実施したりする場合もあります。
単体テストの実施タイミング
ソフトウェアの開発プロセスの表現として良く用いられるのが下図のような V 字モデルになります。この V 字モデルにおいて、単体テストは実装フェーズの次に行われるものであり、詳細設計で行った設計に対する検証を行う役割を持ちます。
そして、その次に行うのが結合テスト、さらにその後に行うのがシステムテストになります。
単体テストに関しては開発組織のメンバーが行うことが多いです。それに対し、システムテストに関しては品質保証組織等の開発メンバー以外の第3者が行うことが多いです。
単体テストを行うメリット
前述の通り、システムテストが実際にユーザーが利用する形態でのテストになります。であれば、システムテストだけ行えば良さそうにも感じますが、そういうわけではありません。
では、どういった理由で単体テストを実施するのかについて、単体テストを行うメリットの観点で説明していきたいと思います。
スポンサーリンク
結合テスト・システムテストの効率が上がる
まず、単体テストを導入することで得られるメリットの1つとして、単体テストよりも後の行程で行われる結合テスト・システムテストの効率が上がるという点が挙げられます。
結合テストでは、関数やモジュール等を単体で動作させるときよりもバグが発生しやすいです。なぜなら、単体でも発生しうるバグに加え、単体同士を結合することで初めて見つかるバグも加えて発生するようになるからです。例えば、複数のモジュールが並列に動作するような場合、2つのモジュールの処理のタイミングによって意図しない動作になるような場合もあります。このようなバグは単体で動作確認しても見つけにくいです。
また、モジュール間や関数間の IF の認識のズレなどによってバグが生じることも多いです。これに関しても結合テストやシステムテストを行うことで発生することになります。例えば、モジュール間の担当者が異なる場合、お互いのモジュールの仕様の認識のズレが異なると、お互いに想定していない入力や出力が行われる可能性があります。基本設計等で認識にズレが生じないように設計する必要があるのですが、そこが甘いとこういった認識のズレによるバグが起こり得ます。
さらに、結合を行うことで、テスト対象となるモジュールや関数のソースコードの規模が大きくなります。バグが発生して原因を調査しようと思っても、ソースコードの規模が大きくなればなるほど調査範囲が広くなるため、原因の調査に時間がかかるようになります。
したがって、単体テストを行わずに結合テストやシステムテストをいきなり始めた場合、テストでバグが発生した際に原因調査に要する時間が多くなります。そのため、結合テスト・システムテストフェーズの効率が下がります。
それに対し、単体テストを実施して単体での動作確認をしっかり行なっておけば、まず単体テストで発見できるバグを結合テスト・システムテストの前に解消しておくことができます。したがって、結合テスト・システムテスト時に発生するバグを少なくすることができます。
もちろん、単体テストでバグが見つかった場合でも調査は必要です。ですが、原因調査対象は結合テスト・システムテスト時に比べて限定されるため、バグの原因を調査する時間も減ります。しかし、前述の通り、結合テストでバグが見つかった場合は、調査対象が広くなるので、その分調査に要する時間が多くなります。
こういった理由より、単体テストを行って単体テストで発見可能なバグは結合テストの前に解消しておいた方が良いです。そして、これによって、結合テスト以降の行程の効率を高めることができます。
自動化しやすい
また、単体テストは自動化しやすいというメリットもあります。
単体テストの自動化を行うような単体テストフレームワークも多く存在し、これらを利用することで用意されたテストケースに従った単体テストを一気に全て自動で実行することができるようになります。そして、実行したテスト結果もレポートとして表示されたりもします。
単なるテスト結果だけでなく、カバレッジ(簡単に言えばテスト実行時に通過した行の割合)やテスト時に実行された行のレポートが表示可能なものもあります。
また、フレームワークと Jenkins などの CI ツールとを併用することで、毎日定期的に単体テストを実施するようなことも可能となりますし、さらに Git などのバージョン管理ツールと連携させれば、レポジトリにソースコードの変更を反映した際に単体テストを自動的に実行するようなことも可能です。
単体テストを自動化することで、このような定期的なテストを人手を介さずに実行させて楽にソフトウェアの品質を保つことができるようになります。また、各プロジェクトのテスト結果やカバレッジの一覧を確認できるようにしておけばプロジェクトの進捗の見える化にもつながります。
特に単体テスト(結合テストも)の場合は自動化の実現が容易です。例えばシステムで何かしらのハードウェアを制御するような場合、システム全体でのテストを実施する際には、そのハードウェアも用意しておく必要があります。もしかしたら、そのハードウェアを手動でセットアップする必要があれば、そこで人手が必要となりますし、例えば毎日テストを実行しようとすると、常にソフトウェアからハードウェアを制御可能な状態にセットアップしておく必要があります。
ですが、単体テストの場合、そのハードウェアを利用する部分をスタブにしてやれば(スタブについては後述で解説します)、ハードウェア不要でテストすることも可能となります。ソフトウェアだけであれば全自動でテストすることも簡単ですし、ハードウェアを用意するためのスペース・手間等も不要となります。
とにかく自動化しやすいというところが単体テストの特徴の1つであると言えると思います。
デバッグ効率が上がる
また、自動化することでソフトウェアの品質を向上させるだけでなく、デバッグ効率も向上します。
コーディングの工程においては、デバッグと動作確認を繰り返しながらモジュール等を開発していくことになります。
この動作確認を手動で行うとなると、人手で行うため動作確認に時間がかかりますし労力も必要となります。デバッグが一度の修正で完了すれば、これらはそんなに気にしなくても良い時間・労力になるかもしれませんが、当然デバッグが一度の修正で完了するとは限らず、何回も繰り返し動作確認を行うことになる可能性もあります。
そんな時に、先に単体テストを実装しておいて自動的に動作結果の検証が行えるようにしておけば、バグの原因調査や修正は手動で人が行うとしても、動作確認は自動で行うことができるようになります。自動で行われため動作確認に要する時間も減りますし、単体テストを実行すれば良いだけなので労力も不要です。
そのため、コーディングの工程に関しても、先に単体テストを導入しておくことで作業効率を高めることが可能となります。
スポンサーリンク
ソフトが変更しやすくなる
また、ソフトが変更しやすい開発体制を整えることが可能となることも単体テストのメリットであると言えます。これは他のテストにも共通して言えることではあるのですが、前述の通り単体テストは自動化しやすいため、この恩恵が得られやすいです。
一度出来上がったモジュールや関数の変更を行う際には、その変更の影響範囲の見極めが必要となります。その変更の影響範囲が正く見極められていないと、その変更を行うことで今まで上手く動作していた機能が上手く動作しなくなる可能性もあります。つまり、変更を行うことで新たなバグを作ってしまう可能性があります。皆さんも、今まで上手く動作していた処理が変更を行うことで意図せず動作が変わってしまったような経験があるのではないでしょうか?
こういった影響範囲は設計資料やソースコードから判断することもできますが、テストの実施により判断することも可能です。そして、ソフトウェアを網羅的にテストできるよう行えるようテストを用意し、さらにテストを自動化しておけば、影響範囲も自動的に洗い出すことが可能となります。
例えば全機能に対してテストを網羅的に実施できるようになっていれば、特定の箇所を変更した場合に、その変更によって影響を受けるテストの結果が NG になるはずです。したがって、このテスト結果を確認することで、変更の影響範囲が意図した範囲だけであるか、他の機能に影響を与えていないかを把握することができます。もし、テストが網羅的に実施できていなければ、もしかしたら変更によって他の機能にバグが生まれたとしても、それに気づけないかもしれません。
ソフトを流用し、それを変更して新たなソフトを産み出すような開発手法を取り入れることも多いですが、出来上がったソフトを変更する際には、前述のような影響範囲のような観点のリスクがあります。
そういったリスクを最小限に抑えるためにもテストが重要であり、さらにそれを自動化しておけば、楽にリスクを抑えることができるというメリットがあります。
そして、このリスクを抑えることはソフトの変更容易性を高めることにつながり、会社や部門のソフトウェア開発力の向上にもつながることになります。このように、テストは品質を高めるだけでなく、ソフトの開発力向上にも寄与するものとなります。
複数のモジュールの並行開発・テストが可能
また、単体テストを実施することで、複数のモジュールの並行開発がやりやすくなります。また、モジュール単体で考えても、そのモジュールの実装とテストの実装を並行して進めることが可能となります。そのため、もちろん平行で作業を進めるための人手は必要となりますが、開発期間の短縮を行うことが可能となります。
例えば複数人で別々のモジュールを開発している場合、特定のモジュールが他のモジュールに依存していると、その依存している箇所の動作確認を行うために他のモジュールが出来上がるのを待つ必要があることがあります。
もっと具体的に言えば、モジュール A がモジュール B の提供する関数を利用する構成になっている場合、モジュール A の動作確認を行うためにはモジュール B が必要になってしまいます。したがって、モジュール A の実装が終わったとしても、動作確認を行うためにモジュール B の出来上がりを待つ必要があります。
ですが、このような場合でも、単体テストを導入すればモジュール A の動作確認はモジュール B の出来上がりを待つことなく実施することが可能となります。この例で言えば、モジュール A が利用するモジュール B の関数をスタブ化してしまえばモジュール A はモジュール B の出来上がりに関係なくテストが実施可能となります。
そのため、こういった平行開発をスムーズに進めるためにも単体テストは有効です。
また、複数のモジュールだけでなくモジュール単体の開発に関しても、モジュールの実装と単体テストの実装を並行して行うこともできますので、モジュール単体の開発スピードを高めたい場合にも単体テストは有効です。
前述の通り、単体テストを導入しておけば、後々のテスト工程の効率を高めることも可能になります。そのため、特定のモジュールの実装が遅延しているのであれば、そこに人材を投入して単体テストの実装を行ってもらうようにすることで、そのモジュールの実装工程の開発期間を短縮することができるようになります。
誰でも単体テストを実施できる
先ほどの例で、開発が遅延しているモジュールに人材を投入して単体テストを実装してもらう例を示しましたが、単体テストの実装ではなく、直接そのモジュール自体の実装に加わってもらうことでも開発期間の短縮を行うことは可能です。
ですが、モジュール自体の実装に加わってもらう場合、その人には品質の高いモジュールを開発するためのコーディング力が備わっている必要もありますし、そのモジュールの仕様だけでなく設計に関してもしっかり理解して作業を行ってもらう必要があります。なので、投入できる人材は限られていますし、人材を投入したとしても開発効率向上の効果が得られるまでに時間がかかる可能性も高いですし、
それに対し、単体テストの実装の場合は、テストの実装は多くのケースでパターン化されるので簡単ですし、設計を理解していなくても、そのモジュールや関数の仕様(入力と出力の関係)さえ理解しておけば作業可能です。そのため、ある程度コーディングができる方であれば、誰を投入したとしても開発効率向上の効果が得られやすいというメリットがあります。
例えば、単体テスト要員であれば、新入社員のような方であっても、すぐにプロジェクトに参加して活躍してもらうようなことも可能だと思います。
スポンサーリンク
単体テストの実施方法
ここまで解説してきたように、単体テストを導入するメリットは非常にたくさんあります。
では、この単体テストはどのようにして実施すれば良いのでしょうか?次は、この点について解説していきます。
実際には、単体テストを行うためにはテストケース(テストの観点やテストの手順・確認する内容をまとめたもの)を考える必要があったりしますが、ここでは単体テストの実施手順というよりは単体テストを行うための仕組みについて説明していきます。
ドライバとスタブ
ここからは「単体 = 関数単体」の前提で解説をしていきます。
単体テストを行うにあたってはドライバとスタブを利用することが多いです。
これは関数に対する単体テストをイメージしていただくと分かりやすいと思いますが、関数は「他の関数から呼び出しされる」ことで実行されます。さらに、その関数自体が「他の関数を呼び出しする」こともあります。ですが、関数に対する単体テストでは、その関数のみをテストするために、そういった呼び出し元の関数や呼び出し先の関数を使わずにテストすることが多いです。
こうなると、テスト対象の関数の呼び出し元の関数も、テスト対象の関数の呼び出し先の関数も存在しなくなってしまいますので、ここで代わりにドライバとスタブを用意します。ドライバがテスト対象の関数の呼び出し元の関数の代わりとなり、スタブがテスト対象の関数からの呼び出し先の関数の代わりとなります。
そして、これらのドライバやスタブは別途実装を行なう必要であることが多いです。では、テスト対象の関数の呼び出し元や呼び出し先となる本物の関数が存在するはずなのに、わざわざ実装してまでドライバやスタブを用意する理由とは何なのでしょうか?
この理由は本物の関数を利用すると実現しにくいテストケースを容易に・確実に実現するためになります。違う言い方をすれば、本物の関数を利用すると実行されにくい行を確実に実行するためになります。
例えば、下記における target
関数をテストすることを考えてみましょう。下記は Python で記述しており、Python のコードが読めない方もおられるかもしれませんが、ここで重要なのは target
関数の動作は、main
関数から指定される引数と random.randint
関数の返却値によって変化するという点になります。この random.randint
は、引数で指定された範囲の中の整数をランダムに返却する関数になります。
さらに、main
関数から指定される引数も結局は random.randint
関数の返却値によって変化します。すなわち、target
関数の引数はランダムに決まりますし、target
関数から呼び出される関数の返却値もランダムに決まります。
import random
def target(x):
y = random.randint(-100, 100)
if x == 0 and y == 0:
return 1 / x
return y / x
def main():
x = random.randint(-100, 100)
ans = target(x)
print(ans)
if __name__ == '__main__':
main()
ここで下記の if
文について考えると、この if
文は引数 x
と random.randint
の返却値の両方が 0
の場合のみ成立することになります。そして、この if
文が成立した場合には 0
での除算が行われて例外が発生するというバグが存在しています。テスト時に一度でも if
文が成立すれば例外が発生してバグが存在することに気づくことができるのですが、この例の場合はランダムに x
や y
が決まるため、何回テストしても一度も if
文が成立せずにバグが存在することに気づけない可能性があります。
if x == 0 and y == 0:
return 1 / x
そして、テストでバグを見つけられれずにソフトウェア・プログラムをリリースしてしまうと市場障害などになる可能性もあります。そのため、テストで確実に全ての行の処理を実行させて動作確認を行なったり、もし実行されない処理があるのであればコードレビュー等を実施してバグがないことを確認したりすることが必要になります。
上記は極端な例ではあるのですが、このように成立しにくい if
文というのはソフトウェアを開発をしていると普通にあり得ます。
こういった成立しにくい if
文などを確実に成立させるためにスタブやドライバが活躍します。これらはテスト用のダミーの関数と考えて良いです。実施したいテストに合わせて自由自在に関数を呼び出す際の引数や関数から返却する値をコントロールできるように実装してやれば、本物の関数を利用した際には成立しにくい if
文などを確実に成立させるようなことも可能です。
例えば、上記の例であれば、ドライバとして target
関数を呼び出す際に引数に 0
を指定し、さらに target
関数から呼び出されるスタブとして必ず 0
を返却する関数を用意してやれば、x
と y
の両方を確実に 0
にすることができます。
そして、下記の if
文も成立することになり、この if
文が成立した時に実行される処理にはバグが存在していますので、そのバグを検出することができます。
if x == 0 and y == 0:
return 1 / x
実は、先ほど紹介した target
関数では x
と y
のいずれか一方のみが 0
である場合にも例外が発生する問題を抱えています。これに関しても、x
と y
のいずれか一方のみが 0
となるようにドライバやスタブを用意してやれば良いことになります。
単体テストフレームワークの導入
大体ドライバやスタブを用意する意味合いは理解していただけたのではないかと思います。
じゃあ、ドライバやスタブは具体的にどうやって導入すれば良いのか?という点に疑問を持つ方もおられると思います。結論としては単体テストフレームワークを利用してやれば良いです。単体テストフレームワークにはドライバやスタブを利用する仕組みが存在し、それらを利用することでドライバやスタブを導入することが可能となります。
例えばですが、Python であれば unittest
というモジュールが標準で備えられており、unittest.TestCase
のサブクラスのメソッドがドライバの役割を果たします。さらに、unittest.mock
から提供されるモックの仕組みによってスタブを実現可能です。このドライバとスタブを利用すれば、テスト対象の関数への引数の指定やテスト対象の関数へ返却する値を自由自在にコントロールすることができます。
この具体的な例は下記ページで示していますので、Python を使っている&単体テストに興味のある方はぜひ読んでみてください。
【Python】unittestで単体テストを行う(カバレッジの計測についても紹介) 【Python/unittest】mockの基本的な使い方Python だけでなく、他のプログラム言語でも単体テストフレームワークが用意されており、これらを利用してドライバやスタブを導入することが可能となります。
また、単体テストフレームワークを利用することで各テストの結果をまとめたレポートを出力するようなこともできますし、テストの自動化も行いやすくなります。
前述でも紹介しましたが、カバレッジを取得してテストの網羅率を示したり、テスト時に実行された行を可視化するようなこともできます。
今後、Python だけでなく他のプログラミング言語におけるテストフレームワークの紹介も行っていきたいと思います。
スポンサーリンク
まとめ
このページではソフトウェア開発における単体テストについて解説を行ないました。
ソフトウェア開発に必須なのはシステムテストになるでしょう。どちらかというと単体テストはスケジュールやリソースによっては省略されるようなこともあります。ですが、単体テストを行うメリットは多く、単体テストを行うからこそシステムテストを効率的に行うようなことも可能となります。
また、システムテストに比べると単体テストは自動化しやすく、その自動化によって開発効率を向上させるようなことも可能になります。
デメリットについては取り扱いませんでしたが、単体テストを行うデメリットは単体テストを行うための準備が必要になる点が挙げられると思います。テストフレームワークを導入したり、テストケースを考えたり、テストケースに応じたドライバやスタブを用意したり、自動化したりする必要があります。ですが、特に開発期間の長いプロジェクトでは単体テストの導入および自動化を早期に導入することで結果的にプロジェクトの期間を短縮することができ、開発するシステム・ソフトウェアの品質も向上させることができるケースが多いです。単体テストを導入するタイミングが早ければ早いほど得られる恩恵も大きくなります。
多くの単体テストフレームワークも用意されており、それらを利用することで単体テストの導入自体も効率的に行うことも可能です。是非単体テストを導入してソフトウェア・システムの品質向上および開発効率化に取り組んでみてください!