JmeterpythonCI

自己驱动自己—Python代码写接口测试(一)

2016-10-25  本文已影响888人  点点寒彬
20161021

背景

《聊聊接口测试》中我提到了使用Jmeter的问题和局限性。

这里其实是有一个问题的。Jmeter的学习成本其实挺大的,基础的发请求断言这类功能当然是很简单,再往后,很多细节上的处理问题,解决起来就非常非常困难,网络上很难找到类似的问题和解决方法,即使是自己去翻官方文档,也不一定就能很快的找到。

那自己写一个接口测试就迫在眉睫了,本着自己驱动自己的想法,我直接把所有内容写在代码中,自己维护起来也很快。

环境


版本相关

操作系统:Mac OS X EI Caption

Python版本:3.6

IDE:PyCharm

第三方依赖库:requests

前端:Bootstrap

可视化:Echarts


思路

这部分主要参考Jmeter的方法。先执行接口测试,然后收集执行结果,写到一个结果文件中,再用脚本去读这个结果文件,生成结果报告。Jmeter是使用xml的方式生成一个jmx的结果,我对JSON熟悉一些,就使用JSON来生成结果文件。

测试报告

报告展示报告展示 报告展示2报告展示2

整体架构

|____Common.py
|____Debug.py
|____NewLive.py
|____outReport.py
|____report.html
|____reportData.json
|____Run.py

Common.py封装了一些通用的方法,为以后拓展多个项目做准备。

Debug.py是用于编写单条接口测试用例的文件,基于Pycharm对unittest的友好支持,调试起来非常方便。

NewLive.py是我的接口测试文件,里面放了所有的接口测试用例、执行方法和生成结果文件的方法。

outReport.py是读取结果文件生成HTML测试报告的脚本

report.html是测试报告。

reportData.json是接口测试文件生成的结果文件。

Run.py是启动器,运行后就会批量执行接口测试。

注意:本工程只适用于单个接口测试项目,如果有多个接口测试项目,则需要增加一些遍历的方法。

接口测试文件

这个文件是每一个接口测试项目的核心文件,整个项目的所有接口测试用例和执行方法都在这里面。

项目的每一个接口,都写一个类。这个接口的测试用例,在这个类中都以test开头。

使用unittest作为框架本是最方便的方法,无奈unittest方法对于结果文件的写入不方便,我又懒得去翻官方文档,于是简单的自己写一个启动方法。

run方法

def run(classInstance):
    """
    执行类中的所有以test开头的方法,前提是初始化的内容要与类中的name属性一致
    :param classInstance:类
    :return:None
    """
    funcs = []
    for x in dir(classInstance):
        if x.startswith('test'):
            funcs.append(x)
    for x in funcs:
        eval(classInstance.name+'.'+x+'()')

这个方法是启动测试的实现方法,run()方法需要传入一个类作为参数,方法中需要获取这个类中的name属性用来启动类中的测试用例,因此需要在类中专门定义这个name属性,并且实例化的时候需要与这个name属性一致。

接口类

class StartLive:
    def __init__(self):
        self.classes = []
        self.name = 'startlive'
        InterFaceInfo = {
            'InterFaceName': 'StartLive',
            'FuncNo': 'xxx',
            'Desc': 'xxx'
        }
        self.classes.append(InterFaceInfo)

接口类的初始化需要定制一些内容。

self.classes列表用于收集这个接口的测试情况。

self.name需要与实例化的名称一致,用于启动测试。

InterFaceInfo说明接口的描述,方便测试报告展示。

测试用例

def test001_StartLiveCommon(self):
        """正常开始直播"""
        payload = {
            "funcNo": "XXX",
            "roomId": "XXX",
            "userId": "XXX",
            "broadIssue": time.strftime("%H%m%d") + "直播开始",
            "broadNotice": time.strftime("%H%m%d") + "直播开始",
        }

        r = requests.post(url, data=payload)
        result = r.json()

        try:
            assert result['error_no'] == '0'
            assert result['error_info'] == '创建直播并发布直播公告成功'
            consequence = "success"
        except Exception:
            consequence = 'error'

        rst_data = {
            "Url": url,
            "desc": "正常开始直播",
            "sendData": payload,
            "rspData": result,
            "result": consequence
        }
        self.classes.append(rst_data)

