単体テスト

コンピュータプログラミングにおいて単体テスト(たんたいテスト)あるいはユニットテスト英語: unit test)とは、ソースコードの個々のユニット、すなわち、1つ以上のコンピュータプログラムモジュールが使用に適しているかどうかを決定するために、関連する制御データ、使用手順、操作手順とともにテストする手法である[1]。ユニットとはアプリケーションのテスト可能な最小の部品単位である、と直観的にとらえることができる。手続き型プログラミングでは、ユニットは、モジュール全体のこともあるが、より一般的には、個々の関数や手続きである。オブジェクト指向プログラミングでは、ユニットは、クラスなどのインタフェース全体だが、個々のメソッドであることもある[2]。単体テストは開発プロセス中にプログラマー、時にはホワイトボックステスターによって作成される。

理想的には、各テストケースは他から独立しているべきである。メソッドスタブモックオブジェクト[3]、フェイク、テストハーネスなどのような代替を、モジュールを分離した状態のテストを支援するために使用できる。一般的に単体テストは、コードが設計通りであることと、意図したとおりに動作することを確認するため、ソフトウェア開発者の手によって書かれ、実行される。その実装は手作業(鉛筆と紙)からビルド自動化の一環として定式化される場合まで、さまざまである。

現在では、単体テストはxUnitといったテスト自動化ツールを用いて行われるのが主流となっており、単体テストを自動化されたテストとして言及するケースもある(本項目も、自動化されたテストとしての記述を含む)。しかし、単体テストはあくまでテストの粒度に対する分類であり、必ずしもテスト自動化を意味しないため、注意が必要である[4]

特長

単体テストの目的は、プログラムの各部分を分離し、個々の部品が正しいことを示すことである[1]。単体テストはコードの一部が満たさなければならない厳格な、明記された契約を規定する。その結果、以下のような利点がもたらされる。

早期に問題を発見する

単体テストは、開発サイクルの初期段階で問題を発見する。

エクストリーム・プログラミングスクラムの両方で多く使用されるテスト駆動開発 (TDD) では、コード自体を書く前に単体テストが作成される。テストに合格すると、そのコードが完成したと見なされる。大規模なコードベースを開発している場合、コードを変更したとき、あるいはビルドの自動プロセスの過程で、同じ単体テストがその機能に対して頻繁に実行される。ユニットテストが失敗した場合は、変更されたコードまたはテスト自体のどちらかのバグであると考えられる。単体テストは、障害や障害の場所を簡単にトレースすることができる。単体テストはテスターやクライアントにコードを渡す前に、開発チームに問題を警告するため、開発プロセスの初期段階に行われる。

変更を容易にする

単体テストはプログラマーがコードを後日リファクタリングすることを可能にし、モジュールがまだ正しく動作することを確認する(例えば回帰テスト)。その手順は変更が障害を引き起こした場合、すばやく原因を究明し修正できるため、すべての関数メソッドに対するテストケースを書くことである。

既に単体テストができているため、プログラマーがコードが正しく動作するかどうかを確認するのは容易である。

継続的な単体テスト環境では保守が常時行われているため、どんな変更に対しても、単体テストは実行形式ファイルとコードの意図された用途を正確に反映している。確立した開発業務や単体テストのカバレッジに応じて、最新情報を掲載した精度を維持することができる。

統合の簡素化

単体テストは、ユニット自体に不確実性を減らすことができボトムアップテストのスタイルのアプローチで使用することができる。最初のプログラムの部分をテストし、その部分の総和をテストすることにより、統合テストは、はるかに容易になる。

精巧に階層化された単体テストは統合テストと等価ではない。周辺ユニットとの統合は、単体テストではなく、統合テストに含まれるべきである。統合テストは、通常、手動で人間によるテストに大きく依存している。高レベルまたはグローバルスコープのテストは自動化が困難であるため、手動テストの方が多くの場合より速く、より安価であると見られている。

