接口测试 我的 python 接口测试框架

测试小书童 · 2016年08月04日 · 最后由 测试小书童 回复于 2016年08月26日 · 518 次阅读

简单介绍

  • Win7 64,python 3,Pycharm. unittest
  • 读取配置文件 -- 读取测试用例 -- 执行测试用例 -- 记录测试结果 -- 生成 html 结果文件
  • 支持指定接口用例 id 的测试
  • 考虑到登陆后返回的 token,useId 给其他接口联合使用的情况
  • 使用 html 在线生成器生成 xml,基于 xml 管理数据,只要改 xml 文件,不用改其他代码。
  • 检查点的定义
    • 检查第一层的全字段
    • 如果有嵌套层,检查第一层的全字段后,检查嵌套层的 model 的 type 类型

模块类的设计说明

  • Httpbase.py 读取 http 的域名和端口
  • Config.py http 方法的封装,可以支持多协议扩展,get,post
  • Runner_m.py 核心代码。run_case 是程序的入口
  • Htmlreport.py 结果生成 html 文件

xml 模型

<root>
    <title>导购码接口测试</title>
    <host>dgm.XXXX</host>
    <port>80</port>
    <No>[]</No> # 指定需要运行哪些接口
    <InterfaceList> # 第一个层固定预留,只用于登陆接口
        <id>1001</id>
        <name>登陆</name>
        <method>POST</method>
        <url>/Login</url>
        <hope>{"appStatus":{"errorCode":0,"message":"操作成功"},"content":[{"user_sex":0,"fk_user_city":440300,"user_id":30,"nickname":"18576759587","user_phone":"18576759587","head_picture":"http:\/\/dgm.boweixin.com\/","has_finance":1,"user_state":1}]}</hope>
        <params>{"account":"18576759587","password":"1112111","type":"0"}</params>
        <login>user_id</login> # 登陆后返回的userid,token等
        <isList>0</isList> # 是否有嵌套
        <!--Version=3.0&UserName=18576759587&IMEI=868157020567821&Password=222222&City=%E6%B7%B1%E5%9C%B3%E5%B8%82&Province=%E5%B9%BF%E4%B8%9C%E7%9C%81&Plat=android&PhoneModel=H60-L02-->
    </InterfaceList>
    <InterfaceList>
        <id>1002</id>
        <name>厂家主页</name>
        <method>GET</method>
        <url>/GetFactoryHome?homeId=2</url>
        <hope>{"appStatus":{"errorCode":0,"message":"操作成功"},"content":[{"business_name":"坤达点子","notice_img":"\/product\/20160718184134_321.jpg","user_type":1,"user_id":2,"goods":[{"good_price":45211.0,"good_id":12,"good_name":"艾欧","banner_picture1":"\/product\/20160719165135_8977.png"},{"good_price":199.0,"good_id":14,"good_name":"麒麟瓜1","banner_picture1":"\/product\/20160720102028_5352.jpg"},{"good_price":452.0,"good_id":6,"good_name":"实力产品","banner_picture1":"\/product\/20160718165448_2602.png"},{"good_price":99898.0,"good_id":11,"good_name":"越南芒果","banner_picture1":"\/product\/20160720100057_5877.jpg"}],"shop_img":"\/product\/20160718120144_3196.jpg","head_picture":"http:\/\/dgm.boweixin.com\/\/product\/20160718134528_4744.jpg","notice_id":1}]}</hope>
        <params>{}</params>
         <login>1</login> # 0不需要登陆后的参数,1表示需要登陆后的参数
        <isList>1</isList> # 是否有嵌套层
    </InterfaceList>

入口代码

gm = con_api_xml.ret_xml() # 读取xml
hb = con_api_xml.ret_http_base(gm) #读取http参数


#初始化报告
html_report1 = htmlreport.HtmlReport(gm)

