14
08

“蜗牛进销存”项目的接口测试框架整合

自动化测试框架以设计思想为指导,包含了测试数据,测试用例脚本,测试工具,支撑组件等一系列集合,并能与管理流程和测试流程相适应。而一套好的测试框架不仅要满足高重用性和高维护性,还要兼顾团队协作等方面。

注:本节教材内容,与上前三周推送的文章为一个项目实战内容。

回顾上节内容,请点击
教材连载:蜗牛进销存项目实战(一)
教材连载:利用Requests库完成“蜗牛进销存”的登录功能

教材连载:利用Requests库完成“蜗牛进销存”的新增会员与测试



接口测试框架整合



1.框架设计


前面我们只是单独对一个功能进行测试,试想一下:如果会员新增功能的用例用成百上千条,那代码岂不是很多?又比如现在要对会员修改或商品出库的接口进行测试,代码量也要成倍的增加? 数据都直接嵌入到代码中,那维护起来会不会很麻烦?为此,我们需要设计一个相对完善的自动化测试框架来解决这些问题。


关于自动化测试框架,常见的几种解释有:

(1)测试自动化框架就是支撑自动化测试的一系列假设,概念和工具。

(2)自动化测试框架就是一个能够进行自动化测试的程序。

(3)由一个或多个自动化测试基础模块、自动化测试管理模块、自动化测试统计模块等组成的工具集合。


从广泛的角度讲,自动化测试框架以设计思想为指导,包含了测试数据,测试用例脚本,测试工具,支撑组件等一系列集合,并能与管理流程和测试流程相适应。而一套好的测试框架不仅要满足高重用性和高维护性,还要兼顾团队协作等方面。

20190320_094910_523.jpg



上图是笔者设计的自动化接口测试框架,主要依靠调度模块来处理整个流程,首先是获取到数据模块的数据,再将数据传给工具模块进行处理,最后将处理后的数据传给执行模块进行测试,并得到执行模块返回的结果。


实际上这个框架借用了数据驱动的思想。数据驱动不是单纯的一种技术,而是一种理念。通过将测试代码和测试数据的分离,让测试人员只关注测试数据本身而从繁重的测试代码中解脱出来。每当我们进行测试时,只需要更新测试数据,自动化测试会根据这些数据判断应该执行什么测试,填入什么数据,得到什么预期。这个过程完全是以数据为主导的,所以我们称之为数据驱动。

 

2.代码实现


按照前面的设计,我们在项目下创建几个文件,如下图。总共有4个文件,分别对应上述的4个模块。

(1) TestData.xlsx:excel文件,用于存放接口测试的数据。

(2) Util.py:实现了一些通用工具的功能,比如读取excel。

(3) Common.py:封装了发送GET和POST请求的功能。

(4) Control.py:调度整个测试运行。


20190320_094933_149.jpg


首先,我们在TestData.xlsx中设计好测试数据,由于上一节中设计excel中的接口测试数据时就考虑了接下来的任务,所以数据不用做出任何更改。测试数据的载体不一定非要使用excel,xml文件,数据库,甚至普通的txt文件都没有问题,当然使用excel是数据驱动最常见的方式。


20190320_094945_447.jpg


然后我们实现执行模块Common.py,构造方法借助登录操作直接获取有效的cookie值并赋给成员属性self.cookies,成员方法httpGet和httpPost分别用于发送GET和POST请求,并返回响应对象。


import requests

class Common:

    # 构造方法,实例化该类时则得到cookie

    def __init__(self):

        loginData = {'username':'boss','password':'boss123','verifycode':'1111'}

        loginRes = requests.post("http://localhost:8080/WoniuSales/user/login",data=loginData)

        self.cookies = loginRes.cookies

    # 发送GET请求

    def httpGet(self, url):

        Res = requests.get(url)

        return Res

    # 发送POST请求

    def httpPost(self, url, data):

        Res = requests.post(url, data=data, cookies=self.cookies)

        return Res


接下来继续实现工具模块Util.py。


(1)readExcel函数,实现了读取excel并返回数据的功能,这里的index参数的默认值为0,代表默认读取excel中的第一个表。注意要提前安装并导入xlrd模块。

(2)解析参数的函数parseData,可以看到在excel的参数都是“参数名=参数值”的形式,为了传递给POST请求使用,需要把它利用字符串分割的方式,解析成键值对的形式,再存放到字典中并返回。

