• 検索結果がありません。

ユニット・テストの概要

N/A
N/A
Protected

Academic year: 2021

シェア "ユニット・テストの概要"

Copied!
28
0
0

読み込み中.... (全文を見る)

全文

(1)

ユニット・テストの概要

オラクル・ホワイト・ペーパー

(2)

ユニット・テストの概要

概要 ... 3

ユニット・テストとは ... 3

テストファーストの原則 ... 4

テスト・フレームワークの使用... 5

テストの自動化 ... 6

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

JUnit ... 6

モック・オブジェクト... 7

Apache Cactus... 7

HttpUnit/ServletUnit ... 8

utPLSQL ... 8

Clover... 8

Anthill Pro... 9

その他のフレームワーク... 10

ベスト・プラクティス ... 10

テスト・データの使用... 10

SQL 挿入スクリプト ... 10

Java クラス... 11

エクスポート・ファイル... 11

その他のベスト・プラクティス... 12

テスト・コードの分離... 12

setter と getter をテストしない ... 12

CRUD テスト永続クラス... 12

ホワイトボックス・テストを書かない ... 12

クラス階層の追跡... 13

テスト・フィクスチャの使用... 13

関連情報 ... 13

コード例 ... 14

コード・クラスの例: StringUtils ... 14

コード・クラスの例: Person... 16

テスト・ケースの例: StringUtilsTester... 18

テスト・ケースの例: PersonTester ... 20

テスト・スイートの例... 21

テスト・フィクスチャの例... 22

ADF ビジネス・コンポーネントによるユニット・テストの例 ... 23

ADF テスト・フィクスチャ ... 23

ADF CRUD テスト ... 25

(3)

ユニット・テストの概要

概要

プロジェクト・マネージャとして作成するコードの品質に対し最終責任がありな がら、その「品質」がどの程度のものかを実際に認識していますか。開発者から、 コードをリファクタする必要性を指摘されていても、すでに完成しているという 理由でコードの変更を避けていませんか。コードの重要部分の機能状態を知る開 発者が、プロジェクトから抜けることを懸念したことはありませんか。ここでは、 ユニット・テストがプロジェクトの進行における有効性、および開発者スタッフ に関するボトルネックの防止方法について説明します。 開発者として、既存コードのリファクタが緊急に必要と感じていても、コードの 破損を恐れ、手を加えることに躊躇していませんか。難しいコードに関係する問 題で、常に悩みを抱えていませんか。ユニット・テストの開始が必要であるにも かかわらず、どこから始めるかわからないことはありませんか。ここでは、コー ドの品質を向上させ、コードの破損なく確実に変更する方法を説明します。

ユニット・テストとは

ユニット・テストは、個々の機能ユニットに対するテストです。単一な機能ユニッ トのテストは、他の機能ユニットが完了しているかどうかには直接依存しません。 Java アプリケーションにおける、機能ユニットは多くの場合 1 つのメソッドです (常にそうではありません)。ユニット・テストは、最も粒度の高いレベルでのテ ストです。 ユニット・テストのセットアップには、機能ユニットの作成以上に時間を要する ことがあります。確実なメリットを得るには、ユニット・テストがコードの品質 を高め、同時に再利用できる必要があります。結果として、ユニット・テストに より、限られた時間により多くの機能を提供するか、または同じ機能を短時間で 提供することが必要です。これは、設計とプログラミングのエラーの大部分が開 発サイクルの早期で発見されるため、ユニット・テストが統合と評価のテストに 必要な開発者の労力を軽減することで達成されます。テストにより開発者が同じ エラーを繰り返すことを予防することができます。評価テストの際に、コーディ ング・エラーに悩まされることなく、システムが仕様に合っているかどうかを確 認できるようにすることを目的としています。 実際、1 つの特定なプロジェクトに対する検証は、不可能ではないにしても非常 に困難です。それが効果的なユニット・テストにより可能になります。ユニット・ テストを効果的にするためには、次に示す構成が必要です。 • テストファーストの原則の適用 • テスト・フレームワークの使用

(4)

• テストの自動化 次の 3 項では、効率的なユニット・テストへの方法を説明します。

テストファーストの原則

テストファーストの原則では、実際のコードより前にテストが書かれます。実際 には、多少のテストを書いた後に多少のコードを書き、それを繰り返します。手 順を次に示します。 はじめに、まだ存在しないメソッドを呼び出すテストを書きます。意味がないよ うですが、テストを書くには、提供が必要なパラメータと、メソッドから返され る結果を考慮する必要があります。 ある文字列を他の文字列に置き換える substitute()というメソッドを書くと仮定し ます。このメソッドのテストを次に示します。

public static void testSubstitute() {

assertEquals("a bear", StringUtils.substitute("a goose", "goose", "bear")); } substitute()メソッドはまだ存在しないため、このテストのコンパイルは当然失敗し ます。したがって、次の手順として適切なシグネチャを持つメソッドを書き、テ ストを実行する値を返します。最初は、次のとおりハードコードされた値を返す ことができます。

public static String substitute (String text, String replace, String with)

{

return new String("a donkey"); }

メソッドが‘a bear’ではなく‘a donkey’を返し、このテストは失敗となります。テス トが実際に機能するかの検証には、1 度は失敗するほうが好ましいと考えます。 ここで、テストを成功させる値を返すようにテストを変更します。

public static String substitute (String text, String replace, String with)