ドキュメント

単体テストはいわばシステムの生きた技術文書を提供する。 開発者がユニットにどのような機能があるかどのように使用すればいいかを知るためには単体テストを調べてユニットのAPIの基本を理解すればよい。

単体テストケースはそのユニットの正常動作に必要不可欠な特性を持っている。これらの特性は、ユニットの適切/不適切な使用を示すだけでなく、ユニットにトラップされるネガティブな動作を示す。単体テストはそれ自身とその内部において重要な性質をドキュメント化している。しかし、多くのソフト開発環境は開発中製品をドキュメント化するためにコードだけには頼らない。

それに比べて、普通の手作業のドキュメントはプログラムの実装から押し流される傾向にあり、陳腐になりがちである(例えば、設計変更、機能追加、ドキュメント最新化の習慣の緩和など)。

設計

テスト駆動のアプローチでソフトウェア開発を行う場合、単体テストが正式な設計の代わりになる。各単体テストは、クラス、メソッド、および観測可能な動作を指定する設計要素として見ることができる。次のJavaの例が、この点を説明する助けになる。

ここに実装のいくつかの要素を規定するテストクラスがある。まず Adder というインタフェース、引数のないコンストラクタをもつ実装クラス AdderImpl があるはずである。続いてAdderインタフェースが、2つの引数をとり、整数を返す add というメソッドを有する、ということをこのテストクラスのコードが示している。また、境界値として代表的な値や、小さな範囲の値に対して、表明 (assertion) によってこのメソッドの動作を規定する。もしassert文の条件が満たされなかった場合、AssertionErrorがスローされ、テストプログラムが中断されることでテスト失敗として報告される。

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        // 正の数を加算できる?
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        // ゼロニュートラル?
        assert(adder.add(0, 0) == 0);
        // 負の数を加算できる?
        assert(adder.add(-1, -2) == -3);
        // 正の数と負の数を加算できる?
        assert(adder.add(-1, 1) == 0);
        // 大きな数値の場合は?
        assert(adder.add(1234, 988) == 2222);
    }
}

この場合、単体テストは最初に書かれているため、求められる解決策の形と挙動を示す設計ドキュメントの役割を果たす。それは細部の実装ではなく、実装はプログラマーに任せられている。「動きそうな最もシンプルな事を行なう」慣例に従えば、テストをパスできる最も簡単な解決法は以下のようなものだろう。

interface Adder {
    int add(int a, int b);
}
class AdderImpl implements Adder {
    int add(int a, int b) {
        return a + b;
    }
}

ダイアグラム(図)に基づいた他の設計手法とは異なり、単体テストを設計の手段として使用することには重要な利点が1つある。設計文書(単体テストそのもの)を実装が設計に準拠していることを確認するために使用できるのだ。単体テスト設計方法においては、開発者が設計通りに解決策を実装していない場合、テストはパスすることは決してない。

単体テストがダイアグラムのようなアクセシビリティを欠いていることは確かだが、UML 図はこんにち、フリーのツールにより大部分の近代的なプログラミング言語に対して簡単かつ自動的に抽出できるようになっている(通常IDEの拡張機能として利用可能)。xUnitフレームワークに依存するフリーのツールは、人の目で見られるためのグラフィカルレンダリング機能を他のシステムにまかせている。

インタフェースと実装の分離

あるクラスは 他のクラスを参照しているため、あるクラスのテストはしばしば別のクラスのテストに影響してしまう。この一般的な例は、データベースに依存しているクラスである。クラスをテストするために、テスターは、しばしばデータベースと対話するコードを書く。これは間違いである。なぜなら単体テストは自分のクラスの境界を超えるべきではないし、特にそのようなプロセス/ネットワーク境界を超えることは許されない。その理由は単体テストスイートで受け入れがたい性能問題が起こりうるからである。また、そのように単体テストの境界を超えることは統合テストになり、テストが失敗したときに、そのコンポーネントが失敗の原因かどうかが分からなくなってしまう。フェイク、モック、統合テストを参照のこと。

