Java EE、Adam Beanの統合テスト

実用的な統合テストにより、生産性が向上し、JavaEEアプリケーションの展開が保証されます。



Adam Bean(ドイツ)は、コンサルタント、トレーナー、アーキテクト、Java EE 6および7エキスパートグループ、EJB 3.X、JAX-RS、およびJPA 2.X JSRのメンバーです。 Javaチャンピオン、トップJavaアンバサダー2012およびJavaOne Rock Star 2009、2011、2012、2013 彼は、Real World Java EEパターン-ベストプラクティスの再考とReal World Java EEナイトハック-ビジネス層の分析の著者です。



Adamの記事「 Java EEの統合テスト 」の翻訳に精通することをお勧めします



はじめに



前の記事「 Java EEの単体テストでは、 Javaアプリケーションと、Mokitoライブラリを使用してすべての外部依存関係をシミュレートするJava EE 6を使用して記述されたアプリケーションの単体テストのアプローチを検討しました。 単体テストはビジネスロジックの検証に重要ですが、Java EE 6アプリケーションの展開を保証するものではありません。

注:Java.Netリソースには、この記事のMaven 3プロジェクト(TestingEJBAndCDI)があり、NetBeans 7およびGlassFish v3.xを使用してテストされています。



注:Java.Netリソースには、この記事のMaven 3プロジェクト( TestingEJBAndCDI )があり、NetBeans 7およびGlassFish v3.xを使用してテストされています。



テストでさまざまなアプローチを使用する



ユニットテストは迅速かつきめ細かいです。 統合テストは遅く、「粗粒度」です。 パフォーマンスを向上させるために、ユニットテストと統合テストをそれぞれ高速テストと低速テストに任意に分割する代わりに、それらの詳細を考慮します。 粒度の細かい単体テストは迅速に実行する必要があります。 通常、テストは、大規模なサブシステムに統合される前に、機能の小さな断片に対して作成されます。 単体テストは非常に高速です-数百のテストをミリ秒単位で実行できます。 単体テストを使用すると、統合テストの完了を待たずに、高速の反復を実行できます。

統合テストは、単体テストに合格した後に実行されます。 単体テストは失敗することが多いため、統合テストの実行頻度は低くなります。 ユニットテストと統合テストに厳密に分割されているため、各テストサイクルで数分(または数時間)節約できます。



性能試験



実用的な統合テストにより、生産性が向上します。 もう1つは、Java Persistence API(JPA)のマッピングとクエリをテストすることです。 マッピングとクエリの正しい構文を確認するためだけにアプリケーション全体をサーバーにアップロードすると、時間がかかりすぎます。

JPAは単体テストから直接使用できます。 その後、再起動のコストは無視できます。 これを行うには、EntityManagerFactoryからEntityManagerのインスタンスを取得するだけです。 たとえば、Predictionクラスのマッピングをテストするために、EntityManagerはPredictionAuditクラスに埋め込まれています(例1を参照)。



public class PredictionAuditIT { private PredictionAudit cut; private EntityTransaction transaction; @Before public void initializeDependencies(){ cut = new PredictionAudit(); cut.em = Persistence.createEntityManagerFactory("integration"). createEntityManager(); this.transaction = cut.em.getTransaction(); } @Test public void savingSuccessfulPrediction(){ final Result expectedResult = Result.BRIGHT; Prediction expected = new Prediction(expectedResult, true); transaction.begin(); this.cut.onSuccessfulPrediction(expectedResult); transaction.commit(); List<Prediction> allPredictions = this.cut.allPredictions(); assertNotNull(allPredictions); assertThat(allPredictions.size(),is(1)); } @Test public void savingRolledBackPrediction(){ final Result expectedResult = Result.BRIGHT; Prediction expected = new Prediction(expectedResult, false); this.cut.onFailedPrediction(expectedResult); } }
      
      





例1. EntityManagerをPredictionAuditクラスに埋め込む



