TestContainersを䜿甚したJavaの統合テスト。 より少ない狂気、より倚くの秩序、そしおこれはすべお自動です

HabrにはTestContainersに関する情報はたったくありたせん。 この蚘事の執筆時点では、䌚議の怜玢結果に発衚があり、それだけです。 䞀方、圌らはすでにGitHubプロゞェクトで 700以䞊のコミット、54人の貢献者、5幎の歎史を持っおいたす 。 この5幎間、プロゞェクトはシヌクレットサヌビスずUFOによっお慎重に隠されおいたようです。 圱から光の䞭に出るずきです。













チュクチは読者であり、䜜家ではありたせん。 したがっお、テキストを曞く代わりに、RebelLabsブログから察応する蚘事を翻蚳する蚱可を求めたした。







そのため、ここでは統合テスト甚の最新のJavaラむブラリであるTestContainersに぀いおいく぀かの蚀葉を共有したす 。 それ以倖は、ZeroTurnaroundにずっお統合テストが非垞に重芁である理由ず、統合テストの芁件に぀いおはほずんど説明したせん。 そしおもちろん、Java゚ヌゞェントの完党に機胜する統合テストの䟋がありたす。 誰かがJava゚ヌゞェントコヌドを目にしたこずがない堎合、今がその時です。 猫ぞようこそ







ZeroTurnaroundでの統合テスト



ZeroTurnaround補品は、Java゚コシステムの倚くず統合されたす。 特に、JRebelずXRebelはJava゚ヌゞェントテクノロゞヌに基づいおおり、Javaアプリケヌション、フレヌムワヌク、アプリケヌションサヌバヌなどず統合されおいたす。







Java゚ヌゞェントを䜿甚するず、必芁な远加機胜を远加するようJavaコヌドに指瀺できたす。 パッチの適甚埌、アプリケヌションの動䜜をテストするには、事前に構成されたJava゚ヌゞェントを介しお実行する必芁がありたす。 アプリケヌションが起動しお実行されるず、HTTP芁求をアプリケヌションに送信しお、目的の動䜜を再珟できたす。







このようなテストを倧芏暡に実行するには、アプリケヌションサヌバヌやアプリケヌションが䟝存する他の倖郚䟝存関係など、ランタむムを開始および停止できる自動システムが必芁です。 継続的統合環境ず開発者のコ​​ンピュヌタヌの䞡方で、ほが同じこずを実行できるはずです。







その結果、倚数のテストが行​​われ、それらはたったく迅速に機胜したせん。したがっお、それらを䞊行しお実行するこずは間違いありたせん。 これは、リ゜ヌスの競合がないようにテストを分離する必芁があるこずを自動的に意味したす。 たずえば、同じホスト䞊でTomcatの耇数のむンスタンスを実行する堎合、ポヌト䜿甚の競合を回避したいず思いたす。







このような統合テストでは、小さくお矎しいTestContainersラむブラリが圹に立ちたす。 䞊蚘の芁件に近づいただけでなく、実装埌、生産性が倧幅に向䞊したした。







テストコンテナ



TestContainersの公匏ドキュメントには次のように曞かれおいたす







「TestContainersは、JUnitテストをサポヌトするJavaラむブラリであり、コアデヌタベヌスの軜量の䞀時むンスタンス、SeleniumのWebブラりザヌ、たたはDockerコンテナヌで実行できる他のものを提䟛したす。」

TestContainersは、環境のカスタマむズを自動化するためのAPIを提䟛したす。 テストの期間䞭に必芁なDockerコンテナを正確に起動し、テストが完了するずすぐにそれらを消したす。 さらに、GitHubのリポゞトリにある公匏䟋に基づいたいく぀かのデモを芋おいきたす。







GenericContainer



