SoftwareEngineering/Java/UnitTest/JMockit

前書き

このチュートリアルでは、サンプル・テスト(Java 8を使用)の助けを借りて、ライブラリーで使用可能なAPIを調べます。
中央API(単一の注釈)は、テスト対象のオブジェクトの自動インスタンス化と初期化をサポートします。
次に、模倣された依存関係を使用するテスト用のmocking API(「Expectations」APIとも呼ばれます)があります。
最後に、外部コンポーネントの完全なコストを避けるための偽の実装の作成とアプリケーションに使用できる小さな偽造 API(別名「Mockups」API)があります。

このチュートリアルはかなり完成していますが、公開されたAPI全体を詳しくカバーしようとはしません。
すべての注釈、クラス、メソッドなどの完全で詳細な仕様は、APIのドキュメントで提供されています 。
" jmockit-1.x-sources.jar "ライブラリファイル(Maven Centralリポジトリからダウンロード可能)には、
Javaソースファイル(Javadocのコメント付き)が含まれており、APIのソースコードやJava IDEのドキュメントに簡単にアクセスできます。

別の章でコードカバレッジツールについて説明します。



自動化された開発者テストとテスト分離

自動化された開発者テストは、開発者自身が独自のコードをテストするために作成したテストです。
それらは通常、JUnitTestNG などのテストフレームワークの助けを借りて書かれています。
JMockitは両方をサポートします。

自動化された開発者テストは、個々のプログラム「ユニット」、複数のそのようなユニットが一緒に働くかどうか、またはテスト中のシステムの「スライス」全体(「SUT」)の2つの大きなカテゴリーに分けられます。

  1. ユニットテスト
    クラスやコンポーネントを他のシステムと分離してテストすることを目的としています。
  2. 統合テスト
    ユニットとその依存関係(テスト対象ユニットが対話する他のクラス/コンポーネント)を含むシステム操作をテストするための統合テスト 。
    このようなテストには、アプリケーションのUI(SUTがある場合)を介して実行されるエンドツーエンドのシステムテストから、関連するユニット間の小さなセットを実行するテストまでが含まれます。
    ここでは、アプリケーションUIを通じて実行されない統合テストに特に関心がありますが、そうでなければ意味のあるビジネスシナリオに関与するすべてのプログラム単位を実行します。

統合テストには複数のユニット間のやりとりが含まれていますが、特定のテストでは、関連するすべてのコンポーネント、レイヤー、またはサブシステムの実行には関心がない場合があります。
システムのそのような部分は、テスト対象のコードがそれらから分離して実行されるように、"模倣され"てしまうかもしれません。
したがって、テスト中のコードをシステムの特定の部分から分離する機能は、一般に、あらゆる種類のテストで有用です。
それは一般的にはできるだけ「現実的」なテストをするのが最善だと言いました。
だから、模造や偽造の使用は、理想的には最低限に抑えるべきです。




モックオブジェクトによるテスト

孤立してコードをテストするための一般的で強力な技術は、"モック"の使用です。
従来、 モックオブジェクトは、単一のテストまたは関連するテストのセットのために具体的に実装されたクラスのインスタンスです。
このインスタンスは、テスト中のコードに渡され、その依存関係の1つに代わるものです。
各模擬オブジェクトは、テスト中のコードとそれを使用するテストの両方によって予期されるように動作し、すべてのテストが成功するようにします。
しかし、これはモックオブジェクトが通常果たす唯一の役割ではありません。
各テストで実行されるアサーションの補完として、モック自体は追加のアサーションをエンコードできます。

JMockit は、メソッドやコンストラクタを "実際の"(モックではない)クラスに直接偽装し、テストでモックオブジェクトをインスタンス化してテスト中のコードに渡す必要がなくなり、従来のモックオブジェクトを超えています。 代わりに、テスト対象のコードによって作成されたオブジェクトは、実際のクラスでメソッドまたはコンストラクタが呼び出されるたびに、テストで定義されたモックの動作を実行します。 クラスが嘲笑されたとき、既存のメソッド/コンストラクタの元の実装は、通常、単一のテストの期間、模擬実装に一時的に置き換えられます。 このモーキングのアプローチは、 publicインスタンス・メソッドだけでなく、 finalメソッドとstaticメソッド、およびコンストラクタにも適用されます。