EntityManagerは例1のコンテナーの外側で動作するため、トランザクションはユニットテストによってのみ制御できます。 この場合、宣言トランザクションは使用できません。 これにより、テストメソッド内でトランザクション境界を明示的に設定できるため、テストがさらに簡単になります。 EntityTransaction#commit()メソッドを呼び出すことで、EntityManagerキャッシュを簡単にクリアできます。 キャッシュをクリアするとすぐに、データベースでデータが利用可能になり、テスト中に検証できます(例1のSavingSuccessfulPrediction()を参照)。



スタンドアロンJPA構成





EntityManagerはJPA仕様の一部であり、glassfish-embedded-all-dependencyにも含まれています。 同じ依存関係がEclipseLinkで実行されます。 データを保存するには外部データベースのみが必要です。 Derbyデータベースはインストールを必要とせず、サーバーモードまたは組み込みデータベースモードで起動でき、ランダムアクセスメモリ(インメモリ)またはディスクにデータを保存できます。



 dependency> <groupId>org.apache.derby</groupId> <artifactId>derbyclient</artifactId> <version>10.7.1.1</version> <scope>test</scope> </dependency>
      
      





例2. Derbyデータベースの「インストール」



Derbyは標準のMavenリポジトリーでサポートされており、1つの依存関係でプロジェクトに追加できます(例2を参照)。 この場合、JDBCドライバーはテスト中にのみ必要であり、サーバーに展開またはインストールすべきではないため、テストスコープに対して依存関係が定義されます。

コンテナなしで単体テストを実行する場合、 Java Transaction (JTA)およびjavax.sql.DataSource機能を使用することはできません。



 <persistence version=“1.0” xmlns="#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="# "> <persistence-unit name="integration" transaction-type="RESOURCE_LOCAL"> <class>com.abien.testing.oracle.entity.Prediction</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:testDB;create=true"/> <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/> <property name="eclipselink.ddl-generation" value="create-tables"/> </properties> </persistence-unit> </persistence>
      
      





例3.単体テスト用に構成されたpersistence.xmlファイル



追加のpersistence.xmlファイルがsrc / test / java / META-INFパッケージに作成され、 テスト専用に使用されます。 展開プロセスがないため、すべてのエンティティを明示的に記述する必要があります。 また、トランザクションタイプはRESOURCE_LOCALに設定されているため、トランザクションを手動で処理できます。 EntityManagerは、データソースを宣言する代わりに、構成済みのJDBCドライバーを介してデータベースに直接アクセスします。 組み込みのDerbyデータベースは、単体テストに最適です。 EmbeddedDriverは、URLを構成するための2つのオプションをサポートしています。データをファイルまたはメモリに保存する方法です。 JPAマッピングとクエリをテストするには、メモリ内のストレージ(メモリ内、例3を参照)を使用した接続文字列を使用します。 すべてのテーブルは、次のテストの開始前にRAMにその場で作成され、テスト後に削除されます。 テスト後にデータを削除する必要がないため、これはテストJPAを喫煙する最も便利な方法です。



より複雑なJPAテストには特定のテストデータセットが必要であり、そのような場合にメモリ内構成を使用するのは不便です。 RAMの代わりに、Derbyデータベースはファイルを使用してデータを保存およびロードできます。 これを行うには、接続文字列を変更するだけです。



  <property name="javax.persistence.jdbc.url" value="jdbc:derby:./sample;create=true”/>
      
      







特に、定義済みのデータセットを必要とするテストは、ファイルに状態を記録するデータベース構成を使用して簡単に実装できます。 移入されたデータベースは、テストが完了する前にプロジェクトフォルダにコピーする必要があり、テスト後に削除する必要があります。 したがって、データベースは起動するたびに削除されます。 クリーニングや変更を心配する必要はありません。



単体テストは統合テストではありません。





クラス名PredictionAuditITに奇妙なITサフィックスがすでにあることに気付いているでしょう。 接尾辞は、単体テストと統合テストを区別するために使用されます。 標準のMavenフレームワークフェイルセーフプラグインは、ITまたはITCaseで終わる、またはITで始まるすべてのテストを実行しますが、これらのクラスはMaven SurefireプラグインおよびJUnitテストでは無視されます。 ユニットテストと統合テストを分離するには、次の依存関係を追加するだけです。

  <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.7.1</version> </plugin>
      
      