{

return new String("a bear"); } この段階より、substitute()メソッドのコードが破損していないかを各手順で確認で きます。 次の手順では、ハードコードされた substitute()メソッドの戻り値を、‘text’パラ メータで指定された文字列の中で‘replace’パラメータに一致したすべての部分文 字列を‘with’パラメータで指定された文字列に置き換えるように変更します。最初 に一致した部分文字列だけでなくすべての部分文字列の置換えをアサートするテ ストや、NULL パラメータの適切な処理をアサートするテストなど、その他のテ ストを段階的に追加します。追加した各テスト・ケースは直ちに実行します。テ

(5)

ストが失敗した場合、そのコード・ユニットの実行が成功するように修正して続 行します。 開発者のなかには、テストファーストの原則に基づく開発とテスト駆動開発を区 別し、テスト駆動開発の「極端」なものがテストファーストの原則に基づく開発 であると考える人もいます(ただし、両方を同一とする考え方もあります)。テ スト駆動開発とは、コードと同時にテストを書く手法です。ただし、ここでは名 称の違いは問題ではありません。どちらにしても責任を一層明確にし、よりクリー ンなインタフェースを使用して、より小さなメソッドを作成すると、効率よく作 業を進められます。必要に応じて、ユニット・テストできるコードのみを書くこ とも可能です。 後述のとおり、ユニット・テストをサポートする IDE プラグインは、既存メソッ ドをベースにテストのためのスタブを作成します。これは、テストファーストの 原則を適用した手法とはいえません。実行状態に関係なく、テストファーストの 原則の理念を考慮し、メソッドの完成後ではなく、できるかぎり早期にテストを 作成してください。それにより、すぐにエラーを発見するユニット・テストのメ リットが活かされます。 テストファーストの原則を適用するメリットは、次の 3 点です。 • 第 1 に、機能の動作ではなく、機能ユニットが何を達成する必要があるか について考えるところから始めます。これにより、この機能にとって不 要なコードを書く必要がありません。 • 第 2 に、コードを書きながら各手順をテストするメカニズムにより、エ ラーの発生をすぐに発見できます。 • 第 3 に、より明確にインタフェースと責任範囲が定義され、より明確なメ ソッドが書けるように支援をします。 • このように、テストファーストの原則の適用により、短時間でより優れた コードを書くことができます。 ここまでの説明で、ユニット・テストは、テスト・チームが後からユニット・テ ストを実行するのではなく、開発者自身が行う必要性が明らかになりました。適 切に構成されたコード内におけるエラーの発生と同時にそれを発見できるほうが、 テスト・チームにブラインド・スポットの検出を依頼するよりも効果的です。ユ ニット・テストの一貫性と、高品質の保証に、開発者同士による相互のレビュー、 またはユニット・テスト・コーディネータによるレビューも必要です。さらに、 有効レベルの低いユニット・テストに対する「安全策」としても機能する統合テ ストにより、高い精度のユニット・テストが実現します。 また、顧客の知識レベルによっては、顧客にユニット・テストへの参加を促した り、顧客によるテスト・ケースの決定や、それに対する助言の依頼も考えられま す。

テスト・フレームワークの使用

前述の例では、assertEquals()メソッドが使用されていました。明らかに、このメソッ ドは、文‘substitute ("a goose", "goose", "bear")’の実際の結果が予想結果である‘a

(6)

ンが失敗すると、簡潔なエラー・メッセージが提供されます。しかし、2 つの文 字列を比較することですべてのテストは実装できません。基本的には、あるタイ プのオブジェクトを同じタイプの他のオブジェクトと比較します。 また、大部分のアプリケーションは、多数のテスト・ケースを書く必要がある多 数のメソッドで構成されます。コードの変更によりアプリケーションが破損して いないことの確認には、すべてのテストの定期的な実行が必要になります。また、 各テストの実行は、単一に行うより複数に行う方が効率的です。1 つのアサーショ ンが失敗した場合も、他のテストは続行されるべきです。最後に、どのアサーショ ンが失敗したかについて、明確なフィードバックの提供が必要です。 それには、ユニット・テスト・フレームワークが必要です。優れたテスト・フレー ムワークは、少なくとも次の機能を提供します。 • 多数のオブジェクト・タイプに基づくアサーションを行う便利なメソッド。 • バグ修正とリファクタをサポートするテストを常時実行する機能。 • 同時に実行できるテストのセットで構成された「テスト・スイート」を作 成する機能。 • 実行されたすべてのテストの概要を表示する、使いやすいインタフェース。 1 つ以上のテストが失敗した場合、それを表示します。 大きいサイズのアプリケーションに対するユニット・テストの実行には、ユニッ ト・テスト・フレームワークがもっとも効果的です。

テストの自動化

大きいアプリケーションの場合、ユニット・テストの自動化が必要です。自動化 により、すべてのテストを定期的に自動で実行し、その結果を記録して後でレ ビューできます。開発者は、作成または変更したコードと、それによって直接影 響される他のコードに対しユニット・テストを実行するだけです。テストが正常 に実行されるかぎり、コードは定期的に自動確認されます。それが行われない場 合、問題の原因となっているすべてのコードまたはテストを修正します。これに より、自動化されたユニット・テストによる「安全策」が提供されます。つまり、 新しいコードや変更されたコードに起因する予期しない副作用を発見し、早期に バグを検出できます。 最もシンプルな形のユニット・テストでは、テスト・スイートの階層を作成して テストが自動化されます。トップに置かれた 1 つまたは少数のテスト・スイート により自動的にすべてのユニット・テストが実行されます。 テストを自動化し、夜間ビルド中にすべてのテストを実行し、翌日は失敗したテ ストに重点を置いてください。

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

JUnit

Java における事実上の業界標準のユニット・テスト・フレームワークは JUnit(オー プン・ソース)です。OTN にアカウントを持つ場合、メニューから「ヘルプ」 →

「更新の確認」 → 「JUnit Extension をチェック」 → 「次へ」により、JDeveloper

