続編の記事を書きました、本記事内のセットアップ手順が自動化されています
git-flowを利用したAnsibleロールのテスト駆動開発の流れ
ユニットテストのしやすいAnsible
ロールのの書き方からTravis CI
に載せるまで解説します。本稿に書いてある方法は最近思いついたのですが、複数のテストケースに対応し、ロール単体で開発ができるため大変重宝しています。
Note
Ansible
自体のインストールは別の記事に譲ります。$ pip install ansible
sample
ロールの作成
Note
本稿のサンプルは以下のURLより入手出来ます
https://github.com/tumf/ansible-unit-test-sample
例としてansible-galaxy
コマンドでsample
ロールの雛形を作ります。
$ ansible-galaxy init sample
以下の様なファイルが生成されます。
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
└── vars
└── main.yml
テストの準備
ここにtests
ディレクトリを作成してここにユニットテストをするためのplaybook等を準備します。playbookはtest.yml
という名前にして以下のように書きます。
---
- hosts: 127.0.0.1
connection: local
tags:
- case-1
vars:
ansible_unit_test: True
roles:
- role: ../..
まず、最初の2行...
- hosts: 127.0.0.1
connection: local
この部分はテストをローカルで動かす為のお約束です。以下ポイントとなる部分は2つです。
ポイント1 プレイブックにcase-1
というタグをつけます。
tags:
- case-1
これがテストケース名になります。(case-1は例です分かりやすい名前をつけて下さい)
ポイント2 ansible_unit_test
の変数を設定します。
vars:
ansible_unit_test: True
これはテスト時にスキップしたいタスクにwhen: is not ansible_unit_test
をつけて実行しないために用意しました。
次に、作成するロールがほかのロールに依存している場合、tests/requirements.yml
に以下のように書いて...
- src: tumf.systemd-service
ansible-galaxy
コマンドでインストールします。
$ ansible-galaxy install -r tests/requirements.yml -p tests/roles
テストしやすいロールの書き方
ロールをテストしやすくするために、出力パスの装飾とスキップタスクの指定を行います。
出力パスの装飾
ロールのタスクをtests/cases/テストケース名
以下にインストールするように作成します。prefix_dir
という変数を作って、ロールの中のファイルパスを全て修飾します。
まず、ロールのdefaultに以下の様に書いておき...
prefix_dir: ""
templateのdest等で出力先のパスの頭にprefix_dir
を置きます。
- template: src="default.j2" dest="{{ prefix_dir }}/etc/default/sample.j2"
notify: reload systemd
スキップタスクの指定
テスト時に本番でないと実施出来ないコマンドなどスキップしたいタスクにwhen: is not ansible_unit_test
をつけて実行しないために用意しました。ansible-playbook
時に-C
つければいいかと思ったのですが、実行環境によってモジュールが読み込まれないことがあります(私は、本番Linuxに対しOSX上でテストを回しています)。
以下のように when: not ansible_unit_test
をつけていきます。
- service: name="sample" state=started enabled=yes
when: not ansible_unit_test
実際本番に対して利用する時のためにデフォルトでansible_unit_test
をFalse
に設定します。
ansible_unit_test: False
テストの実行スクリプト
次に、テスト実行用のスクリプトtests/run
を用意します。
#!/bin/bash
usage_exit() {
echo "Usage: $0 [-w] name" 1>&2
exit 1
}
check="-C"
while getopts wh option
do
case $option in
w)
check=""
;;
h)
usage_exit
;;
esac
done
shift $((OPTIND - 1))
mkdir -p tests/cases
cases=$(ls tests/cases)
if [ ! -z $1 ];then
cases=$1
fi
errors=0
for case in $cases
do
out=$(ansible-playbook ./tests/test.yml -i 127.0.0.1, -t $case -D $check -e prefix_dir="cases/${case}")
result=$?
if echo $out|tail -n 1 |grep -E "changed=0\s+unreachable=0\s+failed=0" >/dev/null
then
echo -n "."
else
echo $case
echo
echo "$out"
errors=$(( errors+1 ))
fi
done
if [ $errors -eq 0 ]
then
echo " ok"
else
echo "${errors} error(s)"
fi
exit $errors
テストの実行
テストは以下のようにして実行します。
$ ./tests/run case-1
case-1
PLAY [127.0.0.1]
GATHERING FACTS ***************************************************************
ok: [127.0.0.1]
TASK: [../.. | template src="default.j2" dest="{{ prefix_dir }}/etc/default/sample"] ***
--- before: cases/case-1/etc/default/sample
+++ after: /Users/tumf/tmp/sample/templates/default.j2
@@ -0,0 +1 @@
+default test
changed: [127.0.0.1]
TASK: [../.. | service name="sample" state=started enabled=yes] ***************
skipping: [127.0.0.1]
NOTIFIED: [../.. | reload systemd] ********************************************
skipping: [127.0.0.1]
PLAY RECAP ********************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=0
1 error(s)
case-1
はtests/test.yml
でtags
に指定したテストケースです。この段階で最後のエラーは気にしないでください。上の出力を見て正しく生成されているか確認してください。正しく生成されるまでプレイブックの修正を行います。
##テストケースを作成する。
テストケースcase-1
が生成するファイルtests/cases/case-1/etc/default/sample
を正しいケースとしてテストケースに登録します。以下のように実行します。
$ ./tests/run -w case-1
(略)
正しく実行できれば(できるはずですが)tests/cases/case-1/etc/default/sample
が実際に書き込まれます。次回より以下のようにしてテストを実行します。
$ ./tests/run case-1
. ok
Note ケース名を省略すると、
tests/cases
以下をスキャンして全て実行します。通常はこちらのほうが便利です。$ ./tests/run . ok
テストケースを増やす
あとはテストケースを必要に応じて増やしていきます。
以下はvar
を変えただけの単純な例です。
---
- hosts: 127.0.0.1
connection: local
tags:
- case-1
vars:
ansible_unit_test: True
var: var of case-1
roles:
- role: ../..
- hosts: 127.0.0.1
connection: local
tags:
- case-2
vars:
ansible_unit_test: True
var: var of case-2
roles:
- role: ../..
以下のコマンドを実行し...
$ ./tests/run case-2
正常に動いたら、テストケースとして登録します。
$ ./tests/run -w case-2
以後は、以下のコマンドでcase-1``case-2
同時にチェックします。
$ ./tests/run
.. ok
簡単ですね。
テストでエラーを発生させる
case-1
のテストケースが登録されている状態で、var
を変えるとテストケースとの間に差分が出るはずです。この差分が検出されるとエラーとなります。
プレイブックを以下のように修正します。
vars:
ansible_unit_test: True
var: var of case-one # ここを変えた
テストを実行します。すると以下のようにエラーが報告されます。
$ ./tests/run
case-1
PLAY [127.0.0.1] **************************************************************
GATHERING FACTS ***************************************************************
ok: [127.0.0.1]
TASK: [../.. | file state="directory" path="{{ prefix_dir }}/etc/default"] ****
ok: [127.0.0.1]
TASK: [../.. | template src="default.j2" dest="{{ prefix_dir }}/etc/default/sample"] ***
--- before: cases/case-1/etc/default/sample
+++ after: /Users/tumf/tmp/sample/templates/default.j2
@@ -1 +1 @@
-default test var of case-1
+default test var of case-one
changed: [127.0.0.1]
TASK: [../.. | service name="sample" state=started enabled=yes] ***************
skipping: [127.0.0.1]
NOTIFIED: [../.. | reload systemd] ********************************************
skipping: [127.0.0.1]
PLAY RECAP ********************************************************************
127.0.0.1 : ok=3 changed=1 unreachable=0 failed=0
.1 error(s)
この差分が意図通りであれば./tests/run -w case-1
で新たなテストケースを書き込んで下さい。間違いであれば、ロールを修正して下さい。
このような感じでテストを繰り返しつつプレイブックを意図通りに完成させていきます。
Travis CI
ここまでやると、CIを利用した自動テストを実行したくなると思います。
Travis CI
ならば.travis-ci.yml
を以下のように書くだけです。
language: python
python:
- '2.7'
install:
- pip install ansible
#- ansible-galaxy install -r tests/requirements.yml -p tests/roles
before_script:
- ansible --version
- ansible-playbook --syntax-check ./tests/test.yml -i ./tests/hosts
script:
- ./tests/run
以下の部分がコメントアウトされています。
#- ansible-galaxy install -r tests/requirements.yml -p tests/roles
このロールが他のロールに依存していない時すなわちtests/requirements.yml
が空になると、ansible-galaxy
がエラーになるため、コメントアウトしています。依存するロールがあり、tests/requirements.yml
を書いた場合はコメントアウトを外して下さい。
以上
以下の記事は大変参考になりました、ありがとうございます!
TravisCIでAnsibleのrole単体のテストを実行する
http://qiita.com/djyugg/items/627aa88e02422612f164