モックは独立した単体テストで最も有用ですが、統合テストでも使用できます。 たとえば、ある時点で電子メールを送信するビジネス操作をテストすることができます。 電子メールが正しく送信されたことを確認するために、このテストでは、電子メール送信APIを模擬し、他のすべてのコンポーネントで実際のコードを実行することもできます(代わりに、偽の電子メールサーバーを設定することもできますテストの最後にアサーションで相談してください)。



次の手順でビジネスオペレーションを提供するビジネスサービスクラスを検討してください。

  1. 操作に必要な永続的なエンティティを見つける
  2. 新しいエンティティの状態を維持する
  3. 関係者に通知電子メールを送信する

最初の2つの手順では、アプリケーションデータベースへのアクセスが必要です。
これは、単純化されたAPIを介して永続性サブシステム(JPAを使用する)に行われます。
3つ目は、電子メールを送信するためのサードパーティのAPIで実現できます。
この例では、Apache の Commons Email ライブラリです。

したがって、サービスクラスには、永続性と電子メールの2つの依存関係があります。
ビジネス操作をテストするために、私たちはJMockitのJPAサポートを利用して永続性を処理し、電子メールAPIを嘲笑します。
作業中のソリューション(すべてのテストを含む)の完全なソースコードは、オンラインで入手できます。

package tutorial.domain;

import java.math.*;
import java.util.*;
import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;

public final class MyBusinessService
{
   private final EntityX data;

   public MyBusinessService(EntityX data) { this.data = data; }

   public void doBusinessOperationXyz() throws EmailException {
      List<EntityX> items =
(1)      find("select item from EntityX item where item.someProperty = ?1", data.getSomeProperty());

      // Compute or obtain from another service a total value for the new persistent entity:
      BigDecimal total = ...
      data.setTotal(total);

(2)   persist(data);

      sendNotificationEmail(items);
   }

   private void sendNotificationEmail(List<EntityX> items) throws EmailException {
      Email email = new SimpleEmail();
      email.setSubject("Notification about processing of ...");
(3)   email.addTo(data.getCustomerEmail());

      // Other e-mail parameters, such as the host name of the mail server, have defaults defined
      // through external configuration.

      String message = buildNotificationMessage(items);
      email.setMsg(message);

(4)   email.send();
   }

   private String buildNotificationMessage(List<EntityX> items) { ... }
}

Database クラスには静的メソッドとプライベートコンストラクタのみが含まれています。
find メソッドと persist メソッドは明白なはずなので、ここではそれらをリストしません。

では、既存のアプリケーションコードを変更せずに"doBusinessOperationXyz"メソッドをどのようにテストできますか?
次のJUnitテスト・クラスでは、永続性操作の正確な実行と、電子メールAPIへの予想される呼び出しを検証します。
これらの検証ポイントは、上記の(1) 〜 (4)番号が付けられています。

package tutorial.domain;

import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;

import org.junit.*;
import org.junit.rules.*;
import static org.junit.Assert.*;
import mockit.*;

public final class MyBusinessServiceTest
{
   @Rule public final ExpectedException thrown = ExpectedException.none();

   @Tested final EntityX data = new EntityX(1, "abc", "someone@somewhere.com");
   @Tested(fullyInitialized = true) MyBusinessService businessService;
   @Mocked SimpleEmail anyEmail;

   @Test
   public void doBusinessOperationXyz() throws Exception {
      EntityX existingItem = new EntityX(1, "AX5", "abc@xpta.net");
(1)   persist(existingItem);

      businessService.doBusinessOperationXyz();

(2)   assertNotEquals(0, data.getId()); // implies "data" was persisted
(4)   new Verifications() {{ anyEmail.send(); times = 1; }};
   }

   @Test
   public void doBusinessOperationXyzWithInvalidEmailAddress() throws Exception {
      String email = "invalid address";
      data.setCustomerEmail(email);
(3)   new Expectations() {{ anyEmail.addTo(email); result = new EmailException(); }};
      thrown.expect(EmailException.class);

      businessService.doBusinessOperationXyz();
   }
} 