(7)

[JDeveloper home]\jdev\lib\ext ディレクトリ(Oracle JDeveloper 10.1.2 以前のバー ジョンの場合)に自動的にダウンロードします。http://www.junit.org から、スタ ンドアローン・バージョンもダウンロードできます。JUnit は前述の機能をすべて 提供し、クライアント JVM で稼働する Java クラスをテストできます。このプラ グインを使用して、既存メソッド用のユニット・テスト・スタブを簡単に作成で き、既存のユニット・テストのセットについてテスト・スイートを作成できます (下図参照)。再利用可能なテスト・フィクスチャのセットも作成できます。これ については後述します。テストファーストの原則に従う場合、メソッドのシグネ チャを定義した直後、実際のコードを書く前に、そのメソッド用のテスト・スタ ブを作成する必要があります。このドキュメントの最後にあるコード例に、JUnit の使用方法を詳しく示します。 JDeveloper JUnit プラグイン

モック・オブジェクト

他のソフトウェア・コンポーネントに依存するコードが多数ある場合、それらに 対するユニット・テストの作成には複雑なセットアップが必要です。データベー スに接続が必要な永続コード、または HTTP リクエストとレスポンスを処理する サーブレットやリモート・サーバーに接続が必要な EJB のリモート・インタフェー スなど、単にクライアント JVM での稼働だけではないコードについて考えてくだ さい。ある程度は、いわゆるモック・オブジェクトによって、より幅広いコンテ キストで稼働する必要性を回避できます。たとえば、データベース接続をセット アップして、データベースからオブジェクトを取り出すかわりに、データベース・ オブジェクトのような動作をするモック・オブジェクトを使用できます。モック・ オブジェクトを開発および使用するための特別なフレームワークを、 http://www.mockobjects.com からダウンロードできます(オープン・ソース)。

Apache Cactus

モック・オブジェクトを使用できず、他のソフトウェア・コンポーネントとの統 合が必要な場合は、Apache Cactus を使用します。Cactus は、JUnit を拡張し、OC4J や Tomcat のようなコンテナからユニット・テストを実行します。これにより、 Cactus では、EJB、サーブレット、タグ・ライブラリ、サーブレット・フィルタ、 JSP ページ、XSLT など、コンテナ内で稼働するものをすべてテストできます。 Cactus はhttp://jakarta.apache.org/cactus/ からダウンロードできます(オープン・

(8)

ソース)。Cactus の Web ページに書かれているように、Cactus を使用すればモッ ク・オブジェクトの作成が必要ないだけでなく、後述する HttpUnit のようなフレー ムワークが必要な場合でも、機能テストを実行することができます。

HttpUnit/ServletUnit

HttpUnit により、Web アプリケーションの HTTP リクエストをエミュレートでき ます。HttpUnit は、フォーム送信、JavaScript、基本フォーム認証、Cookie、ペー ジ・リダイレクトなどの Web ブラウザの動作(の一部)をエミュレートします。 必ずしもコンテナ内からのテストが必要ない場合には、HttpUnit を ServletUnit (サーブレット・コンテナをエミュレート)および JUnit とともに使用することで、

Cactus のかわりとして使用できます。HttpUnit と ServletUnit は、どちらも

http://httpunit.sourceforge.net/ からダウンロードできます(オープン・ソース)。 HttpUnit はブラウザの全ての機能をエミュレートできないため、ブラウザにおけ る妥当性チェックのような、UI テストは HttpUnit で実行できない点に注意してく ださい。

utPLSQL

Oracle データベースを使用している場合、バックグラウンド・バッチ・プロセス などのコードを、PL/SQL を使用して開発することがよくあります。PL/SQL は、 http://utplsql.sourceforge.net/ からダウンロードできる utPLSQL を使用してユニッ ト・テストできます(オープン・ソース)。

Clover

ユニット・テストのもう 1 つの重要な側面は適用範囲の監視です。つまり、実際 にユニット・テストを受けているコードを調べます。それには、Clover を使用し ます。Jakarta Ant ビルド・ファイルで、Clover をビルド・プロセスに統合できま す。Clover は、何が実行されているかを記録するために、クラス・ファイルに特 別なコードを追加します。この記録を使用して、実行されたメソッドおよび、メ ソッド内でどの文が何回どのような順序で実行されたかをレポートできます。そ のため、コードが一層効率的に実行されるように再構成する場合にも有効です。 また、Clover を JDeveloper 10g に統合して、実行された内容をコード自体に視覚 的にも表示できます(下図参照)。Clover の体験版はhttp://www.thecortex.net/clover/ からダウンロードできます。Clover は無償ではありませんが、中規模から大規模 のプロジェクトでの使用をお薦めします。

(9)

Clover レポートの例

JDeveloper に統合できる Clover

Anthill Pro

Anthill Pro は、ソフトウェア・ビルド管理サーバーです。Anthill Pro により、Apache Ant のようなビルダー・フレームワークに基づくビルド・プロセス(ユニット・

(10)

テストと Clover を含む)をスケジュールできます。CVS などのバージョン管理シ ステムと統合できるため、ビルドの開始前に、バージョン管理システムからソー ス・コードを抽出できます。そのため、たとえば夜間ビルドを完全に自動化でき ます。それが終了すると、テスト・マネージャに電子メールを送り、ビルドのス テータスについて通知できます。ビルド・プロセスに関するレポートを作成しま す。特に、問題のあるビルド、失敗したテストを表示します。Anthill Pro の評価 版は、http://www.urbancode.com/products/anthillpro からダウンロードできます。 Anthill は無償ではありませんが、中規模から大規模のプロジェクトでの使用をお 薦めします。