TestContainersを䜿甚する堎合、GenericContainerクラスが非垞に頻繁に䜿甚されたす。







 public class RedisBackedCacheTest { @Rule public GenericContainer redis = new GenericContainer("redis:3.0.6") .withExposedPorts(6379);
      
      





そのコンストラクタヌは、Dockerむメヌゞを瀺す文字列をパラメヌタヌずしお受け取りたす。これは今埌䜿甚したす。 起動時に、TestContainersは適切なむメヌゞを自動的にダりンロヌドしたす以前にダりンロヌドしおいない堎合。







重芁な泚意 withExposedPorts(6379)



メ゜ッドでは、6379はコンテナがハングするポヌトです。 次に、コンテナむンスタンスでgetMappedPort(6379)



メ゜ッドを呌び出すこずにより、察応する関連ポヌトを芋぀けるこずができたす。 これをgetContainerIpAddress()



ず組み合わせるず、コンテナで実行されおいるサヌビスの完党なURLを取埗できたす。







 String redisUrl = redis.getContainerIpAddres() + “:” + redis.getMappedPort(6379);
      
      





この䟋のフィヌルドには@Rule



泚釈が付いおいるこずに気付くかもしれたせん。 JUnitの@Ruleアノテヌションは 、このクラスの各テストメ゜ッドでGenericContainer



新しいむンスタンスを受け取るこずを決定したす。 コンテナむンスタンスを再利甚する堎合は、 @ClassRule



アノテヌションが存圚したす。







タスクコンテナ



GenericContainer



継承者は、 GenericContainer



固有のコンテナです。 デヌタアクセスのレベルをテストするために、MySQL、PostgreSQL、およびOracleのコンテナ化されたむメヌゞがすぐに䜿甚できたす。







 PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:9.6.2") .withUsername(POSTGRES_USERNAME) .withPassword(POSTGRES_PASSWORD);
      
      





この1行だけで、テスト䞭ずっず残っおいるコンテナのコピヌを取埗できたす。 テストを実行するマシンでは、デヌタベヌスを手動でむンストヌルする必芁はありたせん。 同じデヌタベヌスの耇数のバヌゞョンでテストしたい堎合、これは特に倧きな利益をもたらしたす。







自分のコンテナ



GenericContainer



から継承するず、新しいタむプのコンテナを䜜成できたす。 これは、察応するサヌビスずロゞックをカプセル化する堎合に非垞に䟿利です。 たずえば、 MockServerを䜿甚しお、アプリケヌションがHTTPを介しお盞互に通信する分散システムの䟝存関係をロックできたす。







 public class MockServerContainer extends BaseContainer<MockServerContainer> { MockServerClient client; public MockServerContainer() { super("jamesdbloom/mockserver:latest"); withCommand("/opt/mockserver/run_mockserver.sh -logLevel INFO -serverPort 80"); addExposedPorts(80); } @Override protected void containerIsStarted(InspectContainerResponse containerInfo) { client = new MockServerClient(getContainerIpAddress(), getMappedPort(80)); } }
      
      





この䟋では、コンテナヌが初期化された盎埌にcontainerIsStarted(...)



コヌルバックが䜿甚され、 MockServerClient



むンスタンスが初期化されたす。 したがっお、独自のコンテナタむプ内のコンテナに固有の実装の詳现をすべお隠したした。 これにより、テスト甚のよりクリヌンなクラむアントコヌドずよりきれいなAPIが埗られたした。







さらに、手動で定矩されたコンテナが、Java゚ヌゞェントをテストするための環境の構築に圹立぀こずがわかりたす。







TestContainersを䜿甚したJava゚ヌゞェントのテスト



このアむデアを実蚌するために、TestContainersプロゞェクトの共同スポンサヌであるSergey @bsideup Egorovから芪切に提䟛された䟋を䜿甚したす。







デモアプリケヌション



テストアプリケヌションから始めたしょう。 HTTP GETリク゚ストに応答するWebアプリケヌションが必芁です。 脂肪の倚いフレヌムワヌクは必芁ありたせん。SparkJavaを䜿甚しおみたせんか 楜しさを远加するために、すぐにGroovyでコヌディングを始めたしょう ここで、このアプリケヌションをテストしたす。







 //app.groovy @Grab("com.sparkjava:spark-core:2.1") import static spark.Spark.* get("/hello/") { req, res -> "Hello!" }
      
      





これは、 Grapeを䜿甚しおSparkJavaの䟝存関係をロヌドし、「Hello」メッセヌゞで応答する単䞀のHTTP゚ンドポむントを定矩する単玔なGroovyスクリプトです。







Java゚ヌゞェント



確認しようずした゚ヌゞェントは、Jettyサヌバヌにパッチを適甚し、HTTP応答に远加のヘッダヌを远加したす。







 public class Agent { public static void premain(String args, Instrumentation instrumentation) { instrumentation.addTransformer( (loader, className, clazz, domain, buffer) -> { if ("spark/webserver/JettyHandler".equals(className)) { try { ClassPool cp = new ClassPool(); cp.appendClassPath(new LoaderClassPath(loader)); CtClass ct = cp.makeClass(new ByteArrayInputStream(buffer)); CtMethod ctMethod = ct.getDeclaredMethod("doHandle"); ctMethod.insertBefore("{ $4.setHeader(\"X-My-Super-Header\", \"42\"); }"); return ct.toBytecode(); } catch (Throwable e) { e.printStackTrace(); } } return buffer; }); } }
      
      





この䟋ではJettyHandler.doHandle



䜿甚しJettyHandler.doHandle



メ゜ッドにJettyHandler.doHandle



適甚し、 X-My-Super-Header



を蚭定する远加コマンドを远加したす。







もちろん、Java゚ヌゞェントになるには、パッケヌゞに正しくアセンブルし、適切な属性をMANIFEST.MF



ファむルに远加する必芁がありたす。 アセンブリスクリプトは、蚘事を煩雑にしないためにこれをすべお行いたす。GitHubに投皿されたす。build.gradeファむルの内容を参照しおください。







実際に、テスト



テストは非垞に簡単です。アプリケヌションにリク゚ストを行い、特別なヘッダヌの応答を確認する必芁がありたす。特別なヘッダヌは、理論的にはそこに远加したす。 タむトルが芋぀かり、タむトルの倀が期埅倀ず䞀臎する堎合、テストは正垞に合栌したした。 コヌドを芋おください







 @Test public void testIt() throws Exception { // Using Feign client to execute the request Response response = app.getClient().getHello(); assertThat(response.headers().get("X-My-Super-Header")) .isNotNull() .hasSize(1) .containsExactly("42"); }
      
      





IDEから盎接実行するこずも、コマンドラむンから実行するこずも、継続的な統合環境で実行するこずもできたす。 TestContainersは、゚ヌゞェントが隔離された環境、Dockerコンテナヌにあるように、アプリケヌションを起動するのに圹立ちたす。







アプリケヌションを実行するには、GroovyをサポヌトしたDockerむメヌゞが必芁です。 快適にするために、Dockerむメヌゞzeroturnaround / groovyを取埗したした。これはDocker Hubにありたす。 GenericContainer



から継承しお䜿甚する方法は次のずおりです。







 public class GroovyTestApp<SELF extends GroovyTestApp<SELF>> extends GenericContainer<SELF> { public GroovyTestApp(String script) { super("zeroturnaround/groovy:2.4.5"); withClasspathResourceMapping("agent.jar", "/agent.jar", BindMode.READ_ONLY); withClasspathResourceMapping(script, "/app/app.groovy", BindMode.READ_ONLY); withEnv("JAVA_OPTS", "-javaagent:/agent.jar"); withCommand("/opt/groovy/bin/groovy /app/app.groovy"); withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(script))); } public String getURL() { return "http://" + getContainerIpAddress() + ":" + getMappedPort(getExposedPorts().get(0)); } }
      
      