その代わりに、ソフトウェア開発者は、データベースクエリの周りに抽象的なインタフェースを作成し、専用のインタフェースを持つモックオブジェクトを実装する必要がある。この必要な付属物をコード(ネットで有効な結合)から抽象化し、独立したユニットは以前よりも徹底的にテストすることができる。この結果、高品質でかつ保守性があるユニットができあがる。

パラメータ化された単体テスト

パラメータ化された単体テスト(PUT:Parameterized Unit Testing)はパラメータが必要なテストである。通常の単体テストは、閉じたメソッドであるが、PUTはいかなるパラメータも取りうる。PUTはJUnit 4と様々な.NETテストフレームワークによってサポートされてきた。単体テストに適したパラメータは手作業で作成、あるいは、テストフレームワークにより自動生成される場合もある。QuickCheckのようなテスト入力を生成する商用テストツールも多い。

単体テストの制限

もっとも簡単なプログラムでさえも、すべての実行パスを評価するわけではないので、テストでプログラムのエラーが全てわかるわけではない。同じことは、単体テストにも当てはまる。さらに、単体テストは、その定義からユニットのみの機能自体をテストする。したがって、統合エラーや、より広範なシステムレベルのエラー(例えば性能など、複数のユニットにまたがって実行される機能や非機能テスト領域)を捉えることはできない。単体テストは、特定のエラーの存在や不在を示すだけで、エラーが無いことの証明にはならないから、他のソフトウェアテスト活動と合わせて実施しなければならない。 各実行パスの正しい挙動を保証するため、エラーの不在を確認するため、他のテクニックが必要となる、すなわち、ソフトウェアコンポーネントが期待されない挙動を示さないことを証明する正式なメソッドのアプリケーションが必要となる。

ソフトウェアテストは、組み合わせ問題である。たとえば、2分する判定ステートメントは少なくとも2つのテストが必要である。すなわち、結果が真となる場合と、偽となる場合である。結果として、多くの場合、作成コードの1行あたり、プログラマは3から5行のテストコードを書かなければならない[5]。これは明らかに時間がかかり、その手間を投資する価値はないかもしれない。例えば、非決定的あるいは複数スレッドが関係する場合、テストは容易ではない。また、単体テスト用のコードには、少なくともテスト対象コードと同じくらいのバグが存在する可能性がある。フレッド・ブルックスは著書『人月の神話』で「船に乗るときは、クロノメーターを2つ持って行ってはならない。常に1個か3個を持っていけ。」と引用している。その意味は、2個のクロノメーターが矛盾する場合、どちらが正しいのか分からないから、ということである。

単体テストを書くことに関連するもう一つの課題は、現実的かつ有用なテストを設定することの難しさである。テスト対象アプリケーションの一部が、完全なシステムの一部のように振る舞うように関連する初期条件を作成する必要がある。これらの初期条件が正しく設定されていない場合、テストにおいて現実的なコンテキストでコードが実行されず、単体テスト結果の価値と正確性が減少する[6]

単体テストで意図通りの効果を得るためには、厳格な規律がソフトウェア開発プロセス全体で必要となる。実行済のテストだけでなく、ソースコードまたはソフトウェア内の他のユニットに加えられたすべての変更についての正確な記録を保持することが不可欠である。バージョン管理システムの使用が不可欠である。より新しいユニットのバージョンが以前に合格したという特定のテストで失敗した場合、バージョン管理ソフトウェアでは、(もしあれば)それ以降にユニットに適用されたソースコードの変更のリストを保持している可能性がある。

また、失敗したテストケースが毎日検証され、すぐに対処されることを確実にするための、持続可能なプロセスを実装することが不可欠である[7]。このような処理が実装されず、チームのワークフローに深く根付いていない場合、アプリケーションの進化が単体テストスイートとの同期を失い、偽陽性が増加し、テストスイートの有効性が低下することになる。