例4. FailSafeプラグインの構成



単体テストは、標準のmvn cleanインストールの実行中に実行され、mvn surefire:testコマンドを使用して明示的に実行することもできます。 実行タグを使用して、統合テストをMavenフェーズに追加することもできます。

 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.7.1</version> <executions> <execution> <id>integration-test</id> <phase>integration-test</phase> <goals> <goal>integration-test</goal> </goals> </execution> <execution> <id>verify</id> <phase>verify</phase> <goals> <goal>verify</goal> </goals> </execution> </executions> </plugin>
      
      





例5.フェイルセーフプラグインの登録



実行タグを使用すると、 フェイルセーフプラグインがmvn installmvn verify、またはmvn integration-testコマンドで自動的に実行されます。 単体テストが最初に実行され、次に統合テストが実行されます。

統合テストと単体テストに厳密に分割すると、テストサイクルが大幅に短縮されます。 したがって、単体テストは、シミュレートされた環境でメソッドの機能をテストし、統合テストよりも数桁高速です。 単体テストを実行すると、すぐに結果が得られます。

すべての単体テストが正常に完了した場合にのみ、最初は統合テストが遅くなります。 単体テストと統合テスト用に継続的統合を個別に構成することもできます。 mvn clean installコマンドは、 mvn integration-testsコマンドを起動します。 プロセスを分離すると、柔軟性が高まります。 タスクを個別に再起動し、各タスクのプロセスに関する通知を受け取ることができます。



組み込み統合テストのキリングユースケース



ほとんどの統合テストは、コンテナを起動せずに実行できます。 JPA機能は、ローカルで作成されたEntityManagerを使用してテストできます。 ビジネスロジックは、コンテナの外部で便利かつ効果的にテストすることもできます。 すべての依存クラス、サービス、その他すべてのモックオブジェクトを作成する必要があります。

簡単にサポートできるように、2つのエラーメッセージを出し、それらを1か所に保管する必要があると仮定しましょう。 Stringクラスのインスタンスを実装し、構成に使用できます(例6を参照)。



 @Inject String javaIsDeadError; @Inject String noConsultantError; //… if(JAVA_IS_DEAD.equals(prediction)){ throw new IllegalStateException(this.javaIsDeadError); } //… if(company.isUnsatisfied()){ throw new IllegalStateException(this.noConsultantError);
      
      





例6.埋め込み文字列を使用して例外をスローする



MessageProviderクラスは構成をサポートし、プロパティ値を使用して文字列を返します(例7を参照)。



 @Singleton public class MessageProvider { public static final String NO_CONSULTANT_ERROR = "No consultant to ask!"; public static final String JAVA_IS_DEAD_MESSAGE = "Please perform a sanity / reality check"; private Map<String,String> defaults; @PostConstruct public void populateDefaults(){ this.defaults = new HashMap<String, String>(){{ put("javaIsDeadError", JAVA_IS_DEAD_MESSAGE); put("noConsultantError", NO_CONSULTANT_ERROR); }}; } @Produces public String getString(InjectionPoint ip){ String key = ip.getMember().getName(); return defaults.get(key); } }
      
      





例7.汎用コンフィギュレーター



MessageProviderクラス(例7)を見ると、テストするものが残っていないことがわかります。 Map defaultsフィールドのInjectionPointパラメーターのモックを作成して検索をテストできる場合を除きます。 依存性注入後のテストされていない唯一の部分は、依存性注入自体とgetString()メソッドでの使用です。 フィールド名、検索、および実装自体の取得は、コンテナ内でのみ完全にテストできます。 実装の目的は、埋め込まれた値を使用して例外をスローするOracleResourceクラスです。 (記事「 Java EEの単体テスト 」を参照)。このメカニズムをテストするには、埋め込み値を挿入するか、例外からメッセージを抽出する必要があります。 このアプローチでは、未構成フィールドなどの「シャープポイント」をテストすることはできません。 テスト専用に作成されたヘルパークラスは、柔軟性を高め、テストを大幅に簡素化します。



 public class Configurable { @Inject private String shouldNotExist; @Inject private String javaIsDeadError; public String getShouldNotExist() { return shouldNotExist; } public String getJavaIsDeadError() { return javaIsDeadError; } }
      
      