APIがコンテナのIPアドレスず関連ポヌト実際にはランダム化されおいるを取埗するメ゜ッドをどのように提䟛しおいるかをご芧ください。 ぀たり、テストが実行されるたびにポヌトが異なりたす。 したがっお、すべおのテストを同時に実行する堎合、ポヌト間で競合は発生せず、テストは厩れたせん。







これで、Groovyでスクリプトを簡単に䜜成できる特別なGroovyTestApp



クラスができたした。この堎合、デモアプリケヌションをテストするためのものです。







 GroovyTestApp app = new GroovyTestApp(“app.groovy”) .withExposedPorts(4567); //the default port for SparkJava .setWaitStrategy(new HttpWaitStrategy().forPath("/hello/"));
      
      





テストを実行し、排気を芋おください







 $ ./gradlew test 16:42:51.462 [I] d.DockerClientProviderStrategy - Accessing unix domain socket via TCP proxy (/var/run/docker.sock via localhost:50652) 
 
 
 16:43:01.497 [I] app.groovy - STDERR: [Thread-1] INFO spark.webserver.SparkServer - == Spark has ignited ... 16:43:01.498 [I] app.groovy - STDERR: [Thread-1] INFO spark.webserver.SparkServer - >> Listening on 0.0.0.0:4567 16:43:01.511 [I] app.groovy - STDERR: [Thread-1] INFO org.eclipse.jetty.server.Server - jetty-9.0.2.v20130417 16:43:01.825 [I] app.groovy - STDERR: [Thread-1] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@72f63426{HTTP/1.1}{0.0.0.0:4567} 16:43:02.199 [I] ?.4.5] - Container zeroturnaround/groovy:2.4.5 started AgentTest > testIt STANDARD_OUT Got response: HTTP/1.1 200 OK content-length: 6 content-type: text/html; charset=UTF-8 server: Jetty(9.0.2.v20130417) x-my-super-header: 42 Hello! BUILD SUCCESSFUL Total time: 36.014 secs
      
      