由于之前的run()方法是遍历以test开头的方法,因此用例的方法的命名必须以test开头。

第一部分的payload是请求的参数,第二部分是请求的方法,可以根据自己的需求使用get或者post方法。第三部分是断言部分,断言成功则给一个成功的标记,断言失败则给一个失败的标记。第四部分是测试结果的收集,信息包括url、案例描述、发送数据、接受数据和测试结果,用于最终报告的展示。第五部分就是把这个结果放到接口类初始化时候的容器中。

清理方法

def testTearClass(self):
        reportElement.append(self.classes)

在执行完毕之后需要做一下数据收集,因此把初始化中的容器self.classes列表的内容放到reportElement这个大容器中。

注意:这个方法必须放在类的最后,确保这个方法是最后一个被执行的,也就确保了所有的测试结果数据都能被收集,当然,由于要被run()方法执行到,因此命名也必须以test开头

写结果文件

if __name__ == '__main__':
    startlive = StartLive()
    run(startlive)

    with codecs.open('reportData.json', 'w', 'utf-8') as f:
        data = json.dumps(reportElement, sort_keys=True, indent=4)
        f.write(data)

最终文件的执行需要将类初始化,然后在run()方法中传入这个初始化的类,run()方法就会自动执行所有的测试用例,将结果全部归集到reportElement这个容器中。

再调用写json文件的方法把结果文件写出来。

生成测试报告

生成测试报告的核心就是去遍历这个JSON文件的内容。

def exportReport(jsonName):
    """
    根据结果报告的JSON文件生成HTML报告
    :param jsonName:{str}JSON文件名
    :return:None
    """
    tableHead = []
    trs = []
    table = []
    global interFaceName
    with open(jsonName, 'r') as f:
        jsonData = json.loads(f.read())

    for x in jsonData:  # x表示每个接口的数据
        trbody = []
        for i, a in enumerate(x):
            if i == 0:
                interFaceName = a['InterFaceName']
                tableHead.append(exportInterfaceTableHead(
                    "接口名称: {0}, 接口描述: {1}, 功能号: {2}".format(a['InterFaceName'], a['Desc'], a['FuncNo'])))
            else:
                trbody.append(
                    exportTableTr(interFaceName + str(i), a['desc'], a['result'], a['sendData'], a['rspData'],
                                  a['Url']))
        trs.append(''.join(trbody))

    for x, y in zip(tableHead, removeEmptyInList(trs)):
        table.append(x + y + exportBottom())

    interFaceTable = ''.join(table)
    html = htmlHead('直播接口测试', dashBoardTable(exportDashBoardTable(jsonName))) + interFaceTable + htmlFoot(jsonName)
    with codecs.open('report.html', 'w', 'utf-8') as f:
        f.write(html)

生成HTML结果的最优方式,肯定是用Django来做一个小后台,这样可以用模板引擎来来处理HTML,更加快速和灵活。不过为了懒得折腾后台,在整个生成结果的方法中,我用的是硬编码的方式,也就是说我把html的内容全部以字符串的形式放在代码中。然后用format方法把一些遍历的结果放到字符串中,最终把所有的字符串全部拼接到一起,直接写到文件中,就生成了最终的测试报告。

大部分前端展示的内容,都是使用Bootstrap来处理,比较简单直观。饼状图使用的是百度Echarts。

最后

这只是一个初步的结果,后期在项目增加时,需要做一些改造,比如测试报告的归档,测试用例的归档等内容,执行方法的优化等等。

上一篇下一篇

猜你喜欢

热点阅读