(3)assertResult函数,获取用例的信息进行断言,并输出可读性较高的结果。


import xlrd

# 读取excel的函数,返回sheet对象

def readExcel(file_path, index = 0):

    # 打开指定路径的excel

    data = xlrd.open_workbook(file_path)

    # 获取整个sheet对象

    sheet = data.sheets()[index]

    return sheet

# 解析数据并重构

def parseData(str):

    strList = str.split('\n')

    newData = {}

    for line in strList:

        list = line.split('=')

        newData[list[0]] = list[1]

    return newData

# 判断并输出执行结果

def asertResult(rowList, actCode, actContent):

    isPass = True

    # 连接用例编号与用例标题的字符串

    print('############# 开始测试用例 ' + rowList[0] + ' - ' + rowList[1] + '###############')

    if rowList[5]  == actCode and rowList[6] == actContent:

        print("测试用例通过!")

    else:

        isPass = False

        print('测试用例失败!')

        # 错误情况下,输出预期和实际的值,便于分析

        print("状态码:" + "预期 - " + str(rowList[5]) + "实际 - " + str(actCode))

        print("响应:" + "预期 - " + rowList[6] + "实际 - " + actContent)

    print('############# 结束测试用例 ' + rowList[0] + ' - ' + rowList[1] + '###############')

    return isPass


最后是调度模块Control.py。


(1) 类中的构造方法__init__先初始化成员属性self.table,获取excel数据,self.countSucc统计成功用例数,self.countFail统计失败用例数,self.runTime统计执行时间。

(2) runTest方法整合测试,使用数据并断言,详细见注释。

(3) start方法又在runTest方法上封装了一层,用于统计运行时间,这里读者有疑惑了:为什么不把统计运行时间的功能都放在runTest中,看起来似乎有点多余。其实不然,考虑后续我们可能还要对整个测试过程进行其他额外的处理,全部放在一个方法中实现耦合度太高,代码层次也不清晰。


import time

from C06_InterFaceTest.Common import Common

from C06_InterFaceTest.Util import *

class Control:

    # 开始执行测试的方法,读取excel,并将每一行数据进行解析,传递给Common库运行

    def __init__(self):

        self.table = readExcel(r'C:\Users\Administrator\PycharmProjects\python364\C06_InterFaceTest\TestData.xlsx')

        self.countSucc = 0

        self.countFail = 0

        self.runTime = 0

    # 执行请求并断言

    def runTest(self):

        # 每次读取excel的一行数据

        for row in range(1,self.table.nrows):

            rowList = self.table.row_values(row)

            # 解析rowList[4],即接口参数。

            newData = parseData(rowList[4])

            common = Common()

            # 根据rowList[3],即接口类型进行判断,这里假设只有GET和POST两种

            if rowList[3] == 'GET':

                res = common.httpGet(rowList[2])

            else:

                res = common.httpPost(rowList[2], newData)

            # 断言,分别传入预期和实际的值

            isPass = asertResult(rowList, res.status_code, res.text)

            if isPass:

                self.countSucc = self.countSucc + 1

            else:

                self.countFail = self.countFail + 1

    # 调用执行测试的方法,并统计时间

    def start(self):

        # 在执行过程之前和之后分别获取开始和结束时间

        begin = time.clock()

        self.runTest()

        end = time.clock()

        # 计算出运行时长

        self.runTime = end - begin

        print('--------------------')

        print('运行时长:' + str(self.runTime) + "s, 成功数:" + str(self.countSucc) + ", 失败数:" + str(self.countFail))

        print('--------------------')

# 实例化并调用开始运行的方法

s = Control()

s.start()


运行测试,结果如下。结果中展示了每个测试用例的运行情况,当测试用例未通过时,会将状态码和响应内容都输出,最后还对整个测试过程的时间、成功数、失败数做了一个统计。第2个和第3个测试用例不符合我们的预期,测试未通过。


############# 开始测试用例 C_ADD_001 - 所有参数正确###############

测试用例通过!

############# 结束测试用例 C_ADD_001 - 所有参数正确###############

############# 开始测试用例 C_ADD_002 - 只填写必填参数###############

测试用例失败!

状态码:预期 - 200, 实际 - 500

响应:预期 - add-successful, 实际 - <html><head><title>500 Internal Server Error</title></head><body bgcolor='white'><center><h1>500 Internal Server Error</h1></center><hr><center><a href='http://www.jfinal.com?f=ev-3.2' target='_blank'><b>Powered by JFinal 3.2</b></a></center></body></html>