例8.統合テストのヘルパークラス



Configurableクラス(例8を参照)はsrc / test / javaフォルダーにあり、 MessageProviderクラスの統合テストを簡素化するために特別に設計されました。 javaIsDeadErrorフィールドが実装され、 shouldNotExistフィールドを設定する必要なく、テスト中はnullであると想定されています



エイリアンはあなたを助けることができます





Arquillianは不思議な起源の興味深い生き物ですが(映画「Men in Black」を参照)、同時にJUnitフレームワークのTestRunnerクラスに統合されたオープンソースフレームワークでもあります。 これにより、どのクラスをデプロイして実装するかを完全に制御できます。 ArquillianはJUnitテストを実行し、 src / test / javaフォルダーのコンテンツにフルアクセスできます。 Configurableなどのテスト用の特別なクラス(例8を参照)を使用してテストを簡素化できますが、テストはメインアプリケーションコードsrc / main / javaのフォルダーに分類されず、パッケージに含める必要はありません。



Arquillianは、制御部とコンテナの2つの部分で構成されています。 arquillian-junitの制御部分はテストを実行し、テストケース内に依存性注入メカニズムを実装します。



 <dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-junit</artifactId> <version>1.0.0.Alpha5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-glassfish-embedded-3.1</artifactId> <version>1.0.0.Alpha5</version> <scope>test</scope> </dependency>
      
      





例9. Maven 3のArquillian構成



arquillian-glassfish-embedded-3.1依存関係は、アプリケーションサーバーと統合されます。 バージョン3.1と3.0の両方を使用できます。



