从Ansible角色的单元测试到Travis CI
我写了续篇文章,本文中的设置步骤已经自动化。
这是使用git-flow的Ansible角色进行测试驱动开发的流程。
我将解释如何编写易于进行单元测试的Ansible角色,并将其上传到Travis CI。本文中提到的方法是我最近想到的,它可以处理多个测试用例,并且非常方便地在角色单独开发。
注意:
Ansible的安装请参考另一篇文章。$ pip install ansible.
创建示例卷
注意
可从以下网址获取本文的示例样本
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: ../..
首先,先看前两行…
- hosts: 127.0.0.1
connection: local
这个部分是为了在本地测试而设定的约定。以下是两个关键点。
1. 在Playbook中添加一个名为case-1的标签。
tags:
- case-1
這將是測試案例的名稱。(案例1僅為示例,請給予易於理解的名稱)
设定ansible_unit_test的变量。
vars:
ansible_unit_test: True
這是為了在測試時跳過想要跳過的任務而準備的,只需在when條件中加入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的变量,并将角色中的文件路径全部加上修饰。
首先,将以下内容写在默认的角色上…
prefix_dir: ""
在模板的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”,但在不同的执行环境中,有时会导致模块无法加载(因为我在OSX上对生产Linux进行测试)。
我們將在下方加上 `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文件中通过标签指定的测试用例。在这个阶段,请不要关心最后的错误。请查看上面的输出以确认是否正确生成。只有在正确生成之前,我们才会对播放手册进行修正。
创建测试用例。
将由测试用例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
注意:如果省略案例名称,则会扫描tests/cases文件夹并执行所有案例。通常情况下,这更方便。
$ ./tests/run
. 通过
增加测试用例
在需要的情况下,我们会增加测试用例。
以下是一个简单的例子,只是改变了变量”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