サンプルのテストでは、JMockit APIの2つのアノテーションを使用しています。 @Testedは、テスト対象のオブジェクトを適切に初期設定しますが、 @Mockedは与えられた型に@Mocked適用します。

テストにも示されているように、 new Expectations() {{ ... }}ブロック内での記録とnew Verifications() {{ ... }} block内でのnew Verifications() {{ ... }} block )は、録音ブロックまたは検証ブロックの中から希望のメソッド(ここでは示されていなくてもコンストラクタと同様)を呼び出します。
テスト中のコードによって実行される一致する呼び出しから戻す値(またはスローする例外)は、記録中に「 result 」フィールドで指定されます。
呼び出し回数の制約は、記録時または検証時に、「 times = 1 」などのAPIフィールドの割り当てによって指定できます。



JMockitによるテストの実行

JMockit APIを使用するテストを実行するには、通常どおり Java IDE、Maven / Gradle / Ant ビルドスクリプトなどを使用します。
原則として、Windows、Mac OS X、Linux上のバージョン1.6以降のJDKを使用することができます。
JMockitは、JUnit(バージョン4と5)または TestNG の使用をサポートしています。
これらの各テストフレームワークに固有の詳細は次のとおりです。

  • JUnit 4.5+テストスイートでは、クラスパス内のJUnit依存関係/ jarの前に jmockit依存関係(つまり、 jmockit-1.x.jar )が表示されていることを確認してjmockit。
    あるいは、 @RunWith ( JMockit .class)テストクラスに注釈を付けます。
    Eclipseユーザーの場合:クラスパスでjarの順序を指定する場合は、「Java Build Path」ウィンドウの「Order and Export」タブを使用してください。
    また、Eclipse プロジェクトが JDK インストールの JRE を使用していることを確認してください後者には " attach "ネイティブライブラリがないため、 "プレーンな" JREの代わりに使用できます)。
  • TestNG 6.2+およびJUnit 5+テストスイートの場合、テストクラスパス(任意の位置)にjmockit依存関係/ jarを追加するだけです。

他の状況(Oracle JDK以外のJDK実装で実行する場合など)では、JVM初期化パラメータとして " -javaagent: <proper path> /jmockit-1.x.jar "を渡す必要があります。 これは、EclipseとIntelliJ IDEA、またはMaven、Gradle、Antなどのビルドツールを使用して、「Run / Debug Configuration」で実行できます。



Mavenからのテストの実行

JMockit成果物は中央のMavenリポジトリにあります。 これらをテストスイートで使用するには、 pom.xmlファイルに次の行を追加します。

<dependencies>
   <dependency>
      <groupId>org.jmockit</groupId>
      <artifactId>jmockit</artifactId>
      <version>1.x</version>
      <scope>test</scope>
   </dependency>
</dependencies> 

指定したバージョンが実際に必要なバージョンであることを確認してください。 開発履歴ページで現在のバージョンを探します。 JUnit 4を使用する場合、この依存関係は " junit "依存関係の前に来る必要があります。

Maven で JMockit Coverage を使用する方法については、その章の該当するセクションを参照してください。

Gradleからのテストの実行

GradleはmavenCentral()リポジトリから必要な成果物をダウンロードします。 gradle.buildファイルでは、 jmockit依存関係(JUnit 4.xを使用している場合はjunit依存関係の前にあることが望ましい)を追加し、 " x "を目的のバージョン番号に置き換えます。

dependencies {
   ... "compile" dependencies ...
   testCompile 'org.jmockit:jmockit:1.x'
} 




JUnit Antタスクによるテストの実行

build.xmlスクリプトで<junit>要素を使用する場合は、別のJVMインスタンスを使用することが重要です。 たとえば、次のようなものがあります。

<junit fork="yes" forkmode="once" dir="directoryContainingJars">
   <classpath path="jmockit-1.x.jar"/>

   <!-- Additional classpath entries, including the appropriate junit.jar -->

   <batchtest>
      <!-- filesets specifying the desired test classes -->
   </batchtest>
</junit>

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-03-24 (土) 20:09:33 (628d)