glassfishの代わりに、arquillian-jbossas-embedded-6アーティファクトを使用する組み込みJBossサーバー、またはTomcatおよびWeldサーバーを使用できます。



 import org.jboss.shrinkwrap.api.*; import javax.inject.Inject; import org.jboss.arquillian.junit.Arquillian; import org.junit.*; import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; @RunWith(Arquillian.class) public class MessageProviderIT { @Inject MessageProvider messageProvider; @Inject Configurable configurable; @Deployment public static JavaArchive createArchiveAndDeploy() { return ShrinkWrap.create(JavaArchive.class, "configuration.jar"). addClasses(MessageProvider.class, Configurable.class). addAsManifestResource( new ByteArrayAsset("<beans/>".getBytes()), ArchivePaths.create("beans.xml")); } @Test public void injectionWithExistingConfiguration() { String expected = MessageProvider.JAVA_IS_DEAD_MESSAGE; String actual = configurable.getJavaIsDeadError(); assertNotNull(actual); assertThat(actual,is(expected)); } @Test public void injectionWithMissingConfiguration(){ String shouldNotExist = configurable.getShouldNotExist(); assertNull(shouldNotExist); }
      
      





例10. Arquillianを使用した統合テスト



依存関係を構成したら、Arquillianをテスト管理システムとして使用できます。 ユニットテストは、Arquillianで透過的に実行されます。 Maven、Ant、またはIDEでさえ、ネイティブのテスト実行システムではなくArquillianを使用します。 このリダイレクトは透過的ですが、ArquillianはデプロイされたContexts and Dependency Injection(CDI)またはEnterprise JavaBeans(EJB)コンポーネントまたはその他のJava EEリソースをテストに直接デプロイできます。 Arquillianは、アプリケーションサーバーの実装の単なる薄層です。 GlassFish、JBoss、またはTomcatをひそかにダウンロードし、「ネイティブ」組み込みアプリケーションサーバーでテストを実行します。 たとえば、埋め込みGlassFishを使用できます。



Arquillianの使用を開始するには、JARアーカイブをデプロイする必要があります。 createArchiveAndDeployメソッド(例10を参照)では、JARが作成され、 MessageProviderクラスとConfigurableクラス、および空のbeans.xmlファイルでデプロイされます。 デプロイされたクラスは、単体テスト自体に直接埋め込むことができ、テストが大幅に簡素化されます。 InjectionWithExistingConfigurationメソッドはConfigurableクラスにアクセスし、 javaIsDeadErrorおよびshouldNotExistの値を返します(例8を参照)。 アプリケーションサーバー内で実行されているかのようにコードをテストできます。 テストは実際にコンテナを起動するため、一方ではこれは単なる幻想です。他方では違います。



不可能をテストする





インスタンスコンサルタント>会社の依存関係が満たされない場合に何が起こるかを予測することは困難です。 この時点では十分なコンサルタントクラスの実装が常に展開されているため、これは実際の使用では発生しません。 この状況では、統合環境でのテストが不可能になります。 しかし、Arquillianは、デプロイメントモジュールを管理することで、満たされていない依存関係を簡単にテストできます。



 @RunWith(Arquillian.class) public class OracleResourceIT { @Inject OracleResource cut; @Deployment public static JavaArchive createArchiveAndDeploy() { return ShrinkWrap.create(JavaArchive.class, "oracle.jar"). addClasses(OracleResource.class,MessageProvider.class, Consultant.class). addAsManifestResource( new ByteArrayAsset("<beans/>".getBytes()), ArchivePaths.create("beans.xml")); } @Test(expected=IllegalStateException.class) public void predictFutureWithoutConsultants() throws Exception{ try { cut.predictFutureOfJava(); } catch (EJBException e) { throw e.getCausedByException(); } } }
      
      





例11.不可能をテストする



例11のcreateArchiveAndDeployメソッドを使用して、最も必要なクラスOracleResourceMessageProviderおよびConsultantのみがoracle.jarにアーカイブされます。OracleResourceクラスもEventを使用してResultを送信しますが(例12を参照)、 PredictionAuditイベントを無視してデプロイしません。



 @Path("javafuture") @Stateless public class OracleResource { @Inject Instance<Consultant> company; @Inject Event<Result> eventListener; @Inject private String javaIsDeadError; @Inject private String noConsultantError; // -  }
      
      





例12. OracleResourceクラスの必要な依存関係。



イベントが飲み込まれることを想定しています。 predictFutureWithoutConsultantsメソッドはOracleResource# predictFutureOfJavaメソッドを呼び出し、 checkConsultantAvailabilityで条件をチェックした後にIllegalStateExceptionをスローすることを予期しています (例13を参照)。



 public String predictFutureOfJava(){ checkConsultantAvailability(); Consultant consultant = getConsultant(); Result prediction = consultant.predictFutureOfJava(); eventListener.fire(prediction); if(JAVA_IS_DEAD.equals(prediction)){ throw new IllegalStateException(this.javaIsDeadError); } return prediction.name(); } void checkConsultantAvailability(){ if(company.isUnsatisfied()){ throw new IllegalStateException(this.noConsultantError); } }
      
      





例13. OracleResourceクラスでの前提条件チェック



予期されるIllegalStateExceptionの代わりにjavax.ejb.EJBExceptionがスローされることに注意してください(例14を参照)。



警告:EJB側の呼び出し中にシステム例外が発生しました。



 class OracleResource public java.lang.String com.abien.testing.oracle.boundary.OracleResource.predictFutureOfJava() javax.ejb.EJBException //    stacktraceat $Proxy126.predictFutureOfJava(Unknown Source) at com.abien.testing.oracle.boundary.__EJB31_Generated__OracleResource__ __Intf____Bean__.predictFutureOfJava(Unknown Source)
      
      





例14. EJBプロキシを使用してStack Traceクラスを実装します。



単体テストは、 パブリックインターフェイスを介して実際のEJB 3.1コンポーネントにアクセスします。 IllegalStateExceptionは、現在のトランザクションをロールバックする未チェックの例外であり、そのアクションはjavax.ejb.EJBException例外でラップされますEJBExceptionからIllegalStateExceptionを取得して、再度スローする必要があります。



ロールバックテスト





トランザクションロールバックの動作は、ヘルパークラスTransactionRollbackValidatorを導入することで簡単にテストできます。 これは、 SessionContextにアクセスできる通常のEJB 3.1コンポーネントです



 @Stateless public class TransactionRollbackValidator { @Resource SessionContext sc; @EJB OracleResource os; public boolean isRollback(){ try { os.predictFutureOfJava(); } catch (Exception e) { //swallow all exceptions intentionally //    } return sc.getRollbackOnly(); } }
      
      





例15.テスト用のEJB 3.1コンポーネント



TransactionRollbackValidatorテストクラスは、 OracleResourceクラスを呼び出し、すべての例外を処理し、トランザクションの現在のロールバックステータスを返します。 OracleResourceITクラスのデプロイメントおよびテストのセクションをわずかに拡張するだけで、トランザクションが正常にロールバックされたことを確認できます(例16を参照)。



 @RunWith(Arquillian.class) public class OracleResourceIT { //    @Inject TransactionRollbackValidator validator; @Deployment public static JavaArchive createArchiveAndDeploy() { return ShrinkWrap.create(JavaArchive.class, "oracle.jar"). addClasses(TransactionRollbackValidator.class, OracleResource.class,MessageProvider.class, Consultant.class). addAsManifestResource( new ByteArrayAsset("<beans/>".getBytes()), ArchivePaths.create("beans.xml")); } @Test public void rollbackWithoutConsultants(){ assertTrue(validator.isRollback()); } // ,    ,  }
      
      





例16.トランザクションのロールバックをテストする



TransactionRollbackValidatorはEJB 3.1コンポーネントであるため、デフォルトではRequired transaction属性を使用してデプロイされます。 EJB仕様によると、 TransactionRollbackValidator#isRollbackメソッドは常にトランザクション内で実行されます。 新しいトランザクションが起動されるか、既存のトランザクションが再利用されます。 このテストケースでは、 TransactionRollbackValidatorOracleResourceクラスの新しいトランザクションを呼び出します。 新しく起動されたトランザクションはOracleResourceクラスに拡張され、コンサルタントの不足によりEJBExceptionがスローされます。 TransactionRollbackValidatorクラスは、単にSessionContext#getRollbackOnlyメソッドを呼び出した結果を返します。



このようなテストは、ソースコードを変更するか、 src / main / javaフォルダーに追加のヘルパークラスを導入しない限り不可能でした。 Arquillianなどのフレームワークは、テストコードでソースコードを乱雑にすることなく、インフラストラクチャを簡単にテストするユニークな機会を提供します。



...そして継続的な展開?





コンテナの外部および内部でコードをテストすると、明確に定義された作業条件下でのみアプリケーションの正しい動作が保証されます。 小規模なサーバー構成の変更や忘れられたリソースでも、展開が中断されます。 最新のアプリケーションサーバーはすべて、スクリプトまたは切り捨てられたAPIを使用して構成されます。 ソースコードの構成からサーバーを起動すると、発生する可能性のあるエラーが最小限に抑えられるだけでなく、手動による介入が不要になります。 リポジトリ内のコードを変更するたびに、アプリケーションを産業用サーバーにデプロイすることもできます。 包括的な統合と単体テストは、「継続的な展開」の最初の前提条件です。



悪用はパフォーマンスに悪影響を与える





埋め込みコンテナ内のすべての機能をテストするのは便利ですが、非生産的です。 Java EE 6コンポーネントには注釈付きのPlain Old Java Object(POJO)があり、JUnit、TestNG、MockitoなどのJava SEツールを使用して簡単にテストおよびモックできます。 ビルトインコンテナを使用してビジネスロジックをテストすることは、非生産的であるだけでなく、概念的にも間違っています。 単体テストでは、コンテナの動作ではなく、ビジネスロジックのみをチェックする必要があります。 さらに、ほとんどの統合テストは、ローカルEntityManager(例1を参照)を使用するか、コンテナーインフラストラクチャをシミュレートすることで簡単に実行できます。 統合テストのごく一部のみが、組み込みコンテナまたはArquillianなどのテストフレームワークを使用する必要があります。 – , . -, , .



17 19 JavaOne Rock Star «, , Java EE 7»



All Articles