JenkinsとDockerを使い、WebアプリのE2Eテストをする
前回のエントリの続きです。
今回は単純なアプリのテストなので、Dockerfileもテスト手順もシンプルでした。
より複雑な手順が必要となる、Seleniumを利用したWEBアプリのE2Eテストなどもこの方式でできるのかどうか、引き続き実験してみます。
ということで実験しました。
目次
実際の手順
1. WEBアプリを準備する
Laravel4で、GET /users
するとusersテーブルからユーザー一覧を取得して表示する、という簡単なものを作成しました。
piccagliani/docker-l4-sample · GitHub
テストはCodeceptionのWebDriverモジュールを利用して記述しました。
<?php $I = new AcceptanceGirl($scenario); $I->wantToTest('list users'); $I->amOnPage('/users'); $I->see('Bill Evans', '#users tbody tr:nth-child(1)'); $I->see('Thelonius Monk', '#users tbody tr:nth-child(2)'); $I->see('Bud Powell', '#users tbody tr:nth-child(3)');
2. Dockerコンテナ上でWEBアプリケーションを公開する
Dockerファイルをアプリケーションリポジトリの.docker/Dockerfile
に記述します。
少し悩んだ点は、ApacheやMySQLなどの設定をどのように行えばよいか、ということ。
選択肢として、以下を考えました。
- RUN命令でsedを使い、必要な箇所の置換を行う
- ADD命令でファイルを置き換える
前者は、単純な置換であればいいですが、あっという間に未来のないDockerfileになりそうです。
なので後者でいくことにしました。
Dockerfileと並列に、
を用意し、ADDします。
結果、Dockerfileは以下のようになりました。
FROM centos:6 MAINTAINER @piccagliani RUN cp -p /usr/share/zoneinfo/Asia/Tokyo /etc/localtime RUN echo ZONE="Asia/Tokyo" > /etc/sysconfig/clock RUN echo UTC="false" >> /etc/sysconfig/clock RUN source /etc/sysconfig/clock RUN yum update -y RUN yum install -y git RUN rpm -Uvh http://ftp.jaist.ac.jp/pub/Linux/Fedora/epel/6/i386/epel-release-6-8.noarch.rpm RUN rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm # PHP 5.6 RUN yum install -y --enablerepo=remi --enablerepo=remi-php56 php php-pdo php-mysql php-mbstring php-mcrypt # Composer RUN curl -sS https://getcomposer.org/installer | php RUN mv composer.phar /usr/local/bin/composer # MySQL RUN rpm -Uvh http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm RUN yum install -y mysql-community-server # Add config files ADD httpd.conf /etc/httpd/conf/httpd.conf ADD my.cnf /etc/my.cnf ADD php.ini /etc/php.ini # Apache EXPOSE 80 CMD ["/bin/bash"]
docker run
する際に、 -p 8000:80
オプションをつけてあげれば、コンテナの外部からホストのIPを指定してWEBアプリケーションにアクセスできるようになります。
3. Seleniumコンテナを導入、起動する
Codeceptionの開発者である@davertさんが、Selenium Server, Xvfb, Firefox, Chromiumを導入済みのコンテナイメージを共有してくれています。
詳細は Acceptance Testing With No Selenium or PhantomJS Installed を確認してください。
$ docker pull davert/selenium-env $ docker run -d -p 4444:4444 davert/selenium-env
これでバックグラウンドでコンテナが起動している状態となります。
4. CodeceptionのWebDriverモジュールの設定をする
結果から書くと、以下のような設定を行いました。
class_name: AcceptanceGirl modules: enabled: - AcceptanceHelper - Db - WebDriver config: WebDriver: host: 172.17.42.1 # SeleniumサーバーのIP = DockerホストのIP port: 4444 url: 'http://172.17.42.1:8000/' # テスト対象のIP = これまたDockerホストのIP browser: firefox window_size: 1680x1050 wait: 10 capabilities: unexpectedAlertBehaviour: 'accept'
ちょっと悩んだのは、以下の設定項目。
- url
- Starting URL for your app.
- host
- Selenium server host (127.0.0.1 by default).
- port
- Selenium server port (4444 by default).
コメントとして書いていますが、WEBアプリのコンテナも、SeleniumEnvコンテナも、-p
オプションでポートのマッピングを行っているので、どちらもDockerホストのIPにすればとりあえず動くんじゃね?と。。。
5. テスト実行用のスクリプトを作成する
$APPROOT/.docker/run-tests.sh
を以下のように作成しました。
#!/bin/bash service httpd start service mysqld start cd /opt/docker-l4-sample chown -R 777 app/storage mysql -u root < ./.docker/setup.sql composer install vendor/bin/codecept run --debug
前回と同様、/opt/docker-l4-sample
にはJenkinsのジョブのワークスペースがマウントされます。
また、./.docker/setup.sql
はアプリ用のDB・ユーザーの作成を行います。
6. Jenkinsにジョブを作成し、ビルドする
前回と基本的には同じです。
ジョブ名
リポジトリの名前に合わせました。
ジョブの形式
「フリースタイル・プロジェクトのビルド」を選択。
ビルド手順
「シェルの実行」を選択し、以下を記述。ポートのマッピングオプションを追加しています。
docker build -t $JOB_NAME $WORKSPACE/.docker/ docker run -v $WORKSPACE:/opt/$JOB_NAME -p 8000:80 $JOB_NAME sh /opt/$JOB_NAME/.docker/run-tests.sh
ビルドを実行すると、
コンテナがビルドされ、
run-tests.sh
が実行され
最終的に、こんな出力となり、
Codeception PHP Testing Framework v2.0.9 Powered by PHPUnit 4.4.2 by Sebastian Bergmann. Rebuilding AcceptanceGirl... Acceptance Tests (1) ----------------------------------------------------------------------- Modules: AcceptanceHelper, Db, WebDriver -------------------------------------------------------------------------------------------- Trying to test list users (usersCept) Scenario: * I am on page "/users" * I see "Bill Evans","#users tbody tr:nth-child(1)" * I see "Thelonius Monk","#users tbody tr:nth-child(2)" * I see "Bud Powell","#users tbody tr:nth-child(3)" PASSED -------------------------------------------------------------------------------------------- Functional Tests (1) ----------------------------------------------------------------------- Modules: Filesystem, FunctionalHelper, Db, Laravel4 -------------------------------------------------------------------------------------------- Trying to test list users (usersCept) Scenario: * I am on page "/users" [Response] 200 [Page] http://localhost/users [Cookies] [] [Headers] {"cache-control":["no-cache"],"date":["Fri, 23 Jan 2015 09:49:20 GMT"],"content-type":["text/html; charset=UTF-8"]} * I see "Bill Evans","#users tbody tr:nth-child(1)" * I see "Thelonius Monk","#users tbody tr:nth-child(2)" * I see "Bud Powell","#users tbody tr:nth-child(3)" PASSED -------------------------------------------------------------------------------------------- Rebuilding UnitGirl... Unit Tests (0) ------------------------------ Modules: Asserts, UnitHelper --------------------------------------------- --------------------------------------------- Time: 1.39 minutes, Memory: 18.00Mb OK (2 tests, 6 assertions) Finished: SUCCESS
ビルドが成功しました!
今後の課題など
Jenkinsのジョブが同時に走った場合
今回はSeleniumとWEBアプリ間の通信にホストIPを利用して行いましたが、異なるアプリのE2Eテストが同時に実行された場合を考えると、アプリ毎にポートのマッピングを変えないといけないのではないか?と思います。
Jenkinsにジョブを設定する際に「どのポート空いてるんだっけ?」と考えるのはちょっとイヤですね。
他のホストのコンテナに接続するパターン - ✘╹◡╹✘
などを参考に、コンテナ間の通信はどうあるべきか、について理解する必要がありそうです。
以上となります。
課題はありますが、理想のCIサーバを目指し、また一歩進みました。