最近这段时间对内部的一个配置平台实现了 UI 自动化,过程中经常上 testerhome 寻找一些灵感和帮助,最后用到的一些技术比如 docker、behave、phantomjs 等等目前论坛上的资料相对较少,所以特意记录下思路,希望可以给其他同学一点启发。

UI 自动化框架的选择

在之前做过的一个 Android 自动化项目中选用了 calabash,很喜欢 BDD 的风格,函数库够多的时候写起自动化来就像是把用例的中文翻译成英语,so easy~

但是也是之前使用 calabash 的经历发现 ruby 的库实在是不够丰富,虽然就语言本身更喜欢 ruby 一些,没办法,为了没那么多幺蛾子这次还是换成 python 吧。。。
python 的 BDD 框架并不多,比较出名的是 behave 和 lettuce,对比过后选择了 behave。

好吧,其实没有真正对比试用过,就是被 behave 主页上对其他工具的恶毒攻击洗脑了~~
http://pythonhosted.org/behave/comparison.html

与 Phantomjs 的集成

简单来讲 phantomjs 是一个没有 UI 的浏览器,可以与 selenium webdriver 完美集成。
为什么要选用它:

  1. 快,没有 GUI 的浏览器比起 chrome,firefox 这些 webdriver 来执行速度要快很多
  2. 要测试的是内部的配置平台,没有那么多花哨的 js,css,更加注重功能,phantomjs 足够使用
  3. 就是想在 linux 下干活,但是我的 server 并没有 UI。。。

behave 中使用 phantomjis

from behave import *
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

def before_scenario(context,scenario):
    context.dr = webdriver.PhantomJS('phantomjs',service_args=['--ignore-ssl-errors=yes'])
    context.dr.set_window_size(1360, 900)
    ...
from behave import *
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.alert import Alert

@given('I open test portal')
def step_impl(context):
    context.dr.get(context.index)
    assert_that(context.dr.current_url,matches_regexp('7030/cas/login'),"not auth page")
    ...

behave 用例示例

@1887204
Scenario: 1887204-3
Given I open test portal
when I input username and login
then I move to release rule page
when I reset cfp cp-center log
when I reset cfp carrier-center log
then I create new release rule "autotest" "正常订购" "1" "diy" within "180"s
| id | equal | value |
| 运营商 | = | 中国联通 |
| 套餐 | = | lt_50m_qg_suc |
| 客户账号 | = | autotest |
then I check cfp cp-center log within "30"s for "release order success.*target release count:1"
...

测试数据隔离怎么做

正常来说,到上面为止框架已经有了,后面就可以填用例了~

但是我想让自动化更加健壮一些,希望每个用例运行时都是干净的独立的环境。

这就需要我们对每次测试后的环境进行清理,而 behave 对于单个 case 并没有对应 teardown 这样一个专门清理的函数,这就使得错误发生后没有办法很好的还原环境。

最开始我们考虑在 behave 的 after_scenario() 方法中根据失败用例的 tag 进行特定的清理。
这样可以 work,但是额外的清理会浪费较多时间,而且 UI 测试哪里有 100% 的事情,清理过程中的一点问题可能就造成了之后用例的大片失败,这让我们如履薄冰。

就在这个时候 docker 进入了我们视野。
如果我们每个 case 都重启下 docker 数据源容器,每个用例的环境就能保证一致,那测试人员就只需要专注编写核心功能的自动化。

而 docker 启动一个容器很快,装有 PG,redis 的容器只需要几秒钟就能正常运行。
好 那就试试吧~

安装配置 docker

公司的测试机是 centos 系统,如果想玩 docker 过程中不出幺蛾子,建议保证系统到 centos7,内核 3.10 以上。。。
一开始的时候,我是并不信邪的,在 centos6 的系统风风火火的搞了起来,结果脸就被打肿了,各种血泪不说了。

docker 安装很简单,官网文档看看就行

安装好 docker 后,首先从官方库中下载 centos6.6 的镜像。

然后以此为基础分别配置 3 个镜像,分别是安装了 behave 的镜像,pg/redis 的镜像和安装了 application 以及其他组件的镜像。

