Docker中基于Robot Framework的UI自动化测试实战


前言

在当前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()
最近的三年多时间,随着容器技术的火爆及Kubernetes成为容器编排管理的标准,国内外厂商均已开始了全面拥抱Kubernetes的转型, 无数中小型企业已经落地 Kubernetes,或正走在容器化的道路上 。 第一章介绍docker的前世今生,了 解docker的实现原理,以Django项目为例,教大家如何编写最佳的Dockerfile实现构业务镜像的制作。通过本章的学习,大家会知道docker的概念及基本操作,并学会构建自己的业务镜像,并通过抓包的方式掌握Docker最常用的bridge网络模式的通信。 第二章本章学习kubernetes的架构及工作流程,重点介绍如本章学习kubernetes的架构及工作流程,重点介绍如断的滚动更新,通过服务发现来实现集群内部的服务间访问,并通过ingress- -nginx实现外部使用域名访问集群内部的服务。同时介绍基于EFK如何搭建Kubernetes集群的日志收集系统。学完本章,我们的Django demo项目已经可以运行在k8s集群中,同时我们可以使用域名进行服务的访问。第三章本章基于k8s集群部署gitlab、sonarQube、 Jenkins等工具,并把上述工具集成到Jenkins中,以Django项目为例,通过多分支流水线及Jenkinsfle实现项目代码提交到不同的仓库分支,实现自动代码扫描、单元测试、docker容器构建、k8s服务的自动部署。第四章由于公司内部项目众多,大量的项目使用同一套流程做CICD,那么势必会存在大量的重复代码,因此本章主要通过使用groovy实现Jenkins的sharedL ibrary的开发,以提取项目在CICD实践过程中的公共逻辑,提供一系列的流程的接口供公司内各项目调用,开发完成后,还是以Django的demo项目为例,进行Jenkinsfle的改造,最后仅需通过简单的Jenkinsfle的配置,即可优雅的完成CICD流程的整个过程,此方式已在大型企业内部落地应用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值