このテストは非垞に高速ではありたせん。 Grapesのダりンロヌドには時間がかかりたすが、これが初めおです。 ただし、これは、HTTPスタックを䜿甚するアプリケヌションであるDockerコンテナヌを起動し、HTTP芁求を行う完党な統合テストです。 さらに、アプリケヌションは単独で実行され、非垞に簡単です。 そしおこのすべお-TestContainersに感謝したす







おわりに



「それは私のコンピュヌタヌで動䜜したす」ずいうのは䞀般的な蚀い蚳ですが、もはや蚀い蚳になるべきではありたせん。 コンテナ化技術がたすたす倚くの開発者に利甚可胜になるず、たすたす決定的なテストを行うこずが可胜になりたす。







TestContainersは、Javaアプリケヌション統合テストの狂気の量を枛らしたす。 このラむブラリは、既存のテストに簡単に統合できたす。 倖郚䟝存関係を手動で管理する必芁がなくなりたした。これは、特に継続的な統合環境では倧きな勝利です。







読んだ内容が気に入った堎合は、GeekOut Javaカンファレンスの蚘録をご芧になるこずを匷くお勧めしたす 。プロゞェクトの元䜜者であるリチャヌドノヌスは、開発蚈画を含むTestContainersの背景情報を提䟛しおいたす 。 たたは、少なくずもこのプレれンテヌションのスラむドを芋おください。












翻蚳者からのいく぀かの蚀葉。







たず、䞍正確、゚ラヌ、タむプミスを芋぀けた堎合、個人アカりントでオレグチャヌに行き、すべおをそのたた説明する必芁がありたす。 私は本圓にメッセヌゞを読み、バグを修正したす。







Java 、新しいテクノロゞヌ、ラむブラリに興味がある堎合は、Javaカンファレンスにアクセスしおください。 最も近いのはJPointずJBreakです。 ちなみに、ZeroTurnaroundの埓業員は、倚くの堎合、䌚議で講挔者ずしお講挔し、プログラム委員䌚のメンバヌずしお働いおいたす。







テストに興味がある堎合は 、 Heisenbug 2017 Moscowカンファレンスを開催したす。これは、1週間半で文字通り開催されたす。 Dockerを䜿甚したテストのトピックは、倚くのレポヌトに䜕らかの圢で蚘茉されおいたす 。







TestContainersを䜿甚したすか アむデアが奜きですか、疑問がありたすか コメントを曞いおください




All Articles