その他のフレームワーク

他にも、JUnit を活用するフレームワークや拡張機能が多数あり、その多くはオー プン・ソースです。詳細は、JUnit.org (http://www.junit.org) の「Extensions」ペー ジを参照してください。特に、コード適用範囲、パフォーマンス・テスト、ビル ド・プロセス、統合と評価テスト、データ・ロード、JSP テストなどのフレーム ワークや拡張機能があります。

ベスト・プラクティス

本書では次に示すベスト・プラクティスについて説明してきました。 • ユニット・テストで最大の効果を得るために、テストファーストの原則を 適用する。 • 常にユニット・テスト・フレームワークを使用する。 • 定期的に実行するすべてのユニット・テストを自動化する さらに次のようなベスト・プラクティスも考慮してください。

テスト・データの使用

ユニット・テストは、独立した任意の順序で実行できることが必要です。これは、 あるユニット・テストで使用されるテスト・データを他のユニット・テストで変 更してはならないことを意味します。それには、テスト・データの「ベース・セッ ト」を 1 つ作成する方法がベスト・プラクティスです。すべてのユニット・テス トは、このベース・セットの存在を前提にします。ユニット・テストでデータの 変更が必要な場合、テスト自体がデータを作成します。他のユニット・テストと の干渉を防ぐために、テストが作成したデータは後で削除します。 ベース・セットは、SQL 挿入スクリプト、このジョブを行うためのデータを作成 する Java クラス、またはユニット・テストの実行前にインポートされるエクスポー ト・ファイルで構成できます。

SQL 挿入スクリプト

SQL 挿入スクリプトの使用には、次の 2 つのメリットがあります。 • SQL 挿入スクリプトは、Java の知識を持たないユーザーでもメンテナン スできます。

(11)

• SQL 挿入スクリプトは、Java レイヤーからはテストできないデータベー ス永続ロジックをテストできます。 前述の 2 点目の例として、TopLink での継承があります。複数のクラスが 1 つの 表にマップされている場合、TopLink がオブジェクトのサブクラスを調べるため に使用するクラス・インジケータ・フィールドを作成しておきます。事前定義さ れたクラス・インジケータ・フィールドで使用できる値とは異なる値を使用して TopLink からデータを作成できません。ただし、SQL 挿入スクリプトでは無効な 値を挿入できるため、結果的に TopLink を通じてアクセスできないデータが作成 される可能性があります。これの防止には、クラス・インジケータ・フィールド に対して正しい値の使用を規定する CHECK 制約またはデータベース・トリガー を作成します。 永続ロジックは、(直接には)ビジネス・ ロジックに関係していませんが、データ ベースの正常な動作や、Java 永続レイ ヤーとの正常な通信に必要なロジックに 関係しています。最も一般的な例は、主 キーと外部キーの制約です。 大規模なアプリケーションのほとんどは、Java 永続レイヤー以外のチャネルを通 じてデータが変更される可能性があります。たとえば、他のデータ・ソースを使 用する PL/SQL インタフェース、データの修正メンテナンス、システム・アップ グレード中のデータ変換などです。そのような状況が発生する可能性がある場合、 永続ロジックを必ずテストします。 SQL 挿入スクリプトのサイズが非常に大きい場合、メンテナンスに時間がかかり、 エラーが発生しやすくなるというデメリットがあります。このような問題に対し て、他の 2 つの方法を検討してください。

Java クラス

データ作成クラスには、次のメリットがあります。 • データ作成クラスは、データの挿入に関して永続レイヤーの正常な動作も テストします。これは、オブジェクト-リレーショナル・マッピングのエ ラーの早期発見に有効です。 • IDE により提供されるコードのサポートによりエラーを防止できます。 Java コンパイラを使用すると、属性またはクラスの削除または名前変更 に関するベース・セットの変更の必要性を簡単に発見できます。このよ うに、ベース・セットは、他の 2 つの選択肢の場合よりも簡単にメンテ ナンスできます。 デメリットとしては、データ作成クラスの初期セットアップの作成に時間がかか ることです。ただし、初期セットアップの部分的な生成方法も考えられます。

エクスポート・ファイル

エクスポート・ファイルには、次のメリットがあります。 • 開始状態をロードする際に変換スクリプトを適用できるため、データ変換 をテストするサポートが提供されます。これは特に、すでに本番に移行 しているシステムに適した代替方法です。 • PL/SQL Developer や Toad のようなユーティリティ・ツールを使用してテ スト・データをメンテナンスできます。 第 1 のメリットは、デメリットでもあります。データベースの構造が変更される たびにデータ変換が必要になるため、柔軟性が低いことや本番のシステムに関係 していない変更も同様にデメリットとなります。変換は、多くの場合時間がかか

(12)

るため可能なかぎり避けます。システムを本番に移行し、他の 2 つの代替方法の 1 つを使用した後、または変換のテストに、エクスポート・ファイルの使用を検 討してください。

その他のベスト・プラクティス

テスト・コードの分離

テスト・コードはアプリケーション・コードから分離してください。テスト・コー ドを本番にデプロイしてはならないというのが、その最大の理由です。それはデ プロイ・プロセスを複雑にし、jar ファイルが必要以上に大きくなるだけでなく、 テスト・コードが隠しておく必要があるメソッドも公開することがあるため、セ キュリティのリスクが高くなります。パッケージ保護されたメソッドのテストに は(必要に応じて)、テストされるコードと同じパッケージ内の別のプロジェク トにテスト・コードをデプロイします。

setter と getter をテストしない

Java Bean プロパティ用の getter メソッドと setter メソッドで特別なコードを書い ていないかぎり、これらのメソッドのためにユニット・テストを書くことに時間 をかける価値はありません。getter と setter に対するユニット・テストを書くには かなりの時間が必要になります。しかし、大部分の getter と setter は、ユニット・ テストが適用される他のメソッドにより任意の時点で呼び出されるため、他の方 法では発見されない getter と setter の問題をテストで発見する可能性は非常に低く、 テストに時間をかける価値がありません。 一般的ルールは、分割できる適切なテストを書くことです。テスト自体が分割で きない場合は、単純すぎることを意味します。

CRUD テスト永続クラス

TopLink を使用する場合、setter や getter のテストと異なり、永続コードの作成、 取得、更新、削除のテストは有意義です。これは、たとえばシーケンスのエラー など、大部分のオブジェクト-リレーショナル・マッピング・エラーの発見に役立 ちます。作成には、必須な関連属性のみの提供で十分です。更新には、1 つまた は 2 つの属性に制限できます。多くの場合、CRUD テストの大部分が生成できま す。

ホワイトボックス・テストを書かない

ユニット・テストでは、クラスがどのように実装されるかではなく、クラスが何 を行うかに重点を置く必要があります。クラスのホワイトボックス・テストは、 private および protected メソッドもすべてテストすることになり、それによる実装 の変更によって、テストも変更が必要になる可能性が高くなります。したがって、 既存のテストを使用して何も破損しなかったことを実証する場合、ホワイトボッ クス・テストは効果的なリファクタの妨げになります。同様に、インタフェース の実装クラスではなく、インタフェースに対してテストを記述することをお薦め します。

(13)

クラス階層の追跡

クラス階層のためにユニット・テストを作成する場合、ユニット・テストのため にパラレルな階層のセットアップが必要になります。これには、サブクラスのユ ニット・テストを実行する場合、サブクラスのコンテキスト以外でもスーパーク ラスのすべてのテストが自動的に実行されるというメリットがあります。スー パークラスが抽象メソッドを持つ抽象の場合、抽象ユニット・テストを持つ抽象 テスト・クラスも作成できます。このテスト・クラスのサブクラスは、その後スー パークラスの抽象ユニット・テストの実装が必要なため、これらのテストは確実 に実装されます。

テスト・フィクスチャの使用

ユニット・テストを作成する場合も、通常のコードを記述場合と同様に、コード の重複を避けるための配慮が必要です。その方法として、ユニット・テストの前 にコンテキストをセットアップに使用し、後で破棄できる再利用可能なコードで 構成された、テスト・フィクスチャを作成する方法があります。これは、たとえ ば、データベース接続のセットアップ、特別な属性値を持つ一部のオブジェクト のインスタンス化などに使用できます。

関連情報

ユニット・テスト全般と JUnit の詳細は、JUnit のサイト(http://www.junit.org)を

参照してください。そこから、JUnit の Javadoc をダウンロードでき、有意な FAQ も含まれています。Wiki のサイト(http://c2.com/cgi/wiki?UnitTest)にも、ユニッ

ト・テストに関するその他の情報が含まれています。ADF の各種機能の総合的な 例(ユニット・テストを含む)が、ADF Toy Store Demo

(http://www.oracle.com/technology/products /jdev/collateral/papers/10g/adftoy store.html)にあります。

(14)

コード例

コード・クラスの例: StringUtils

次のクラスは、文字列内の特定の文字列に一致するすべての部分文字列を置換す る substitute()メソッド、文字列が“bear”である場合は true を返す isABear()メソッド、 空の文字列“ ”を NULL 値に変換する trimEmptyToNull()メソッドを実装します。

package oracle.code.utils;

import java.util.List; import java.util.ArrayList; import java.util.StringTokenizer;

public class StringUtils {

private static String BEAR = new String("bear"); private static String EMPTY = new String("");

/** * 文字列内の特定な文字列に一致するすべての部分文字列を置換します * * これは単なる例です。この置換メソッドのかわりに * 通常は in.ReplaceAll(find,newString)を使用します * * @param in: 置換を行う文字列 * @param find: 置換される部分文字列 * @param newString: この文字列に置換 * @return in に一致するすべての部分文字列を * newString に置換した結果の文字列 */

public static String substitute (String in, String find, String newString)

{

// いずれかの文字列が NULL である場合、元の文字列を返す if ( in == null || find == null || newString == null) {

return in; }

char[] working = in.toCharArray();

StringBuffer stringBuffer = new StringBuffer();

// find 文字列が見つからない場合、元の文字列を返す int startindex = in.indexOf(find);

if (startindex < 0) { return in; } int currindex=0; while (startindex > -1) {

for(int i = currindex; i < startindex; i++) {

stringBuffer.append(working[i]); }

(15)

currindex = startindex;

stringBuffer.append(newString); currindex += find.length();

startindex = in.indexOf(find,currindex); }

for (int i = currindex; i < working.length; i++) { stringBuffer.append(working[i]); } return stringBuffer.toString(); } /** * s が"bear"である場合、true を返す * * @param s: テストする文字列

* @return s が"bear"である場合は true */

public static boolean isABear(String s) { return BEAR.equals(s); } /** * 空の文字列""を NULL 値に変換 * * @param string: 変換する文字列

* @return 文字列が空の場合は NULL、そうでない場合は string */

public static String trimEmptyToNull (String string) {

if (string == null || EMPTY.equals(string.trim())) { return null; } return string; } }

(16)

コード・クラスの例: Person

次のクラスは、名前と年齢をプロパティに持つ Person を実装する非常に簡単な例 です。これは、ユニット・テストで必要になる equal メソッドもオーバーライドし ます。前述のとおり、getter と setter はテストしません。

package oracle.code.model;

public class Person {

private String name; private Integer age;

public Person () { } /** * 非デフォルト・コンストラクタ * * @param name: 人物の名前 * @param age: 人物の年齢 */

public Person (String name, Integer age) { this.name = name; this.age = age; } /** * 人物の名前を設定 * * @param name: 人物の名前 */

public void setName(String name) { this.name = name; } /** * 人物の名前を返す * * @return */

public String getName() { return this.name; } /** * 人物の年齢を設定 * * @param age: 人物の年齢 */

public void setAge(Integer age) { this.age = age; } /** * 人物の年齢を取得 * * @return 整数の年齢

(17)

*/

public Integer getAge() { return this.age; } /** * 指定された人物とこの人物と比較 * * @param p: 比較対象の人物

* @return this の人物と p が等しい場合は true */

public boolean equals(Object p) { // p が this と等しい場合は true を返す if (this == p) { return true; } // p が人物でない場合は false を返す if (!(p instanceof Person)) { return false; } // すべての属性が等しい場合は true を返す Person q = (Person)p;

return ( (this.name == null ? q.name == null: this.name.equals(q.name))

&& (this.age == null ? q.age == null: this.age.equals(q.age))

); }

(18)

テスト・ケースの例: StringUtilsTester

次のクラスには、StringUtils クラスのためのユニット・テストが含まれています。 このテストの詳細は、インラインで説明されています。このクラスは、次のよう にコードを使用せずに、testSubstitute()メソッド・スタブを作成した JDeveloper の JUnit プラグインで作成されました。 • テストを追加するプロジェクトを右クリック→ 「New」 → 「General」 → 「Unit Tests (JUnit)」 → 「Test Case」

• 「Subject」タブで、テスト・ケースの作成が必要なクラスまでブラウズし ます。同じタブに、メソッドのツリーが表示されます。

• メソッドのツリーを展開し、テスト・ケースに含めるメソッドをすべてク リックします。

• 「Class」タブで、クラス‘StringUtilsTester’を指定します。デフォルトの Package Name(‘oracle.code.utils.test’)と Extends(‘junit.framework.TestCase’) を受け入れます。 • 完了。 この後、テスト・ケースにあるメソッドの名前を変更して、ネーミング・メソッ ドの事実上の標準を適用します。 package oracle.code.utils.test; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import oracle.code.utils.StringUtils; import oracle.code.model.Person;

public class StringUtilsTester extends TestCase {

public StringUtilsTester(String sTestName) {

super (sTestName); }

/**

* String substitute (String, String, String) */

public void testSubstitute() {

// A: このテストは成功する

assertEquals("a bear", StringUtils.substitute("a goose", "goose", "bear"));

// B: このテストは失敗する

assertEquals("a goat", StringUtils.substitute("a goose", "goose", "bear"));

// C: このテストは失敗する

// 失敗したときに表示されるメッセージも指定

assertEquals("substitute goose by bear", "a goat", StringUtils.substitute("a goose", "goose", "bear"));

(19)

// D: assertTrue/False の使用例

assertEquals("a bear is a bear", true, StringUtils.isABear("bear"));

assertTrue("a bear is a bear", StringUtils.isABear("bear")); assertFalse("a goat is not a bear", StringUtils.isABear("goat")); }

/**

* String trimEmptyToNull(String) */

public void testTrimEmptyToNull() {

// E: assertNull/assertNotNull の使用例

assertEquals("trim empty string to null is null", null, StringUtils.trimEmptyToNull(""));

assertNull("trim empty string to null is null", StringUtils.trimEmptyToNull(""));

// trimEmptyToNull の引数が殻でない場合のテスト assertNotNull("trim x to null is not null", StringUtils.trimEmptyToNull("x"));

} }

(20)

テスト・ケースの例: PersonTester

次のクラスには、Person クラスのための JUnit テスト・ケースが入っています。こ のテストの詳細は、インラインで説明されています。前述の例同様、このクラス は JDeveloper の JUnit プラグインを使用して作成されました。 package oracle.code.model.test; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import oracle.code.model.Person;

public class PersonTester extends TestCase {

public PersonTester(String sTestName) {

super(sTestName); }

/**

* boolean equals (Person) */

public void testequals() {

// オブジェクトに基づくアサーション

Person jan = new Person("Jan", new Integer(10)); Person piet = new Person("Jan", new Integer(10)); Person joris = new Person("Jan", new Integer(10));

Person fred = new Person();

Person klaas = new Person("Klaas", new Integer(10));

// F: assertEquals は // 再帰型

assertEquals("jan must equal itself", jan, jan); // 対称型

assertEquals("jan equals piet", jan, piet); assertEquals("so piet must equal jan", piet, jan); // 他動型

assertEquals("piet equals joris", piet, joris); assertEquals("so jan must equal joris", jan, joris); // 特殊ケース

assertEquals("fred equals itself", fred, fred);

assertFalse("fred not equals null", fred.equals(null)); assertFalse("jan must not equal klaas", jan.equals(klaas)); // G: オブジェクトに関する assertSame

assertSame("fred is same object as fred”, fred, fred); assertNotSame("jan is not same as piet", jan, piet); }

(21)

テスト・スイートの例

次のクラスは、PersonTester と StringUtilsTester を開始する JUnit テスト・スイート です。テスト・スイートは、他のテスト・スイートでも構成できるため、様々な レベルで複数のテストを実行できます。すべてのユニット・テストを(間接的に) 実行する 1 つのスーパー・テスト・スイートも作成できます。 このテスト・スイートは、次のように JDeveloper の JUnit プラグインで作成され ました。 • テストを入れるプロジェクトを右クリック → 「New」 → 「General」 → 「Unit Tests (JUnit)」 → 「Test Suite」

• 「Class」タブで、クラス‘AllTests’とパッケージ‘oracle.code.test’を指定し ます。デフォルトの Extends(‘java.lang.Object’)を受け入れます。 • 「Cases」タブで、「Add」をクリックすることにより、すべてのテスト・ ケースを追加し、個々のテスト・ケースにナビゲートします。 • 完了。 新しいクラスの追加が必要な場合、このテスト・スイートを削除して、新しいテ スト・スイートを作成するか、または特定のテスト・ケースを手動で追加します。 Suite()メソッドは、テスト・ケースをクラスとして追加します。イントロスペク ションを使用して、JUnit はテスト・ケースからテスト・メソッドを抽出できます。 したがって、これらのメソッドの名前は、“test”で始まる必要があります。他に、 addTest()メソッド(addTestSuite()のかわりに)を使用して、手動でテスト・メソッ ドを追加する方法もあります。suite()メソッドをテスト・ケースに追加し、そこに 手動でテスト・メソッドも追加できます。テスト・メソッドの特定なサブセット の実行以外では、自動スイート抽出を推奨します。これにより、テスト・ケース の追加または削除ごとにスイート作成コードを更新する必要がありません。 package oracle.code.test; import junit.framework.Test; import junit.framework.TestSuite;

public class AllTests {

public static Test suite() {

TestSuite suite = new TestSuite("AllTests");

suite.addTestSuite(oracle.code.model.test.PersonTester.class);

suite.addTestSuite(oracle.code.utils.test.StringUtilsTester.class);

return suite; }

public static void main (String args[]) {

String args2 [] = {"-noloading", "oracle.code.test.AllTests"};

junit.swingui.TestRunner.main(args2); }

(22)

テスト・フィクスチャの例

次の例では、PersonTester テスト・ケースを再度使用します。SetUp()と tearDown() という 2 つのメソッドが追加されました。JUnit は、各テスト(名前が‘test’で始ま る個々のメソッド)の実行前に自動的に setUp()メソッドを実行して、テスト・フィ クスチャをインスタンス化します。tearDown()メソッドは、各テスト後に実行され、 テスト・フィクスチャを破棄します。テスト・ケースに他のメソッドのための 1 つまたはそれ以上のテストが入っている場合、それらのテストはテスト・フィク スチャを再利用できます。 package oracle.code.model.test; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import oracle.code.model.Person;

public class PersonTester extends TestCase {

private Person jan; private Person piet; private Person joris; private Person fred; private Person klaas;

public PersonTester(String sTestName) {

super(sTestName); }

public void setUp() {

this.jan = new Person("Jan", new Integer(10)); this.piet = new Person("Jan", new Integer(10)); this.joris = new Person("Jan", new Integer(10)); this.fred = new Person () ;

this.klaas = new Person("Klaas", new Integer(10)); }

public void tearDown() { this.jan = null; this.piet = null; this.joris = null; this.fred = null; this.klaas = null; } /**

* boolean equals (Person) */

public void testEquals() {

// F: assertEquals は // 再帰型

assertEquals("jan must equal itself", jan, jan); // 対称型

assertEquals("jan equals piet", jan, piet); assertEquals("so piet must equal jan", piet, jan);

(23)

// 他動型

assertEquals("piet equals joris", piet, joris); assertEquals("so jan must equal joris", jan, joris); // 特殊ケース

assertEquals("fred equals itself", fred, fred);

assertFalse("fred not equals null", fred.equals(null)); assertFalse("jan must not equal klaas", jan.equals(klaas)); // G: オブジェクトに関する assertSame

assertSame("fred is same object as fred", fred, fred); assertNotSame("jan is not same object as piet", jan, piet); } }

ADF ビジネス・コンポーネントによるユニット・テストの例

ADF テスト・フィクスチャ

次に、ADF アプリケーション・モジュールでテスト・ケースを提供するテスト・ フィクスチャの作成方法を示します。このアプリケーション・モジュールは、HR データベース・スキーマへの接続を提供し、HR スキーマへのデータの格納のため、 または HR スキーマからのデータの取得のために使用できるビュー・オブジェク トを公開します。前述の例と異なり、このテスト・フィクスチャは、独立したク ラスとして作成され、データベースへの接続に ADF アプリケーション・モジュー ルを必要とするすべてのテスト・ケースに再利用できます。 このテスト・フィクスチャは、次に示す JDeveloper の JUnit プラグインで作成さ れました。 • テストを入れるプロジェクトを右クリック → 「New」 → 「General」 → 「Unit Tests (JUnit)」 → 「Business Components Test Fixture」

• ダウンロード・リストから、使用するアプリケーション・モジュールを含 むビジネス・コンポーネント・プロジェクトを選択します。 • ダウンロード・リストから、アプリケーション・モジュールを選択します。 • 適切な構成を選択します。 • 完了。 作成後、静的文字列に対するクラス変数の使用やネーミング規則など、重要なコー ディング・プラクティスにコードが多少変更されました。さらに、返されるアプ リケーション・モジュールは、Bundled Exception Mode が‘true’に設定されていま す。したがって、失敗したテストは、トランザクションのコミット時にバンドル されて一度に提示されるため、失敗はすぐに他の手順の実行を妨げません。 この例では、JDBC DataSource のかわり に、名前付き JDeveloper 接続を使用する 構成が選択されました。これにより、 J2EE Web/EJB コンテナのコンテキスト 内ではなく、テスト・ケースをスタンド アローンで実行できます。

(24)

package oracle.hr.testfixtures;

import oracle.jbo.*; import oracle.jbo.client*;

public class HrServicesConnectFixture {

private static final String AM = "oracle.hr.services.HrServices"; private static final String CF = "HrServicesLocal";

private ApplicationModule applicationModule;

public HrServicesConnectFixture() { } /** * アプリケーション・モジュールのインスタンスを獲得 */

public void setup() throws Exception {

this.applicationModule =

Configuration.createRootApplicationModule(AM, CF); /*

* ADF/Struts サポートと同様、Bundled Exception Mode を使用 */ this.applicationModule.getTransaction().setBundledExceptionMode( true ); } /** * アプリケーション・モジュールのインスタンスをクリーンアップ */

public void tearDown() throws Exception { Configuration.releaseRootApplicationModule(this.applicationModule, true); } /** * 割り当てられたアプリケーション・モジュールを返す * * @return 割り当てられたアプリケーション・モジュール */

public ApplicationModule getApplicationModule() {

return this.applicationModule; }

(25)

ADF CRUD テスト

次の例は、前のテスト・フィクスチャで ADF ビジネス・コンポーネントのエンティ ティ・ライフサイクルをセットアップする方法を示しています。最も基本的なエ ンティティ・ライフサイクルは、データベースで新しいデータの作成後にそれを 取り出し、更新後にそれを最終的に削除します。 特に、削除が失敗すると、データベースは変更された状態のままです。前述した とおり、これは各テストは同じ初期セットアップでの開始が必要で、他のテスト の成功または失敗に依存させない理由の 1 つです。 最後に、ManagerId と DepartmentId のためにハードコードされた値を使用して部門 のマネージャと位置を設定するのではなく(例にあるとおり)、事前定義された 名前に基づきデータベースからマネージャと部門を検索して、これらのオブジェ クトの ID をかわりに使用することができます。 package oracle.hr.dataaccess; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import oracle.hr.testfixtures.HrServicesConnectFixture; import oracle.jbo.JboException; import oracle.jbo.Key; import oracle.jbo.ViewObject; import oracle.jbo.Row;

public class DepartmentsTests extends TestCase {

private static String DEPARTMENTID = "DepartmentId"; private static String DEPARTMENTNAME = "DepartmentName"; private static String MANAGERID = "ManagerId";

private static String LOCATIONID = "LocationId"; HrServicesConnectFixture connectionFixture = new HrServicesConnectFixture();

public DepartmentsTests (String sTestName) {

super(sTestName); }

public void testDepartmentsViewObjectLifeCycle() { ViewObject departmentsView = this.connectionFixture.getApplicationModule().findViewObject( "Depart ments"); /** * ビュー・オブジェクトが NULL でないことを確認 */ assertNotNull(departmentsView); /** * 部門の挿入をテスト */

Row departmentRow = departmentsView.createRow(); try

(26)

{ departmentRow.setAttribute(this.DEPARTMENTID, "1"); departmentRow.setAttribute(this.DEPARTMENTNAME, "test department"); departmentRow.setAttribute(this.MANAGERID, "100"); departmentRow.setAttribute(this.LOCATIONID, "1000"); }

catch (JboException jbo) {

fail("Should not have thrown this " + jbo.getClass().getName() +

" exception yet due to bundled exception mode"); } this.connectionFixture.getApplicationModule().getTransaction(). commit(); /** * 新しい部門の問合せをテスト */

Row[] departmentRows = new Row[0]; try

{

Key key = departmentRow.getKey();

departmentRows = departmentsView.findByKey(key, 1); assertEquals("expect to find one department with id 1", departmentRows.length,

1) ; }

catch (JboException jbo) {

fail("Should not have thrown this " + jbo.getClass().getName() +

" exception yet due to bundled exception mode"); } /** * 新しい部門の更新をテスト */ try { departmentRows[0].setAttribute(this.DEPARTMENTNAME, "testing department"); this.connectionFixture.getApplicationModule().getTransaction(). commit(); }

catch (JboException jbo) {

fail("Should not have thrown this " + jbo.getClass().getName() +

" exception yet due to bundled exception mode"); } /** * 新しい部門の削除をテスト */ try {

(27)

departmentRows[0].remove();

this.connectionFixture.getApplicationModule().getTransaction(). commit();

}

catch (JboException jbo) {

fail("Should not have thrown this " + jbo.getClass().getName() +

" exception yet due to bundled exception mode"); }

}

public void setUp() throws Exception {

this.connectionFixture.setUp(); }

public void tearDown() throws Exception {

this.connectionFixture.tearDown(); }

(28)

ユニット・テストの概要 2004 年 12 月

著書: Jan Kettenis, Remco de Blok Oracle Corporation World Headquarters 500 Oracle Parkway Redwood Shores, CA 94065 U.S.A. 海外からのお問合せ窓口: 電話: +1.650.506.7000 ファックス: +1.650.506.7200 www.oracle.com

Copyright © 2004, Oracle. All rights reserved.

この文書はあくまで参考資料であり、掲載されている情報は予告なしに変更されることがあります。 オラクル社は、本ドキュメントの無謬性を保証しません。また、本ドキュメントは、法律で明示的または暗黙的に記載 されているかどうかに関係なく、商品性または特定の目的に対する適合性に関する暗黙の保証や条件を含む一切の保証 または条件に制約されません。オラクル社は、本書の内容に関していかなる保証もいたしません。また、本書により、 契約上の直接的および間接的義務も発生しません。本書は、事前の書面による承諾を得ることなく、電子的または物理 的に、いかなる形式や方法によっても再生または伝送することはできません。

参照

関連したドキュメント

Why does riding a road bike help you lose weight more than jogging..

本時は、「どのクラスが一番、テスト前の学習を頑張ったか」という課題を解決する際、その判断の根

[r]

[r]

[r]

[r]

[r]

Medial