启动 app,pg 容器,其中 pg 容器以 container 的形式附在 app 的容器内,这样做的目的是为了让两个容器的 ip 一致,这样 app 的配置文件中关于 pg,redis 的配置可以用 127.0.0.1 指定。并且这两个容器又相对独立,用例执行结束后可以 stop 掉 pg 容器,并新建一个容器附在 app 容器中。
这么做的效果跟所有组件布在同一个容器中是一样的,好处在于重启 pg,redis 比重启 app 要快,快很多~

docker run -d -v /opt/data/remote:/opt/share 172.16.154.92:5000/centos:6-cfp-apps-2.6.1
docker run -d  --net=container:{上一步产生容器的ID} 172.16.154.92:5000/centos:6-cfp-env-2.6.1

集成 celery

到上一步为止,我们大体的结构已经有了,但是我们想让它更快,更好用一点。于是我们加上了 celery。
celery 是一个比较常用的 python 异步框架,分别在 docker 机器以及 behave 容器中安装上。

我们希望自动化控制脚本通过 docker 机器中的 celery 去新建 n 个 behave 以及 app,env 的容器,并在 case 完成后重新新建 env 容器。
然后我们希望 behave 容器中的 celery 接受用例 id 和 app 容器的 ip,从而去指定的 app 页面中执行用例

behave 容器中使用 supervisor 启动 celery 并监听 behave 消息队列

[supervisord]
nodaemon=true

[program:celery]
command=bash -c "cd /root/ && celery worker -A tasks -Q behave -l info -c 1 -f /opt/share/celery%(ENV_celery)s.log"
startretries=0

docker 机器中 celery 监听 main 队列。

celery worker -A tasks -Q main -c 10 -l info

celery 的 task 脚本 tasks.py 中指定队列

from celery import Celery,platforms
import subprocess
import os,sys
import time

platforms.C_FORCE_ROOT = True
app = Celery()

app.conf.update(
    CELERY_IMPORTS = ("tasks", ),
    BROKER_URL = 'redis://172.16.154.92:9852/0',
    CELERY_RESULT_BACKEND = 'redis://172.16.154.92:9852/1',
    CELERY_ROUTES = {
        'tasks.cfp_start': {'queue': 'main'},
        'tasks.behave_init': {'queue': 'main'},
        'tasks.get_ip': {'queue': 'main'},
        'tasks.behave': {'queue': 'behave'},
        'tasks.rm_docker': {'queue': 'main'},
        'tasks.stop_docker': {'queue': 'main'},
    },
    ...
)

这样我们通过控制脚本得到需要测试的 caseid,然后启动配置个数的 app,pg,behave 容器,在检查到 app 容器 ready 后发送 caseid 和容器 ip 到 celery 队列。
然后监控 case 执行过程,在某个 case 完成后,重启该 case 对应容器的 pg 容器,完成后发送下个 caseid 和 ip 到 celery。这样分布式的测试保证了单个用例的执行环境。
运行速度也有一定的保障。
毕竟我的测试机可是 24 核 24g 的机器。。。 运行过程中整机的 cpu 有时突破 90%,也算是资源没有浪费了~~

结果处理

因为每次 behave 都只是执行一个 case,因此生成的 junit 报告也只有一个 case 的信息。因此需要做报告合并。
不想重复造轮子搜索了下, 通过两个现有的模块 xunitparser,junit_xml 愉快搞定。生成一个大的 junit 报告

然后就是放在 jenkins,用插件展示了。

用例调试

之所以用例调试要专门拿来说下,是因为使用 docker 容器创建的容器都是私有 ip,不能直接访问。
docker 倒是可以将容器中的端口映射一个实体机的端口,但这个并不十分好用,因为我们 app 的配置文件里面需要配置一些跳转的 url,而这些跳转的 urlip 又是私有的或者干脆是 127.0.0.1 的。。。 于是我们新建了一个 squid 的容器跟 app 容器在一个网段,然后暴露代理端口给我们,这样浏览器设下代理就 ok 了。

然后就是用例运行中的截图,phantom 本来就是没有界面的嘛,而且又是多个用例异步的执行,这要怎么调试呢。
也很简单,behave 中通过 after_step() 方法在每个用例每个步骤执行后都可以 screenshot,保存在用例的目录。该目录 mount 在 docker 机器中。
然后启动一个 nginx 容器,设置好截图目录,就可以直接查看啦~~


↙↙↙阅读原文可查看相关链接,并与作者交流