Apache Camelでの統合テスト用のサービススタブの作成(Scala DSLを使用)

画像


これは、テストでのScalaの使用に関する3番目の記事です。 今日は、Apache Camelを使用してテストスタブと情報システムコンポーネントを作成する例について説明します。


多くの場合、統合テストのためにシステムの任意の部分の動作をエミュレートする、スタブを作成する、または単純な統合コンポーネントを作成する必要があります。 必要な回答を返すWebサービス、データベースに入力するテスト、キューからメッセージを読み取り、処理結果を返すアプリケーション、ファイルジェネレーター、およびその他のコンポーネントを使用できます。


統合の1回限りの検証には、単純なJavaまたはScalaアプリケーション、Apache JMeterスクリプト、またはSoapUIを使用します。 しかし、常に機能し、リクエストに応答し、テスターからのアクションを必要としないシステムが必要です-私は始めて忘れていました。 この問題を解決するために、Apache Camelフレームワークに基づいてアプリケーションを作成できます。
5つの例を考えてみましょう。


  1. あるエンコーディングでファイルを読み取り、別のエンコーディングで書き込み。
  2. データウェアハウスでのスケジュールされたWebサービス要求とメッセージの保存。
  3. GET要求パラメーターに応じてメッセージを返すWebサービスの実装。
  4. キューからメッセージを読み取り、データベースにメッセージを送信します。
  5. ファイルの内容によるルーティングの例。