# 测试用例(组)类
class TestInterfaceCase(unittest.TestCase):
    def __init__(self, testName, hope, index):
        super(TestInterfaceCase, self).__init__(testName)
        self.hope = hope
        self.index = index
    def setUp(self):
        self.config_http = config.ConfigHttp(hb.host, hb.port)
    def function(self):
        response = ""
        if self.index == 1: # 登陆的接口测试
             if gm[self.index]["method"] == "POST":
                response = self.config_http.post(go.URL, go.PARAMS)
                go.REALLY_RESULT = eval(response)
                print(go.REALLY_RESULT)
                hope = eval(self.hope)
                # temp = testJson.compareJson(hope, go.REALLY_RESULT, gm[self.index]["isList"])
                temp = check.compare(hope,go.REALLY_RESULT)
                if temp:
                    go.LOGIN_KY = gm[1]["login"]
                    go.LOGIN_VALUE = go.REALLY_RESULT["content"][0][go.LOGIN_KY]
                    go.RESULT = 'Pass'
                    html_report1.success_num = html_report1.success_num + 1
                else:
                    go.RESULT = 'Fail'
                    html_report1.error_num = html_report1.error_num + 1
        else:
            if gm[self.index]["login"] != "0":
                    go.PARAMS[go.LOGIN_KEY] = go.LOGIN_VALUE
            if gm[self.index]["method"] == "POST":
                response = self.config_http.post(go.URL, go.PARAMS)
            if gm[self.index]["method"] == "GET":
                response = self.config_http.get(go.URL, go.PARAMS)
            print(type(response))
            go.REALLY_RESULT = eval(str(response))
            hope = eval(self.hope)
            # temp = testJson.compareJson(hope, go.REALLY_RESULT, gm[self.index]["isList"])
            temp = check.compare(hope,go.REALLY_RESULT,  gm[self.index]["isList"])
            print(temp)
            if temp:
                go.RESULT = 'Pass'
                html_report1.success_num = html_report1.success_num + 1
            # except AssertionError:
            else:
                go.RESULT = 'Fail'
                html_report1.fail_num = html_report1.fail_num + 1
# 获取测试套件
def get_test_suite(index):
    test_suite = unittest.TestSuite()
    hope = gm[index]["hope"] # 预期值
    # print(hope)
    test_suite.addTest(TestInterfaceCase("function", hope,index))
    return test_suite

# 运行测试用例函数
def run_case(runner):
    html_report1.case_total = 0
    case_list = hb.No
    case_list = eval(case_list)  # 把字符串类型的list转换为list
    html_report1.case_list = case_list
    temp_case = ""
    if len(case_list) == False: #判断是否执行指定的用例ID
        temp_case = gm
        for index in range(1, len(temp_case)):
            go.URL = gm[index]['url']
            go.PARAMS = gm[index]["params"]
            test_suite = get_test_suite(index)
            runner.run(test_suite)
            # 记录运行结果
            gm[index]["result"] = go.RESULT
            gm[index]["really_result"] = go.REALLY_RESULT
    else:
        for i in case_list:
            for j in range(1, len(gm)):
                if str(i) == gm[j]['id']:
                    go.URL = gm[j]['url']
                    go.PARAMS = gm[j]["params"]
                    test_suite = get_test_suite(j)
                    runner.run(test_suite)
                    gm[j]["result"] = go.RESULT
                    gm[j]["really_result"] = go.REALLY_RESULT
# 运行测试套件
if __name__ == '__main__':
    start_time = time.time()
    runner = unittest.TextTestRunner()
    run_case(runner)
    end_time = time.time()
    html_report1.time_caculate(end_time - start_time)  # 计算测试消耗时间
    html_report1.generate_html( r'D:\\app\\auto_http34_test\\report\report.html')     # 生成测试报告

检查点的代码

def compare(exJson,factJson,isList=0):
    isFlag = True
    if exJson.get("appStatus") == factJson.get("appStatus"):
        if isList== False: # 如果没有嵌套层
            return isFlag
        data2 = exJson.get("content")
        data3 = factJson.get("content")
        for item2 in data2:
            for item3 in data3:
                keys2 = item2.keys()
                keys3 = item3.keys()
                if keys2 == keys3: # 如果嵌套层的key完全相等
                     for key in keys2:
                        value2 = item2.get(key)
                        value3 = item3.get(key)
                        if type(value3)==type(value2):
                           pass
                        else:
                            isFlag = False
                            break
                else:
                    isFlag = False
                    break
    else:
        isFlag = False
    print(isFlag)
    return isFlag

运行结果用的是 pyh 拼接的 html 页面

  • 页面比较丑没有去美化

后续优化

  • 接口加密
  • 美化 UI
  • 期望值从其他接口查询
  • 其他优化

github 源码

后续

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 31 条回复 时间 点赞

多谢分享,正想学下 Python,很好的资料。

@lose 有没有兴趣来小红书看看么,我们也正在 build 一个基于 Python 的测试自动化

虽然 py 我不会,但是可以看得出是好东西。。👍

#2 楼 @cesc 人在深圳,还想再沉淀一年,明年才会规划换工作的打算😬

#3 楼 @jiazurongyu 对的,我所有的框架都是基于 python 来做的,包括自动化 UI,monkey,远程的 ssh 监控服务器等等,学了 python 后,实在对其他语言不感兴趣,哈哈

好吧,原来是把 requests 又封装了一遍

#6 楼 @jphtmt

看代码,封装的比较简单

