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

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。

最后

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 在使用Jmeter进行接口的性能测试时,由于Jmeter 是JAVA应用,对于CPU和内存的消耗比较大,所以,当需...
    燕京博士阅读 4,158评论 0 16
  • 前言 软件开发的V模型大家都不陌生,其中测试阶段分为单元测试→功能测试→系统测试→验收测试。其中单元测试一般...
    伊人风采_690d阅读 3,332评论 12 55
  • 主要文体来自 CDNS:https://www.cnblogs.com/ceshisanren/p/5639895...
    Amano阅读 10,959评论 3 27
  • 43期温州亲子班 【30天月度检视】姓名吴小军 #基本情况#(写孩子的) 姓名:柯夕树 年龄:8岁 小组:第1组 ...
    吴小军Wuxiaojun阅读 294评论 0 0