文章目录
前言
在当前SaaS和容器技术大火的今天,有必要讲一下在容器中,是如何基于Robor Framework进行自动化测试的。写这边文章的主要目的倒不仅仅是因为容器技术太火,最主要的还是因为当前的很多业务是SaaS的形态,并且不止有一两个人问过我如何在容器中进行自动化测试。所以这篇文章主要分享一下我的一些基本做法,可能各位业界大咖有更加优化和便捷的方式,欢迎切磋交流。
1. 涉及内容
本次实战中不涉及镜像将来的运行环境,仅仅涉及定义的docker file内容,以及相关的要点。主要有以下相关内容组成。
(1)定义一个docker image
(2)编写自动化测试用例
(3)执行自动化测试
(4)测试报告分析
(5)发送结果邮件
2. 定义一个docker image
2.1 docker file
因为是基于Robot Framework的自动化测试框架,基本的运行环境主要是python,这里选用python:3.7.7-slim-buster作为基础镜像。下面的代码是整个docker file的内容,这里不细说,不懂的自己百度或者私聊我吧。
FROM python:3.7.7-slim-buster AS aws
WORKDIR /home/python/automation
COPY ./src/requirements.txt ./
COPY ./src/ .
RUN python3 -m pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt && apt-get update && apt-get install -fy curl wget unzip zip msmtp mutt fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libdrm2 libgbm1 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libx11-6 libx11-xcb1 libxcb-dri3-0 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 xdg-utils && dpkg -i google-chrome-stable_amd64.deb && rm -rf /var/lib/apt/lists/* && unzip chromedriver_linux64.zip && mv -f chromedriver /usr/local/share/chromedriver && ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver && rm -rf google-chrome-stable_amd64.deb chromedriver_linux64.zip && mv msmtprc ~/.msmtprc && mv muttrc ~/.muttrc && chmod 0600 ~/.msmtprc
CMD sh shell_me.sh
这里简单说一下,docker file中主要定义了基础镜像,工作目录,需要安装的依赖和配置,以及镜像运行的时候执行的命令(最后一行)。
2.2 src目录
接下来看一下我在src目录下放了什么,首先说明一下,这里不一定非得叫src,随便一个名字都可以。下图是src目录的整个结构。
automation_result目录,这个目录在生成的docker image中会是空的,将来用于存储测试报告,测试截屏等文件。图中只是为了展示将来里面可能存放的文件内容。
01.UI_TEST.txt文件,这是测试用例文件,定义了将来需要执行的所有测试用例,以及官方库,自定义库,用户关键字等的引用,具体内容如下图。
chromedriver_linux64.zip文件和google-chrome-stable_amd64.deb文件,google的chromedriver文件和chrome安装文件,防止被墙了,下载到本地。
msmtprc和muttrc文件,msmtp和mutt模块的配置文件,用于发送通知邮件,正常情况下在~/目录下,内容如下图。
my_library.py文件,自定义的方法库,在测试用例中引用。
my_variables.py文件,测试数据定义文件,以key=value的格式定义的python文件。
report_analyze.py文件,用于解析robot framework测试报告,并发送测试结果邮件的脚本。
requirements.txt文件,定义了本次实战用需要用的python库,如下图。
shell_me.sh文件,定义了镜像运行时,需要执行的shell命令,如下图。
这里执行的命令很简单,先执行自动化测试,然后分析测试报告并发出测试结果通知邮件,把代码贴出来。
#!/bin/bash
robot -V my_variables.py --extension txt -l automation_result/log -r automation_result/report --output automation_result/output.xml 01.UI_TEST.txt
python3 report_analyze.py
3. 编写自动化用例
既然是实战,编写UI自动化测试用例这里就多塞入一点知识,这里包含的知识有:参数文件的定义与引用;浏览器设置与浏览器参数设置;自定义关键字库,并引用;定义用户关键字并引用;测试过程中界面截图。
3.1 测试参数文件定义
在产品测试过程中,自动化测试用例往往并不是执行一两次就完事儿了,这样的话也失去了做自动化测试的意义。同一个测试用例,不仅仅需要多次重复执行,还需要在不同的环境中执行,比如频繁构建的开发环境要执行,试商用的Staging环境要执行,生产环境中为了监控的目的或者有了新的部署也需要执行。不管怎么说,为不同的测试环境各准备一套测试用例显然不是大家期望的方式。这就用到了测试用例与测试数据分离的思想,同一套测试用例,在不同的环境上运行的时候,加载不同的测试数据,这样也更加便于后期的维护。本节主要讲述如何定义一个简单的测试参数文件,并在测试用例中引用这个测试参数文件。
如下图,我们定义了一个名为my_variables.py的python文件,这是一个非常简单的测试参数文件:定义了一个url的地址,指向本人的CSDN博客;定义了浏览器类型为Chrome;并为浏览器定义了一些参数选项,本节不详细解释。
参数文件定义好了,下一步我们看看怎么引用参数文件。
第一种方式是在RIDE中引用,在suite设置页面中点击Variables按钮,通过文件浏览方式指定对应的参数文件就可以了。这种方式多用于本地执行用例或者调测用例的时候。在测试Suite中添加Variables参数数,并选择定义好的参数文件即可,如下图。
第二种方式是在执行测试用例的时候引用参数文件,这种方式比较适合在CICD中使用。
3.2 浏览器设置与浏览器参数设置
有时候测试用例需要在不同的浏览器上执行,因此吧浏览器类型定义成参数,便于将来用例执行的时候能够随意切换,这里仅仅以Chrome浏览器举例。如下图,定义一个名为browser的参数,参数值设置为Chrome。
browser = “Chrome”
这样子,在编写测试用例的时候,只要写浏览器类型是
b
r
o
w
s
e
r
即
可
。
当
执
行
测
试
用
例
的
时
候
,
会
自
动
将
{browser}即可。当执行测试用例的时候,会自动将
browser即可。当执行测试用例的时候,会自动将{browser}替换成定义参数变量browser设置的浏览器类型。
测试用例代码如下:
Open Browser ${url} ${browser}
最终执行的时候,会被替换成正确的值。
INFO : Opening browser 'Chrome' to base url 'https://blog.csdn.net/lipeixinglive'.
这里重点讲述一下Chrome的两个参数选项:headless和window-size。 headless一般会配合–no-sandbox参数一起使用,headless选项最主要的作用是执行WEB UI测试的时候,浏览器在后台运行,不会在屏幕上显示浏览器。 这样极大的便利了以linux为主要服务器的CICD场景中进行WEB UI的自动化测试任务。windows-size参数主要用于设置chrome打开是的页面分辨率,这能够提升UI测试中控件提取的成功率,进而提升WEB UI测试任务的稳定性, 因为linux下,一般情况下默认执行分辨率不高,可能存在需要拾取的页面控件不在显示范围的情况。其实Chrome可供设置的选项很多,大家可以自行查阅chrome的官方文档说明。
3.3 自定义方法库并引用
在我们上面提到的src目录中的my_library.py文件中,定义了一个非常简单的方法,获取一个uuid4标准的uuid。
import uuid
def getGUID():
guid = uuid.uuid4()
return str(guid)
if __name__ == '__main__':
print(getGUID)
在测试用例中引用的时候也很简单。
第一种方式是,在RIDE中的suite设置页面,点击Library按钮,通过文件浏览的方式指定对应的库文件就可以了。
第二种方式是,直接在测试用例文件中,以文本方式指定。
本质上来讲,第二种方式是第一种方式的呈现。因为通过RIDE指定的库或者参数文件等,最后都是反应到测试用例文件中的*** Settings ***下面
这样以来,自己定义的方法库就引入到测试用例文件中了,那应该怎么在设计测试用例的时候调用自定义库中的方法呢?其实自定义方法的调用,和官方库中的方法调用是一样的,方法名加参数的方式。
上面代码是本次实战中的第二个测试用例,其中高亮的行就是直接引用了自定义库中的getGUID方法,并且以log的方式打印出返回的GUID,这个方法没有入参,如果有入参的话,顺序跟在后面就可以了,跟Robot Framework官方库的方法引用完全一致。
测试用例里面这两行步骤的执行结果如下。
20210316 10:12:33.564 : INFO : ${my_guid} = d8122228-0092-4a69-8d3e-ba2fc56e4a47
20210316 10:12:33.565 : INFO : my guid is : d8122228-0092-4a69-8d3e-ba2fc56e4a47
3.4 定义用户关键字并引用
本次实战中,我们直接把第二个测试用例的步骤定义成了用户关键字,然后在第三个用例中引用用户关键字。
从下图可以看出,测试用例002和测试用例003的执行结果完全一样。
3.5 测试过程中界面截图
测试过程中的截图操作,主要包含两种情况,可以叫做主动截图和被动截图。
对于主动截图,是测试过程中,在特定的位置进行截图,用于将来的分析或者比对等相关工作。在selenium中,通过“Capture Page Screenshot”关键字即可完成,这个方法只有一个入参,截图的保存路径和文件名。
Capture Page Screenshot sdn_homepage001.png # 主动截屏
对于被动截图,其实这个是selenium本身的功能,测试执行错误的时候,默认会自动截图并保存在测试报告存放目录下,并且可以在测试报告中直接查看,名称一般都是selenium-screenshot-id.png。举个例子,下面是我们第一个测试用例的代码。
001_Open My CSDN Blog_fail
[Tags] develop staging production #定义了测试用例的tag,便于筛选执行测试用例
# 通过参数文件中设置的参数打开 CSDN 主页
Open Browser ${url} ${browser} options=${browser_options}
Wait Until Element Is Visible //*[@id="floor-user-profile_485"]/div/div[1]/div[2]/div[3]/ul/li[3]/a timeout=50 #等待控件出现,最多等待50s
Page Should Contain FKFKFKFKFKFKFKFK # 页面中肯定不存在FKFKFKFKFKFKFKFK,因此用例执行失败
Close Browser
[Teardown]
这个用例一定会执行错误,因为页面中不存在“FKFKFKFKFKFKFKFK”,这个时候会触发selenium的自动截图操作。
这个用例的测试报告如下,从测试报告中可以看出,当测试用例执行报错的时候,selenium也是调用的“Capture Page Screenshot”方法进行截图操作,并且图片会自动关联到测试报告中展示。
4. 执行自动化测试用例
细心的朋友可能已经发现了,前面定义的docker file的最后一行命令中对应的脚本shell_me.sh中的第一行就是在docker中执行自动化测试用例的命令。跟在docker外面执行,本质上是没有什么区别的。
CMD robot -V my_variables.py --extension txt -l automation_result/log -r automation_result/report --output automation_result/output.xml 01.UI_TEST.txt
这里面通过-V指定了测试参数文件,通过–extension指定了测试用例文件类型,通过-l/-r/–output指定了测试报告存放路径。
这里的定义很简单。镜像拉起来以后就会去自动执行测试。其实这样是不科学的,理想情况下,应该是通过jenkins定义并触发测试用例的执行,或者定义成cronjob的方式定时执行测试用例,等将来有空的时候再补充这一块。
5. 测试报告分析
其实这一块重要性或者迫切性没有那么强。主要目的是,解析一下测试报告,如果测试用例有fail的话,在邮件正文中展示错误用例列表。这样读邮件的人不必解压附件查看哪些用例错了,通过邮件body就能知道大概。
基本的原理,就是通过python下的xml.etree库解析output.xml文件,对用例执行情况进行统计,组装失败用例列表。
6. 发送测试结果通知邮件
这里发送测试结果通知邮件是通过mutt完成的,mutt是linux下优秀的命令行邮件处理工具。因为是在dock中使用,在前面docker file中定义了安装mutt和msmtp的过程,并且把预先配置好的配置文件muttrc和msmtprc拷贝到了对应的目录下。
这样,在真正需要发邮件的时候,直接运行shell命令即可,下面的shell中需要把变量标识的邮件body,邮件subject,收件人等替换掉。
echo "${mail_body}" | mutt -s "${mail_subject}" ${mail_receiver} -a my_attachment
收到的邮件效果如下。
7. 测试报告分析源代码
最后,作为福利,把测试报告分析的源代码共享出来,写的不够精炼,但是可用。
# coding=utf-8
import sys
import subprocess
# 引用xml.etree库
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
def AnalyzeReport():
# 读取测试报告xml文件
my_tree = ET.ElementTree(file='./automation_result/output.xml')
root = my_tree.getroot()
fail_list = ""
totalcase = 0
passcase = 0
failcase = 0
suite_statistic = ""
for child in root:
case_name = ''
case_status = ''
# 分析suite标签下的内容
if child.tag == 'suite':
for elem in child.iter():
if elem.tag == 'test':
for sub_elem in elem.iter():
if sub_elem.tag == 'status':
case_status = sub_elem.attrib['status']
# 如果用例状态是FAIL,把用例名称添加到错误用例列表fail_list
if case_status == "FAIL":
case_name = "\n" + elem.attrib['name'] + "-----"
case_status = case_status + "\n"
# fail_list = fail_list + ''' <br /> ''' + case_name + case_status + ''' <br /> '''
fail_list = fail_list + case_name + case_status
# 分析statistics标签下的内容
if child.tag == 'statistics':
for elem in child.iter():
if elem.tag == 'total':
for sub_elem in elem.iter():
if sub_elem.tag == 'stat' and sub_elem.text == 'All Tests':
totalcase = int(sub_elem.attrib['pass']) + int(sub_elem.attrib['fail'])
passcase = sub_elem.attrib['pass']
failcase = sub_elem.attrib['fail']
elif elem.tag == 'suite':
for sub_elem in elem.iter():
if sub_elem.tag == 'stat':
if sub_elem.attrib['fail'] == "0":
suite_statistic = suite_statistic + ''' <br /> ''' + sub_elem.attrib['name'].ljust(50,
"-") + "--PASS RATE--" + str(
'{:.2%}'.format(int(sub_elem.attrib['pass']) / (
int(sub_elem.attrib['pass']) + int(sub_elem.attrib['fail'])))) + ''' <br /> '''
else:
suite_statistic = suite_statistic + ''' <br /> ''' + sub_elem.attrib['name'].ljust(50,
"-") + "--PASS RATE--" + str(
'{:.2%}'.format(int(sub_elem.attrib['pass']) / (int(sub_elem.attrib['pass']) + int(
sub_elem.attrib['fail'])))) + ''' <br /> '''
# 计算成功率
fail_rate = (int(failcase)/int(totalcase))*100
pass_rate = round(100 - fail_rate, 2)
print("pass rate : " + str(pass_rate))
# 如果不是100%成功,执行shell命令发送通知邮件
if pass_rate < 100:
if pass_rate != 100:
shell_status, shell_result = subprocess.getstatusoutput('rm -rf automation_result.zip && zip -r automation_result.zip ./automation_result/*')
mail_body = 'Hi,\n Your automation has failure. The fail list is as follows:\n ' + str(fail_list) + '\n And you can also get the details in the attachment.\n Good Luck!!\nBest Regards,\nYour Automation'
my_shell = 'echo "' + mail_body + '" | mutt -s "Automation FAIL!!" billson_li@trendmicro.com -a automation_result.zip'
shell_status, shell_result = subprocess.getstatusoutput(my_shell)
if __name__ == "__main__":
AnalyzeReport()