問題を解決するために使用されるツールを簡単に説明してください。 Apacheキャメル( http://camel.apache.org/)-情報システムのサブシステムである個々のアプリケーション間のメッセージ交換を実装するように設計されたJavaフレームワーク。 エンタープライズ統合パターン(EIP)ミドルウェア開発アプローチを実装します。 ファイル、データベース、キューマネージャー、Webサービス、およびその他のコンポーネントを操作できます- コンポーネントプロジェクトページには240以上があります。 Camelアプリケーションは、いわゆるエンドポイント、エンドポイント、およびそれらの間でメッセージを変換およびルーティングするためのルールを記述します。


Camelコンポーネントはエンドポイントを実装します。 これは、メッセージのプロデューサー(Producer)またはコンシューマ(Consumer)です。 一部のコンポーネントは、両方のタイプのポイントを実装できます。たとえば、ファイルからメッセージを受信して​​記録できます。 一部のコンポーネントは、タイマーなどのメッセージプロデューサーのみ、またはロギングなどのコンシューマーのみを実装します。


アプリケーション中に、メッセージ本文とそのヘッダーが操作されます。 Camelを使用する手順は次のとおりです。


  1. メッセージのソース(ファイル、キュー、データベース、サービス、タイマーなど)を説明します。
  2. データと形式を変換するための規則を説明します。
  3. メッセージの受信者(ファイル、キュー、データベース、サービス、コンソールへの出力など)およびルーティングロジックを記述します。
  4. ソースをリッスンするアプリケーションを起動し、メッセージが表示されたらメッセージを変換して受信者にルーティングします。

ルーティングおよびメッセージ変換ルールを記述するために、さまざまな言語が使用されます。 私たち自身は、Scala DSL scala-dsl-eipを選択しました。この言語は、コンポーネントソフトウェアの簡単かつ迅速な作成に適しているからです。 Scalaでは、 SBTプロジェクトビルドシステムを使用します。


ファイルからメッセージを読み取り、http postリクエストを送信する素晴らしい例があります。 少し古いですが、役に立つかもしれません。


» Http://www.lightbend.com/activator/template/camel-http
» Https://github.com/hilton/activator-camel-http#master


準備作業
SBTに基づいてアイデアのあるプロジェクトを作成します。 プロジェクト作成の例を見ることができます-Scalatestを使用した情報システムの監視と統合テストの実装。 パート1
build.sbtファイルに、設定を書き込みます


name := "camel-scaladsl" version := "1.0" scalaVersion := "2.11.8" val camelVersion = "2.17.1" libraryDependencies ++= Seq( //   Camel "org.apache.camel" % "camel-core" % camelVersion, "org.apache.camel" % "camel-scala" % camelVersion, //    Camel   "org.apache.camel" % "camel-quartz" % camelVersion, "org.apache.camel" % "camel-spring-redis" % camelVersion, "org.apache.camel" % "camel-http" % camelVersion, "org.apache.camel" % "camel-jetty" % camelVersion, "org.apache.camel" % "camel-jms" % camelVersion, "org.apache.camel" % "camel-jdbc" % camelVersion, //    "ch.qos.logback" % "logback-classic" % "1.1.2", "org.slf4j" % "slf4j-api" % "1.7.7", //    xml   "org.scala-lang.modules" % "scala-xml_2.11" % "1.0.5", //   H2 "com.h2database" % "h2" % "1.4.192", "org.apache.commons" % "commons-dbcp2" % "2.1.1", //    activemq "org.apache.activemq" % "activemq-client" % "5.13.3" ) 

src / main / resourcesファイルにlogback.xmlファイルを追加します。logback.xmlファイルには、ログレベルとメッセージ形式が設定されています。


 <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT" /> </root> </configuration> 

それ以外の場合、デフォルトのレベルはDEBUGです-表示される情報が多すぎます。


例1
あるエンコーディングでファイルを読み取り、別のエンコーディングで書き込みます。 これは、camel-coreパッケージのコンポーネントhttp://camel.apache.org/file2.htmlを使用する単純なアプリケーションです。 FromFileToFileAppアプリケーションを起動するオブジェクトと、ルートを記述するFromFileToFileRouteクラスで構成されます。 ルートを持つクラスは、別のファイルに移動できます。


src / main / scala / FromFileToFileApp.scalaファイルの内容


 import org.apache.camel.CamelContext import org.apache.camel.main.Main import org.apache.camel.scala.dsl.builder.{ScalaRouteBuilder, RouteBuilderSupport} object FromFileToFileApp extends App with RouteBuilderSupport { // Camel Main      val mainApp = new Main val context = mainApp.getOrCreateCamelContext //     mainApp.addRouteBuilder(new FromFileToFileRoute(context)) //  mainApp.run } class FromFileToFileRoute(context: CamelContext) extends ScalaRouteBuilder(context) { //         "inbox" """file:inbox?charset=utf-8""" ==> { //       "outbox" to ("file:outbox?charset=Windows-1251") } } 

FromFileToFileRouteクラスでは、メッセージの内容に変換は行われず、ルーティングは行われません。 アプリケーションを起動すると、フォルダー「inbox」、「outbox」がプロジェクトフォルダーに自動的に作成されます。 「inbox」ディレクトリに到達すると、ファイルは自動的に読み取られます-フォルダから消えます。 次に、「送信トレイ」ディレクトリに別のエンコーディングで表示されます。 この場合、amelによって読み取られたメッセージは、別のサブフォルダーの「受信ボックス」フォルダーに保存されます。


例2
データウェアハウスでのスケジュールされたWebサービスリクエストとメッセージストレージ。 この例では、タイマーによって為替レートのデータを収集し、Redisに送信します。 メッセージに対してアクションを実行する(本文とヘッダーを書き込む)ために、メソッド「process」があります。 Redisの場合、値はヘッダーのペア「CamelRedis.Key」/「CamelRedis.Value」を使用して送信されます。 HTTP GETリクエストを返すメッセージの本文を抽出し、「CamelRedis.Value」のタイトルにする必要があります。


キーは、ソートに適した一意のキー(ミリ秒単位の現在の時間)によって生成されます。


 import org.apache.camel.{Exchange, CamelContext} import org.apache.camel.main.Main import org.apache.camel.scala.dsl.builder.{ScalaRouteBuilder, RouteBuilderSupport} import org.springframework.data.redis.serializer.StringRedisSerializer object FromHTTPToRedisApp extends App with RouteBuilderSupport{ val mainApp = new Main //     stringSerializer  Redis mainApp.bind("stringSerializer",new StringRedisSerializer) val context = mainApp.getOrCreateCamelContext mainApp.addRouteBuilder(new FromHTTPToRedisRoute(context)) mainApp.run } class FromHTTPToRedisRoute (context: CamelContext) extends ScalaRouteBuilder(context) { //  ,      HTTP  """quartz:timerName?cron=0+0/1+*+*+*+?""" ==> { //     log("  ") //    to("http://www.google.com/finance/info?q=CURRENCY%3aUSDRUB") //   -  edis,    process((exchange: Exchange) => { exchange.getOut.setHeader("CamelRedis.Key",System.currentTimeMillis()) exchange.getOut.setHeader("CamelRedis.Value",exchange.getIn.getBody(classOf[String])) }) //             //        (Body: [Body is null]]) to("log:FromHTTPToRedisApp") //    Redis // #stringSerializer -      to("""spring-redis://172.16.7.58:6379?serializer=#stringSerializer""") } } 

リモートホストからRedisに書き込むには、許可が必要な場合があります。 たとえば、実行中のホスト上のRedisコンソールで、次のコマンドを実行します


 CONFIG SET protected-mode no 

Redisにエントリを表示する例を図に示します。


画像


例3
GET要求パラメーターに応じてメッセージを返すWebサービスの実装。 この例では、Jettyコンポーネントを使用して、パラメーター付きのGET要求を受信し、パラメーター値またはエラー付きのxmlを返す単純なHTTPサーバーを実装します。


 object JettyApp extends App with RouteBuilderSupport{ val mainApp = new Main val context = mainApp.getOrCreateCamelContext mainApp.addRouteBuilder(new JettyRoute(context)) mainApp.run } class JettyRoute(context: CamelContext) extends ScalaRouteBuilder(context) { //      """jetty:http://0.0.0.0:1234/myapp/myservice""" ==> { delay(2 seconds) process((exchange: Exchange) => { //    uuid  get    val uuidParam = exchange.getIn.getHeader("uuid") //     val pattern = """[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}""".r //        //    ,      uuid def responseText = uuidParam match { case null => "Uuid parameter not found" case pattern() => s"$uuidParam" case _ => s"Uuid parameter format is not valid" } //      xml exchange.getOut().setHeader(Exchange.CONTENT_TYPE,"text/xml; charset=utf-8") //  xml  . exchange.getOut().setBody(<uuid>{responseText}</uuid>) //      s"<uuid>$responseText</uuid>"   }) } } 

確認のリクエストの例:
" Http:// localhost:1234 / myapp / myservice?Uuid = 2a577d52-e5a1-4da5-96e5-bdba1f68e6f1 ;
" Http:// localhost:1234 / myapp / myservice?Uuid = 123 ;
" Http:// localhost:1234 / myapp / myservice ;
" Http:// localhost:1234 / myapp / myservice?Guid = 2a577d52-e5a1-4da5-96e5-bdba1f68e6f


サービス応答の例を図に示します。


画像


例4
キューからメッセージを読み取り、データベースに書き込みます。 別の例として、キューとデータベースの操作が強調されました。 これらのコンポーネントをカスタマイズするには、別のアプローチが必要です。 前の例で構成がエンドポイント行のパラメーターを使用して実行された場合、ここで事前にオブジェクトを作成し、それに基づいてコンポーネントを作成し、さらに使用する必要があります。


データベースの場合、org.apache.commons.dbcp2.BasicDataSourceクラスのインスタンスを作成し、接続パラメーターを渡します。 キューに対して、javax.jms.ConnectionFactoryクラスのインスタンスを作成します。このクラスには、接続パラメーターも保存されます。 次に、エンドポイントのこれらのコンポーネントの名前が作成され、URIで使用されます。 違いは、キャメルjdbcコンポーネントがデータベースに使用され、キャメルjmsに基づく新しいコンポーネントがキューに作成されることです。


例でレコードが挿入されるテーブルは、次のクエリによって作成されます。


 CREATE TABLE MESSAGETABLE( ID UUID NOT NULL PRIMARY KEY, DATETIME TIMESTAMP, BODY VARCHAR(65536) 

次のコードは、キューからメッセージを取得し、データベースでリクエストを実行して、一意の識別子、時間、およびメッセージ本文を追加します。


 import java.text.SimpleDateFormat import java.util.{UUID, Date} import org.apache.camel.component.jms.JmsComponent import org.apache.camel.main.Main import org.apache.camel.scala.dsl.builder.{RouteBuilderSupport, ScalaRouteBuilder} import org.apache.camel.{CamelContext, Exchange} //       BasicDataSource import org.apache.commons.dbcp2.BasicDataSource //    -   ConnectionFactory  import org.apache.activemq.ActiveMQConnectionFactory object FromMQToDBApp extends App with RouteBuilderSupport { val mainApp = new Main //            val ds = new BasicDataSource ds.setDriverClassName("org.h2.Driver") ds.setUrl("jdbc:h2:./h2db") //  endpoint  ,       "h2db" mainApp.bind("h2db",ds) //      MQConnectionFactory val cf = new ActiveMQConnectionFactory("tcp://192.168.3.38:61616") //       mainApp.bind("amq-jms", JmsComponent.jmsComponentAutoAcknowledge(cf)) val context = mainApp.getOrCreateCamelContext mainApp.addRouteBuilder(new FromMQToDBAppRoute(context)) mainApp.run } //            class FromMQToDBAppRoute(context: CamelContext) extends ScalaRouteBuilder(context) { //    .   ,      - "amq-jms",      //         """amq-jms:queue:TESTQ""" ==> { process((exchange: Exchange) => { //  uuid, / val uuid = UUID.randomUUID val time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) //    val messageBody = exchange.getIn.getBody(classOf[String]) //     exchange.getOut.setBody(s"INSERT INTO PUBLIC.MESSAGETABLE (ID, DATETIME, BODY) VALUES('$uuid', '$time', '$messageBody')") }) //      //   jdbc,    DataSource to("jdbc:h2db") } } 

メッセージデータベースに書き込もうとすると、フィールドがフィールドよりも長いことに注意してください(以前のクエリで作成されたテーブルのフィールド長は65536文字です)-エラーが発生します。 これは、ボディを目的のサイズにトリミングするか、errorHandler(deadLetterChannel( "file:error"))を追加することで解決できます。これにより、「error」フォルダー内のエラーにつながるメッセージが送信されます。


この例では、H2データベースとの相互作用を考慮しています。 他のデータベースの場合、適切なライブラリをbuild.sbtに追加し、ドライバークラスの名前、URLを決定する必要があります。 ユーザー名やパスワードなど、他の接続プロパティが必要になる場合があります。


Postgresqlを操作するための接続の詳細の説明の例:


build.sbtへのライブラリの追加


  libraryDependencies += "org.postgresql" % "postgresql" % "9.4.1207" 

クラスの実装:


  val ds = new BasicDataSource { setDriverClassName("org.postgresql.Driver") setUrl(conf.getString("jdbc:postgresql://myhost:5432/mydb")) setUsername(conf.getString("myusername")) setPassword(conf.getString("mypassword")) } 

キューイングは少し複雑です。 一部のキューマネージャでは、リポジトリでアクセスするためのライブラリが開かれていません。 この場合、* .jarファイルが使用され、プロジェクトのlibフォルダーに保存されます。


キューマネージャの場合、接続ファクトリタイプの適切なオブジェクトを作成する必要があります。
たとえば、IBM Websphere MQと対話するコードは次のようになります。


  val cf = new MQQueueConnectionFactory { setHostName("myhost") setPort(1414) setTransportType(1) setQueueManager("myqmname") setChannel("mychannel") } 

Oracle Weblogic Jmsの場合はさらに興味深いです。 Weblogic Server 11gで単純なJMSキューを作成する方法のキューを作成する場合、コンポーネント宣言は次のようになります。


  val env = new util.Hashtable[String, String] env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory") env.put(javax.naming.Context.PROVIDER_URL, "t3://myhost:7001") val ic: InitialContext = new InitialContext(env) val connectionFactory = ic.lookup("jms/TestConnectionFactory").asInstanceOf[QueueConnectionFactory] //  jms/TestConnectionFactory - jndi  ConnectionFactory" mainApp.bind("ora-jms", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory)) 

エンドポイントURIは次の形式になります:「ora-jms:queue:./ TestJMSModule!TestJMSQueue」、ここで./は現在のサーバー、「TestJMSModule」JNDIモジュール名「TestJMSQueue」-JNDIキュー名


例5
ファイルの内容によるルーティング。 この例では、コンテンツに応じてメッセージのルーティングを検討します。


入力にxmlメッセージがあり、その処理はTo要素の値に依存すると仮定します。


<To>ActiveMQ</To> -キューに送信する必要があり、 <To>H2</To> -何らかの方法で処理してデータベースに送信する必要があり、 <To>someAdress</To> -何らかの方法で処理します。


メッセージの送信先となるエンドポイントの名前を含むDestinationヘッダーがメッセージに追加されます。


メッセージの処理中にエラーが発生した場合、またはルーティングテーブルに対応する値がない場合、メッセージを「direct:trash」に送信します。


この例では、ロック構造 "???"を使用しています。これにより、存在しないコードブロックを置き換えてコンパイルを成功させることができます。 代わりに、処理ロジックを記述する必要があります。


 import org.apache.camel.{Exchange, CamelContext} import org.apache.camel.scala.dsl.builder.ScalaRouteBuilder import scala.xml.XML class ContentOrientedRouting(context: CamelContext) extends ScalaRouteBuilder(context) { //    ,    "direct:trash" errorHandler(deadLetterChannel("direct:trash")) //      Map val destMap = Map( "ActiveMQ" -> "jms-amq:queue:inbox", "H2" -> "direct:h2db", "someAdress" -> "direct:outbox") //      val addRoutingAction = (exchange: Exchange) => { //    "To"  XML-,     val body = exchange.getIn.getBody(classOf[String]) val xmlBody = XML.loadString(body) val toValue = (xmlBody \\ "To").text //   endpoint,     -   "direct:trash" val dest = destMap.getOrElse(toValue,"direct:trash") //    exchange.getOut.setHeader("Destination", dest) } """direct:inbox1""" ==> { process(addRoutingAction) //    "Destination" endpoint     recipients(_.in("Destination")) } //     endpoint """jms-amq:queue:inbox""" ==> {???} """direct:h2db""" ==> { process((exchange: Exchange) => {???}) to ("jdbc:h2db") } """direct:outbox""" ==> { //         to("file:someFile", "log:Somelog") } """direct:trash""" ==> {???} } 

この例は、この目的のために小さなアプリケーションを実装する方法を示しています。 追加の側面を検討しましょう。 アプリケーションの開発と保守をより便利にすることができます。


アプリケーションを構成するために、コードに接続パラメーターをつなぐのではなく、構成ファイルに保存するために、Typesafe ライブラリーを使用します。


build.sbtに以下を追加します。


 libraryDependencies += "com.typesafe" % "config" % "1.3.0" 

src / main / resourcesフォルダーでapplication.confファイルを作成します。このファイルに設定を指定し、コードから設定を呼び出します。


アプリケーションは、sbt runコマンドによって起動されます。 場合によっては、これは不便かもしれません。
sbt-assemblyプラグインhttps://github.com/sbt/sbt-assemblyを使用してjarファイルを作成し、java –jar camelapp.jarコマンドを実行することができます。 すべての依存関係は.jarファイルに含まれるため、サイズは大きくなりますが、コンポーネントをダウンロードせずにすぐに開始します。


nohupアプリケーションを使用してバックグラウンドで実行すると便利です。


$ PATH環境変数に含まれるフォルダーで実行するスクリプトを作成し、任意のディレクトリから名前で呼び出します。 たとえば、/ usr / local / bin /にあります。 実行するスクリプト:


/ usr / local / bin / camelstart


 #!/bin/bash /usr/bin/nohup java -jar /opt/camelapp.jar& 

停止するには:
/ usr / local / bin / camelstop


 #!/bin/bash pkill -f camelapp 

アプリケーションの起動はcamelstartコマンドで行われ、停止はcamelstopで行われます。


Apache Camelを使用することの長所と短所を強調できます。
長所:



短所:



さらに、Apache CamelはJVM上で実行されるため、それに基づいて構築されたアプリケーションにはこのプラットフォームの長所と短所があります。


弊社でApache CamelをScalaDSLと組み合わせて使用​​した経験は、スタブ、統合コンポーネント、および場合によっては負荷テストの作成に対する有効性を示しています。



Source: https://habr.com/ru/post/J306792/


All Articles