import requests
# 配置类
class ConfigHttp:
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.headers = {'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
          'User-Agent':'Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0'}
    # 设置http头
    def set_header(self, headers):
        self.headers = headers
    # 封装HTTP GET请求方法
    def get(self, url, params):
        url = "http://"+self.host+":"+self.port+url
        try:
            r = requests.get(url, params=params, headers=self.headers)
            r.encoding = 'UTF-8'
            return r.text
        except Exception:
            print('no json data returned')
            return {}
    # 封装HTTP POST请求方法,支持上传图片
    def post(self, url, data=None, files=None):
        data = eval(data)
        url = 'http://' + self.host + ':' + str(self.port)+url
        r =requests.post(url, files=files, data=data)
        print(data)
        json_response = r.text
        return json_response

更着楼主学!zzz

#7 楼 @lose 嵌套层的话就是 list 的比较了吧

def dictlistcompare(self, list1, list2):
    if len(list1)!=len(list2):
        return 0
    for i in list1:
        if i in list2:
            continue
        return 0
    return 1

#4 楼 @lose 好的,期待你分享更多干货

数据库校验?数据恢复?

#3 楼 @jiazurongyu 你不睡觉的啊团子哥!

赞,有些问题想请教能加个 QQ 吗

慎用eval, 如果你希望将 json 字符串转成 dict,可以导入json模块

#14 楼 @jacexh 哈哈,对的宁愿用 ast.literal_eval,嗯,用 json dumps,loads 去优化,只是偷了下懒,都被你发现了😁

#12 楼 @neyo 暂时不考虑去数据库操作

赞,研究研究

#15 楼 @lose 你用 xml 管理测试数据,很多数据的话会不会很麻烦?

#18 楼 @mads 不会,最简单是复制粘贴而已,当然也可以我的生成器,自动生成 xml

#19 楼 @lose 你的生成器咋用


没有嵌套层直接返回值了 不是没有做任何比对吗

#21 楼 @klxiaoqi 看代码的上一行,就是对比的第一层无嵌套的情况,有嵌套才会对比嵌套层

TESTERHOME 上的测试框架都是这样的吗。。写一个接口用例 就你这个框架你看要增加 修改几段代码? 效率太低了,XML 要增加,而且你的 XML 完全没有考虑 参数包括 HEAD 的参数化,数据库依赖, 包括接口依赖,场景初始化,如果一个接口的基本功能都验一遍 比如每个参数是否为空,特殊字符,长度等等 你 XML 就需要多少段。。再加上 业务的用例 ,检查点的增加,太难维护了。。
而且你这个检查点也无法验证一个 JSON 串的结构,包括重名数据 等等 。

#23 楼 @kofalex

TESTERHOME 上的测试框架都是这样的吗。。写一个接口用例 就你这个框架你看要增加 修改几段代码? 效率太低了

  • .怎么感觉你在抨击什么?大家只是分享下自己的想法和思路,还有接口测试在每个人眼中都不一样

太难维护

  • 就现在而言,我的这个接口测试框架,不用改任何代码,直接配置 xml 即可,至于你说的难维护没有发现哪里难维护,直接复制,粘贴而已,如果不想复制,粘贴,请用接口生成器

比如每个参数是否为空,特殊字符,长度等等 你 XML 就需要多少段。。再加上 业务的用例 ,检查点的增加

  • 嗯,按照你的意思还有其他情况也要考虑,服务器的 cpu,io,注入,men,mysql 语句运行情况,tomcat,java 资源,cdn,分布式情况的考虑

关于后面优化

  • 接口测试的检查点,我不会去查数据库
  • 帖子后面我说了优化情况,会考虑到多接口联合测试
  • 会考虑一个接口测试两次,正常测试和异常测试。正常测试就维持我现在不变的情况,异常测试还没有想好怎么模板化,如果你有什好建议,麻烦提出来,谢谢

以楼主为目标学习!楼主学习 python 多久了?

你的框架应该是使用的author = 'shouke' 写的框架,希望你注明作者,别把别人的写的框架说成自己的,要尊重作者,尊重知识产权。

#26 楼 @loading shouke 是我老朋友,对之前确实是看了他的那个框架,包括报告生成报告。但是你请仔细看看我和他框架的区别。他的框架所有的缺点在我这里完全没有。。已经把他的新浪微博贴上去了

测试小书童 关闭了讨论 08月12日 17:23
测试小书童 重新开启了讨论 08月12日 17:24

#30 楼 @mdl_xiaotuanzi 是啊,你是在上海的么

#27 楼 @lose 淡定淡定,别理喷子

#27 楼 @lose 楼主你好,可以留个 QQ 吗勾搭一下

测试小书童 [该话题已被删除] 中提及了此贴 08月26日 10:59
测试小书童 关闭了讨论 08月26日 12:28
测试小书童 python 接口测试框架测试报告 (二) 中提及了此贴 12月05日 08:55
测试小书童 关闭了讨论 06月22日 14:24
测试小书童 Appium 开源分享优化版 中提及了此贴 03月20日 17:53
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册