はじめに
自分は長らく仕事で、PerlのWebアプリケーションのおもりをしています。
ですが、そのアプリケーションにはテストがなくて、何をするにもドキドキです…。
そもそも作り始められた当時はそのような文化はメジャーではなかったのです。
時間が経ち、世の中には便利なものがたくさん登場してきました。
今であれば、そのアプリケーションに自動テストを動かす環境を整備することもできなくはないのではないか?
そう考えました。
シンプルなPerl CGIのアプリケーションをDocker上で動かし、
CircleCIを使って、E2Eテストを動かすようにしてみます。
動かすまでが目的なので、そんなに凝ったことはしません。
ソース
この記事で使っているソースは↓です。
https://github.com/ken1flan/perl_test_on_circleci
Perl CGIのサンプルアプリケーション
サンプルのCGIは、データベースを使った簡単なものにしました。
/cgi-bin/index.cgi にアクセスすると、「ただいまのラッキーにゃんこは〜です☆」と表示します。
〜の部分にはデータベースCatsDBのCatColorsに保存されているレコードからランダムにひとつ取得したものを表示します。
構成
CGIはapache httpdのmod_cgidによって動いています。
また、データベースはMariaDBを使っています。
Dockerに載せる
Dockerfileに必要なパッケージを入れたり、設定をしたりする手順を書き、それをを元にマシンのイメージを作成します。
Perl Dockerイメージ
perl cgiを動かすコンテナのDockerイメージを作ります。
配布されている公式CentOSのイメージに、CGIを動かしていたサーバにインストールしていたパッケージを追加したり、設定ファイルをコピーしたりしています。
実際のファイルはこちらをみてください。
systemd
Webサーバを通常のサーバと同様に、systemdを使って立ち上げることにします。ですが、公式イメージをそのまま使うだけでは権限が足りずにできません。Docker-hubのCentOSのDescriptionの中程にしたがって、設定をしていきます。
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
Dockerfileの最後で起動するようにします。
CMD ["/usr/sbin/init"]
httpd
yumパッケージのhttpdを入れ、systemdで扱えるように登録します。
RUN yum install -y httpd httpd-devel
RUN systemctl enable httpd.service
CGIのファイルをコンテナにコピーします。
COPY httpd/conf.d/01-cgi.conf /etc/httpd/conf.d
EXPOSEで、80番ポートをコンテナがリッスンできるようにします。
EXPOSE 80
Dockerイメージの作成
Dockerfileが出来上がったら、Dockerイメージを作成します。
$ docker build -t ken1flan/perl_test_on_circleci .
MariaDB
準備するのが億劫だったので、CircleCIのパッケージを利用してしまいます。
dockerhubの公式パッケージでもよさそう。
Docker Compose
複数のコンテナに分けているので、素のDockerだと起動が面倒なので、
開発時はdocker-composeで管理を簡単にできるようにします。
実際のファイルはこちらをみてください。
perlコンテナ
Dockerイメージは先の手順で作ったものを使います。
perl:
image: ken1flan/perl_test_on_circleci:latest
ホストの8080番へのアクセスをコンテナの80に接続します。
ports:
- "8080:80"
ホストのカレントディレクトリをコンテナの/code
にマウントします。
ホストのapp
ディレクトリをコンテナの/var/www
にマウントします。
volumes:
- .:/code
- ./app:/var/www
mariadbと名前で通信できるようにしたり、mariadbコンテナから起動してからperlコンテナが起動するようにします。
depends_on:
- mariadb
systemctlが使えるようにします。
privileged: true
mariadbコンテナ
Dockerイメージだけ指定しています。他の設定はしていません。
mariadb:
image: circleci/mariadb:10.3
起動
$ docker-compose up -d
WARNING: Found orphan containers (perl_test_on_circleci_db_1, perl_test_on_circleci_mysql_1) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
Starting perl_test_on_circleci_mariadb_1 ... done
Starting perl_test_on_circleci_perl_1 ... done
$
$ docker-compose ps
Name Command State Ports
--------------------------------------------------------------------------------
perl_test_on_circleci_m docker-entrypoint.sh Up 3306/tcp
ariadb_1 mysqld
perl_test_on_circleci_p /usr/sbin/init Up 0.0.0.0:8080->80/tcp
erl_1
$
セットアップ
Dockerイメージからコンテナを立ち上げただけでは何もデータが入っていないので、入れていきます。
Perlコンテナでbashを実行し…
$ docker-compose exec perl bash
mysqlコマンドでデータベース作成、テーブル作成、初期データ投入を行います。
# cd /code
# mysql -u root -h mariadb < createtables.sql
動作確認
下記にアクセスして無事にページが表示されればOKです。
http://localhost:8080/cgi-bin/index.cgi
E2Eテスト
ブラウザを使ってCGIの動作をテストします。
Seleniumを使えば、クロスブラウザでも可能ですが、今回はChromeを使いました。
Chromeとlibpngのインストール
PerlコンテナにChromeとスクリーンショットを取るために使うlibpngのパッケージを入れます。
RUN yum install -y libpng libpng-devel
COPY yum.repos.d/google-chrome.repo /etc/yum.repos.d
RUN yum install -y google-chrome-stable
テストスクリプト
WWW::Mechanize::Chromeを使って、http://127.0.0.1/cgi-bin/index.cgi
にアクセスし、表示されている文言を確認します。
実際のファイルはこちらをみてください。
E2Eテストはブラウザを使って行いますが、WWW::Mechanize::Chrome
を使っています。
このモジュールはSeleniumと違い、直接Chromeを操作するので、手軽でよいと思い、選択しました。
use Log::Log4perl qw(:easy);
use WWW::Mechanize::Chrome;
実際のページを取得する前に、起動しています。
このときに画面を使わないheadless
を指定しています。今回作ったDockerイメージはWindowマネージャのようなものが一切入れていないので、この指定がないと動きません。
my $mech = WWW::Mechanize::Chrome->new(headless=> 1);
起動したら、get
にURLを指定するだけで、HTMLが取得できます。
その内容をチェックすることでテストできます。
$mech->get('http://127.0.0.1/cgi-bin/index.cgi');
my $content = $mech->content;
ok( $content =~ /ただいまのラッキーにゃんこは(茶トラ|サバトラ|キジトラ|ヒョウ柄|三毛|ブチ|サビ|シャム|黒|白|灰)です☆/ );
テストコードには入れませんでしたが、content_as_png
でスクリーンショットを保存することもできます。
my $png = $mech->content_as_png();
CircleCI
ここまでで、Docker上でCGIのE2Eテストができましたので、CircleCI上に持っていきたいと思います。
実際のファイルはこちらをみてください。
Dockerイメージの指定
CircleCIの公式Dockerイメージでは当然動かないので、自分で設定したDockerイメージを使う必要があります。
CircleCIではDocker HubにあるDockerイメージであれば利用することができるので、自前のDockerイメージをDocker Hubに登録し、それを指定します。
executors:
my-executor:
docker:
- image: ken1flan/perl_test_on_circleci:latest
- image: mariadb:10.3
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
作業ディレクトリの指定
プロジェクトのファイルは指定しないと、/root/project
に配置されますが、その位置だとサーバの設定をいろいろ変える必要がでてきてしまいます。
開発環境と同じように/code
以下にファイルを置くように.circleci/config.ymlで設定しています。
working_directory: /code
mariadbの起動待ち
データベースの準備が整わないうちにテストが始まると、失敗してしまいます。
ですので、MariaDBの接続用ポートを監視して、通信できることを
Dockerfile中でncatをインストールしておいて…
RUN yum install -y nmap-ncat
.circleci/config.ymlで、ncatで3306ポートを監視することで、mariadbの起動を待つようにしています。
- run:
name: waiting for mariadb to be ready
command: |
for i in `seq 10`; do
nc -z 127.0.0.1 3306 && echo Success && exit 0
echo -n .
sleep 1
done
echo Failed waiging for mariadb to be ready && exit 1
httpdの起動
- run:
name: setup httpd
command: |
rm -rf /var/www
ln -s /code/app /var/www
APP_ENV=test httpd
テスト
- run:
name: e2e_test
command: APP_ENV=test carton exec perl test/e2e_test.pl
できた!
32bitは?
古いアプリケーションだと32bitのOS上で動いていることが多いと思います。
このことについてはまだ考察が足りていません。
最後に
なんだかんだと、ちゃんとDocker上でPerl CGIを動かし、CircleCI上でE2Eテストもできるようになりました。
これで、古いアプリケーションのメンテナンスを頼まれても、ちゃんとテストを書きながらやれますね!