組み込みシステムソフトウェアの単体テストには、他にない課題がある。ソフトウェアは、最終的に実行されるものとは異なるプラットフォーム上で開発されているので、デスクトッププログラムでテスト可能であっても、実際のデプロイ環境で容易にテストプログラムを実行することはできない[8]

アプリケーション

エクストリーム・プログラミング

単体テストは、エクストリーム・プログラミングの基礎となるもので、それは自動化ユニットテストのフレームワークに依存している。この自動化ユニットテストフレームワークは、xUnitのようなサードパーティ製、あるいは、開発グループ内で自作することができる。

エクストリームプログラミングはテスト駆動開発での単体テストの作成を利用している。開発者は、ソフトウェア要件、または欠陥を暴露する単体テストを書く。要件がまだ実装されていないか、意図的に既存のコードの欠陥を暴露するため、このテストは失敗する。そして、開発者は、そのテストと他のテストにパスするような最も簡単なコードを書く。

システム内のほとんどのコードは、単体テストが実施されるが、コード中のすべてのパスである必要はない。エクストリームプログラミングは従来の 「すべての実行パスをテストする」方法よりも、 「失敗する可能性があるすべてのテストを実行する」戦略を義務付けている。これにより、開発者が従来の方法よりも少ないテストプログラムを開発することにつながるが、古典的な方法においては、すべての実行パスが完全にテストされるほど十分念入りに実施されないため、これは大きな問題でなく、事実の言い換えである。 エクストリームプログラミングというのは、テストが(しばしば高価すぎて時間がかかり経済的に実施可能でないため)、いかにして効果的に限られた資源を集中するかについて基本的な考え方を示している。

重要なのは、テストコードは、重複を全て取り払い実装コードと同じ品質に維持されている点で、第一級のプロジェクト成果物であるという考えである。開発者は、テスト対象コードと対でコードリポジトリに単体テストコードを公開する。エクストリームプログラミングの徹底した単体テストの利点は、上記で述べたように、より簡単で、より自信があるコード開発とリファクタリング、コード統合の容易化、正確なドキュメンテーション、モジュール化の高い設計である。これらの単体テストはまた、回帰テストとして頻繁に実行される。

単体テストはまた、 創発的設計の概念の重要な要素である。創発的設計はリファクタリングに大きく依存しているので、単体テストもまた不可欠な要素である[9]

テクニック

単体テストは、通常自動化されているが、手動で実行されることもある。IEEEは、どちらかが片方より優れているとはしていない[10]。手動による単体テストではステップ・バイ・ステップの手順書を利用することがある。とはいえ、単体テストをする目的は、ユニットを分離し、その正しさを検証することである。自動化は、これを達成するための効率が高く、本項に記載されている多くの利点を備える。逆に、慎重に計画されていないと、注意を欠いた手動の単体テストケースでは、多くのソフトウェアコンポーネントを含む統合テストケースとして実行されて、その結果、全部でないにしても、大部分、本来単体テストで達成されるべき目標に達しない可能性がある。

自動化アプローチを使用しながらも、独立効果を完全に実現するために、テスト対象ユニットやコード本体は、その自然環境の外のフレームワーク内で実行される。換言すれば、元々作成された製品または呼び出し元のコンテキストの外で実行される。そのような独立した形でのテストでは、テスト対象コードと他のユニットや製品内のデータ空間の間の不要な依存関係が明らかになる。依存関係は、その後、除去することができる。

自動化フレームワークを使用しながら、開発者はユニットの正しさを検証するために、基準をコード内に反映させる。テストケースの実行時には、フレームワークのログ中に、基準に満たないテストが記録される。多くのフレームワークは自動的に失敗したテストケースにフラグを立て、まとめて報告する。障害の重症度に応じて、フレームワークは、後続のテストを停止する場合がある。