############# 结束测试用例 C_ADD_002 - 只填写必填参数###############

############# 开始测试用例 C_ADD_003 - 缺少姓名###############

测试用例失败!

状态码:预期 - 200, 实际 - 500

响应:预期 - add-failed, 实际 - <html><head><title>500 Internal Server Error</title></head><body bgcolor='white'><center><h1>500 Internal Server Error</h1></center><hr><center><a href='http://www.jfinal.com?f=ev-3.2' target='_blank'><b>Powered by JFinal 3.2</b></a></center></body></html>

############# 结束测试用例 C_ADD_003 - 缺少姓名###############

############# 开始测试用例 C_ADD_004 - 姓名重复###############

测试用例通过!

############# 结束测试用例 C_ADD_004 - 姓名重复###############

############# 开始测试用例 C_ADD_005 - 手机号重复###############

测试用例通过!

############# 结束测试用例 C_ADD_005 - 手机号重复###############

--------------------

运行时长:1.837591851259516s, 成功数:3, 失败数:2

--------------------


3.后续优化


很多人印象里的自动化测试框架就是一个能够进行自动化测试的程序。其实这不全面,真正的自动化测试框架可以不是一个程序,它仅仅是一种思想和方法的集合,说白了,就是一个架构,大家应该都知道操作系统其实也是一个架构吧,你可以把其理解成一个基础的自动化测试框架为一个简单的操作系统,它定义了几层架构,定义了各层互相通信的方式。通过这个架构我们才能在上面进行拓展我们的测试对象(核心体)、测试库(链接库)、测试用例集(各个windows进程)、测试用例(线程),而其之间的通过参数的传递进行通信(即相当于系统中的消息传递)。


蜗牛学院通过多年在自动化测试领域的探索和积累,总结出了一套行之有效的方法,我们认为,一套好的框架应该达到如下目的:

(1)独立于测试工具。

(2)测试步骤和测试资产可重用。

(3)测试数据易定制。

(4)异常处理机制。

(5)测试脚本易开发。

(6)测试脚本易维护。

(7)无人干预执行。

(8)代码可移植性高。

(9)适宜于团队开发。


20190320_095551_034.jpg


从上图可知,自动化测试框架除了我们已实现的数据驱动外,还有关键字驱动框架,测试智能化框架等。至于哪种框架更好,这并没有定论。我们应该根据实际工作的需求,结合人力、组织、流程、成本、进度等角度选取最容易实践并能快速产生价值的框架,然后不断的调整优化,让其发挥最大的效益。下面是一些框架优化的方向,供大家参考。


(1) 异常捕获。

(2) 更高层次的封装。

(3) 自动部署功能。

(4) 定时执行。

(5) 日志跟踪。

(6) 错误截图。

(7) 生成报告。

(8) 发送邮件。

(9) 自动验证。

(10) 持续集成测试。

(11) 分布式测试。


永远记住,你的“自动化测试框架”是给测试人员用的,如果你真的想把自动化测试做成一个规模,那么你需要将测试工程师当做你的用户,你不能指望他们有耐心的去编写测试脚本或者指望他们能够像你一样对这些思想有良好的掌握。你要将他们当成什么都不懂的用户,因此你的框架必须是“一切简单化”的化身,简单的操作、简单的维护、简单的拓展。


做一个自动化测试框架主要是从分层上去考虑,而不是简简单单的应用一种思想,它是各种思想的集合体。真正的自动化测试框架是与流程上结合的,而不简简单单的靠技术实现,技术其实不是很复杂,关键就在于对其架构和流程的深刻把握,而这需要很长的一段时间,所以不要指望一口气能吃成胖子,只能一步一步按需求来,需求指导思想的应用。


在商业应用中,往往会因为实际的需求不同而需要做更多的工作,所以大家不要有“一招鲜吃遍天”的想法,这也是不切实际的,随着项目的深入,我们会发现这个接口测试框架需要满足更高要求,考虑的因素也更多,这也对测试工程师提出了更多的挑战。



为了答谢大家对蜗牛学院的支持,蜗牛学院将会定期对大家免费发放干货,敬请关注蜗牛学院的官方微信。


20190320_095757_834.jpg




版权所有,转载本站文章请注明出处:蜗牛学苑, https://www.woniuxy.cn/article/235