1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaのSeleniumテストがDockerコンテナを起動・停止できるようにした

Last updated at Posted at 2022-02-06

このサンプル一式を収めたGitHubプロジェクトが下記の場所にあります。

解決したい問題

わたしはWebのユーザインターフェースつまりブラウザに表示されたHTML画面を対象として自動化テストを実行するコードをJavaまたはGroovy言語で書いています。いっぽう勉強のため、Python言語で書いたWebサーバアプリケーションを包んだDockerイメージを作った。サーバアプリをDockerコンテナとして立ち上げて、Javaで書いたSeleniumテストで動作確認したくなりました。

それはもちろんできます。次のような手順で操作すればいい。

(1) わたしのMacBook AirでTerminalのウインドウを開き、Dockerコンテナをデーモンプロセスとして起動する。するとWebサーバアプリが立ち上がって http://127.0.0.1/ というURLが使えるようになる。

$ cd ~/tmp
$ docker run -d -p 80:8080 kazurayam/flaskr-kazurayam:1.1.0

(2) JUnitテストを含むプロジェクトにcdして、Gradleのtestコマンドを起動してSeleniumテストを実行する。

$ cd ~/SeleniumTestInJavaBackedByDockerContainer
$ gradle test

するとSeleniumによってWebブラウザが立ち上がり、その中で http://127.0.0.1/ のサイトが開いて、閉じる。これでテストは終了。

(3) テストが終了したら後始末をしよう。下記のコマンドを実行する。IPポート80番をLISTENしているDockerコンテナのidがわかる。

$ docker ps --filter publish=80 --filter status=running -q
fd5ad3b76b13

(4) 見つけたDockerコンテナのidを指定して停止する。

$ docker stop fd5ad3b76b13

この手順はむずかしくない。だがSeleniumテストを実行するたびに繰り返しDockerコンテナを起動して停止するのが面倒だ。操作を間違えるし。たとえばDockerコンテナを停止せずにもう一度起動しようとすると "IP port is already in use" といわれてしまう。だからわたしは次のことを実現したい。

Seleniumテストのためにlocalhost上でWebサーバを動かしたいが、そのためにDockerコンテナを起動・停止するのをJUnitテストの一部としてJavaコードから実行したい。

解決方法

わたしは subprocessj というJavaライブラリを自作しMaven Central で公開した。subprocessjはjava.lang.ProcessBuilderをくるんで簡素なAPIで呼び出せるようにしたもの。subprocessjのクラス群を使えばJavaコードからdocker run コマンドを実行することができる。

次のようなことをするJUnitテストをサンプルとして書いた。

  • このテストはSelenium WebDriverを使って "http://127.0.0.1:3080/" というURLをテストする

  • このURLを提供するWebサーバはローカルホスト上のプロセスで動く。そのプロセスを起動するのにDockerコンテナを使う。わたしが事前に準備したDockerイメージを利用する。

  • このURLを提供するWebサーバはPython言語で書かれており、Palletsプロジェクトが開発して https://flask.palletsprojects.com/en/2.0.x/tutorial/ で公開しているもの。それをわたしがDockerイメージに直した。Seleniumテストを開発するための練習台として繰り返し利用できるようにしたかったので。

  • このテストはDockerコンテナを起動・停止するのだが、そのためには docker run, docker ps , docker stopなどのコマンドライン用のコマンドをJUnitテストの内部でJavaコードから実行する。

説明

実行環境

サンプルを実行するために必要な環境は次のとおり

  • Dockerをインストールする必要がある。わたしは主にMacで作業する。Docker Desktopをインストールした。
  • Widnows10のPCにWindows版のDocker Desktopをインストールして、動作することを確認済み。
  • Java8+ と Gradle v6+ が必要。
  • わたしはMacマシンで、bashシェルの環境でテストした。

シーケンス図

サンプルのJUnitテストがどういう動作するのか、処理シーケンスを図にした。

sequence

サンプルとしての JUnit5テスト

サンプルコードの全体を読むには下記のリンクを参照のこと。

コードの一部を引用しながら説明しよう。

@BeforeAll

Dockerコンテナを起動するところ

        @BeforeAll
        public static void beforeAll() throws IOException, InterruptedException {
            File directory = Files.createTempDirectory("DockerBackedWebDriverTest").toFile();
            ContainerRunningResult crr =
                    ContainerRunner.runContainerAtHostPort(directory, publishedPort, image);
            if (crr.returncode() != 0) {
                throw new IllegalStateException(crr.toString());
            }
            // setup ChromeDriver
            WebDriverManager.chromedriver().setup();
        }

com.kazurayam.subprocessj.docker.ContainerRunner クラスが "docker run" コマンドを包み込んでいる。これによってDockerコンテナを起動する。

@BeforeEach

Webブラウザを起動するところ

        @BeforeEach
        public void beforeEach() {
            driver = new ChromeDriver();
        }

@Test

ブラウザの中でURLを開き、応答されたHTMLの中身を検査するところ

        @Test
        public void test_page_header() {
            driver.navigate().to(String.format("http://127.0.0.1:%d/", HOST_PORT));
            WebElement siteName = driver.findElement(By.xpath("/html/body/nav/h1"));
            assertNotNull(siteName);
            assertEquals("Flaskr", siteName.getText());
            delay(2000);
        }

ちなみに http://127.0.0.1/ をブラウザで開いたらこんな画面が見える。

Flaskr

@AfterEach

ブラウザを閉じるところ

        @AfterEach
        public void afterEach() {
            if (driver != null) {
                driver.quit();
                driver = null;
            }
        }

@AfterAll

Dockerコンテナを停止するところ。所定のIPポート番号をLISTENしているDockerコンテナを探し、そのidを把握する。そしてコンテナidを指定してdocker stopする。

        @AfterAll
        public static void afterAll() throws IOException, InterruptedException {
            ContainerFindingResult cfr = ContainerFinder.findContainerByHostPort(HOST_PORT);
            if (cfr.returncode() == 0) {
                ContainerId containerId = cfr.containerId();
                ContainerStoppingResult csr = ContainerStopper.stopContainer(containerId);
                if (csr.returncode() != 0) {
                    throw new IllegalStateException(csr.toString());
                }
            } else {
                throw new IllegalStateException(cfr.toString());
            }
        }

docker stopでコンテナを停止するにはわりと時間がかかる。わたしの手元で10秒ぐらい。

サンプルを再利用するには

build.gradle を見てください。Gradleに関するスキルを持っている人を前提し説明を省略します。

結語

この記事で紹介したクラス群は Subprocessj 0.3.1 以降に含まれています。これによってわたしはPythonで書いたWebサーバアプリを対象にWebユーザインタフェースの自動化テストをJavaで書くことができるようになった。Dockerコンテナ化したWebサーバの起動と停止をJUnitテストの中で自動実行できるので、とても楽だ。この方法はDockerコンテナの中身がなんであれ応用できる。ほかにどんな使い道があるか、いろいろ試してみたい。

Dockerに関する参考情報

その他関連情報

Subprocessjプロジェクトの成果物はMaven Centralリポジトリにあります。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?