結果として、伝統的に単体テストはプログラマに、分離され、凝集したコード本体を作成する動機づけとなる。これを常に行えば、ソフトウェア開発における健全な習慣がつくられる。デザインパターン、単体テスト、リファクタリングを組み合わせれば、最善の解決策が出るようになる。

ユニット・テスト・フレームワーク

ユニット・テスト・フレームワークは、ほとんどの場合、コンパイラスイートの一部として配布されていないサードパーティ製品である。それはさまざまな言語のために開発され、単体テストのプロセスを簡素化するのに役立つ。テストフレームワークの例として、 xUnitと総称される、オープンソースのさまざまなコード駆動型テストフレームワーク、また、TBrun, JustMock, Isolator.NET, Isolator++, Parasoft Test (C/C++test, Jtest, dotTEST), Testwell CTA++, VectorCAST/C++のようなプロプリエタリ/商用ソリューションがある。

特定のフレームワークのサポート無しに単体テストを実行することは一般に可能であり、それはテスト対象ユニットを実行し、アサーションおよび例外処理やその他の制御フロー機構を利用して失敗を通知するものである。フレームワークなしの単体テストは、単体テストの採用に参入障壁がある場合有効である。少ない単体テストは全く無いよりもはるかにましであるが、一旦フレームワークが導入されれば、単体テストの追加は比較的楽になる[11]。一部のフレームワークでは、多くの先進的な単体テスト機能が欠落しており、手でコーディングする必要がある。

言語レベルの単体テストのサポート

直接単体テストをサポートしているプログラミング言語もある。その文法は(サードパーティ製または標準の)ライブラリをインポートせずに単体テストを直接宣言することができる。さらに、単体テストのブール式条件が非単体テストコードで使用されるブール式と同じ構文で表現することができる。(ifwhileの文のように)

直接単体テストをサポートしている言語は以下の通り。

備考

  1. Kolawa, Adam; Huizinga, Dorota (2007). Automated Defect Prevention: Best Practices in Software Management. Wiley-IEEE Computer Society Press. p. 75. ISBN 0-470-04212-5. http://www.wiley.com/WileyCDA/WileyTitle/productCd-0470042125.html
  2. Xie, Tao (unknown). Towards a Framework for Differential Unit Testing of Object-Oriented Programs”. 2012年7月23日閲覧。
  3. Fowler, Martin (2007年1月2日). Mocks aren't Stubs”. 2008年4月1日閲覧。
  4. 第9回 テストで重要なのは見極めること”. キーワードでわかるシステム開発の流れ. ITmedia (2008年5月15日). 2014年2月19日閲覧。
  5. Cramblitt, Bob (2007年9月20日). Alberto Savoia sings the praises of software testing”. 2007年11月29日閲覧。
  6. Kolawa, Adam (2009年7月1日). Unit Testing Best Practices”. 2012年7月23日閲覧。
  7. daVeiga, Nada (2008年2月6日). Change Code Without Fear: Utilize a regression safety net”. 2008年2月8日閲覧。
  8. Kucharski, Marek (2011年11月23日). Making Unit Testing Practical for Embedded Development”. 2012年5月8日閲覧。
  9. Agile Emergent Design”. Agile Sherpa (2010年8月3日). 2012年5月8日閲覧。
  10. IEEE Standards Board, "IEEE Standard for Software Unit Testing: An American National Standard, ANSI/IEEE Std 1008-1987" in IEEE Standards: Software Engineering, Volume Two: Process Standards; 1999 Edition; published by The Institute of Electrical and Electronics Engineers, Inc. Software Engineering Technical Committee of the IEEE Computer Society.
  11. Bullseye Testing Technology (2006-2008). Intermediate Coverage Goals”. 2009年3月24日閲覧。
  12. Python Documentation (19992012). unittest -- Unit testing framework”. 2012年11月15日閲覧。

関連項目

外部リンク

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.