zoukankan      html  css  js  c++  java
  • excel+requests管理测试用例接口自动化框架

    背景:

    某项目有多个接口,之前使用的unittest框架来管理测试用例,将每个接口的用例封装成一个py文件,接口有数据或者字段变动后,需要去每个py文件中找出变动的接口测试用例,维护起来不方便,为了便于接口变动后维护,使用excel来管理测试用例,接口有变动不需要修改代码,只需要维护excel即可。

    思路:

    为了方便维护测试用例,一个接口的测试用例使用一个excel文件来管理,每个excel文件中有两个sheet页,第一个sheet页是接口的基本信息,包括接口名称,地址和请求方式,第二个sheet页为接口的测试用例,如下图所示

    第一个sheet页

    第二个sheet页

     接口请求的数据类型为X-WWW-FORM-URLENCODED,在测试用例中每个字段为一列,每条用例为一行,倒数第二列为预期结果,倒数第三列为该条用例的描述。

    接口自动化框架结构:

    common目录存放公共的方法,例如写日志,连数据库

    config目录存放配置文件和读取配置文件内容的方法,cfg.ini包括发送邮件的配置信息和接口的ip和端口

    data目录存放接口的测试用例

    logs目录存放用例执行的日志

    report目录存放测试报告

    run_main.py为用例执行的入口

    源码:

    api_test.py封装读取测试用例的数据,执行测试用例和校验测试结果

    #coding:utf-8
    
    import xlrd,os
    import requests
    from datetime import datetime
    from xlrd import xldate_as_tuple
    from config import readConfig
    from common.logger import Log
    
    '''
    获取测试用例data所在的目录
    '''
    d = os.path.dirname(__file__) #返回当前文件所在目录(common文件夹路径)
    parent_path = os.path.dirname(d) #返回common的父级目录
    data_path = os.path.join(parent_path,'data') #返回data所在目录
    data_path1 = os.listdir(data_path) #返回data目录下所有的文件
    
    log = Log()
    
    def api_data():
        for filename in data_path1:
            book = xlrd.open_workbook(os.path.join(data_path,filename))
    
            '''
            获取excel文件中接口信息
            '''
            table = book.sheet_by_index(0) #通过索引,获取相应的列表,这里表示获取excel的第一个列表
            inf_name = table.row_values(1)[0] #返回接口名称
            inf_address = table.row_values(1)[1] #返回接口地址
            inf_mode = table.row_values(1)[2] #返回请求方式
    
            '''
            获取excel文件中测试用例信息
            '''
            sheet = book.sheet_by_index(1) #通过索引,获取相应的列表,这里表示获取excel的第二个列表
            nrows = sheet.nrows #获取所有行数
            filed = sheet.row_values(0)
            # print(filed)
    
            for i in range(1,nrows):
                d1 = {}
                for j in range(0,len(filed)-2):
                    ctype = sheet.cell(i, j).ctype  # 表格的数据类型
                    cell = sheet.cell_value(i, j)
                    d = {}
                    if ctype == 2 and cell % 1 == 0:  # 如果是整形
                        cell = int(cell)
                    elif ctype == 3:
                        # 转成datetime对象
                        date = datetime(*xldate_as_tuple(cell, 0))
                        cell = date.strftime('%Y/%m/%d')
                    elif ctype == 4:
                        cell = True if cell == 1 else False
                    # print(cell)
                    d.update({filed[j]:cell})
                    # print(d)
                    d1.update(d)
                # print(d1)
    
                '''
                获取excel文件中测试用例预期结果和描述
                '''
                a = []
                for k in range(len(filed)-2,len(filed)):
                    ctype = sheet.cell(i, k).ctype  # 表格的数据类型
                    cell = sheet.cell_value(i, k)
                    if ctype == 2 and cell % 1 == 0:  # 如果是整形
                        cell = int(cell)
                    elif ctype == 3:
                        # 转成datetime对象
                        date = datetime(*xldate_as_tuple(cell, 0))
                        cell = date.strftime('%Y/%m/%d')
                    elif ctype == 4:
                        cell = True if cell == 1 else False
                    a.append(cell)
                # print(a[0])
                # print(type(a[0]))
    
                '''
                获取cfg.ini配置文件中接口公共信息(ip和port)
                '''
                ip = readConfig.ip  # 获取配置文件中接口ip
                i_port = readConfig.i_port  # 获取配置文件中接口port
                url = "http://" + ip + ":" + i_port + inf_address
                headers = {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
                    "X-Requested-With": "XMLHttpRequest",
                    "Connection": "keep-alive"
                }
                par = d1
    
                '''
                判断请求方式是GET还是POST,并且判断测试用例预期结果与实际响应一致
                '''
                if inf_mode == 'GET':
                    r = requests.get(url, params=par)
                    result = r.json()
                    # log.info("---编号%s,接口名称%s---")%(i,inf_name)
                    print(inf_name, str(result).replace('None','null'), a[1])
                    if str(result).replace('None','null') == a[0]:
                        log.info("pass")
                        log.info("--------")
                    else:
                        log.info("false")
                        log.info("--------")
                elif inf_mode == 'POST':
                    r = requests.post(url, data=par, headers=headers)
                    result = r.json()
                    print(inf_name, str(result).replace('None', 'null'), a[1])
                    if str(result).replace('None', 'null') == a[0]:
                        print('pass')
                        print('--------')
                    else:
                        print('false')
                        print('--------')
    
    
    api_data()

    配置文件cfg.ini(主要配置邮箱和接口ip+port等常用数据信息)

    [email]
    
    smtp_server = smtp.163.com
    port = 465
    sender = xxx;psw是QQ邮箱的授权码
    psw = xxx
    
    ;收件人多个时,中间用逗号隔开,如'a@xx.com,b@xx.com'
    receiver = xxx[interface]
    
    ip = xxx
    ;接口ip
    port = xxx
    ;接口端口

    readConfig.py读取配置文件中数据

    # coding:utf-8
    import os
    import configparser
    
    cur_path = os.path.dirname(os.path.realpath(__file__))
    configPath = os.path.join(cur_path, "cfg.ini")
    conf = configparser.ConfigParser()
    conf.read(configPath,encoding='utf-8')
    
    
    smtp_server = conf.get("email", "smtp_server")
    
    sender = conf.get("email", "sender")
    
    psw = conf.get("email", "psw")
    
    receiver = conf.get("email", "receiver")
    
    port = conf.get("email", "port")
    
    ip = conf.get("interface","ip")
    
    i_port = conf.get("interface","port")

    优化:

    部分接口访问时,响应未知用户,需要用session关联接口,先调用登录接口,把登录接口的调用封装成了一个实例方法,实现了复用,登录之后,登录接口的http响应会把session以 cookie的形式set到客户端,之后的接口都会使用此session去请求

    封装登录接口user_login.py

    #coding:utf-8
    import requests
    from common.logger import Log
    
    class Login():
        log = Log()
    
        def __init__(self,s):
            self.s = s
    
        def login(self,code,passwd):
            url = "http://192.168.20.100:8081/backend/system/user/login"
            headers = {"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",
                       "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36",
                       "X-Requested-With":"XMLHttpRequest",
                       "Cookie":"JSESSIONID=92D7FB4C7FB917B7D2E8DC429A63443F",
                       "Connection":"keep-alive"
                      }
            d = {"code":code,"passwd":passwd}
    
            res = self.s.post(url,headers=headers,data=d)
            result1 = res.text #字节输出
            self.log.info(u"调用登录方法,获取结果:%s"%result1)
            return res.json()

    优化api_test.py中部分代码(红色部分为优化的代码)

    1.在请求接口前首先调用登录接口

    2.加入执行用例的编号(p),每循环一次自增1

    #coding:utf-8
    
    import xlrd,os
    import requests
    from datetime import datetime
    from xlrd import xldate_as_tuple
    from config import readConfig
    from common.logger import Log
    from case.user_login import Login
    
    '''
    获取测试用例data所在的目录
    '''
    d = os.path.dirname(__file__) #返回当前文件所在目录(common文件夹路径)
    parent_path = os.path.dirname(d) #返回common的父级目录
    data_path = os.path.join(parent_path,'data') #返回data所在目录
    data_path1 = os.listdir(data_path) #返回data目录下所有的文件
    
    s = requests.session()
    lon = Login(s)
    log = Log()
    
    
    def api_data():
        p = 1
        for filename in data_path1:
            book = xlrd.open_workbook(os.path.join(data_path,filename))
    
            '''
            获取excel文件中接口信息
            '''
            table = book.sheet_by_index(0) #通过索引,获取相应的列表,这里表示获取excel的第一个列表
            inf_name = table.row_values(1)[0] #返回接口名称
            inf_address = table.row_values(1)[1] #返回接口地址
            inf_mode = table.row_values(1)[2] #返回请求方式
    
            '''
            获取excel文件中测试用例信息
            '''
            sheet = book.sheet_by_index(1) #通过索引,获取相应的列表,这里表示获取excel的第二个列表
            nrows = sheet.nrows #获取所有行数
            filed = sheet.row_values(0)
            # print(filed)
    
            for i in range(1,nrows):
                d1 = {}
                for j in range(0,len(filed)-2):
                    ctype = sheet.cell(i, j).ctype  # 表格的数据类型
                    cell = sheet.cell_value(i, j)
                    d = {}
                    if ctype == 2 and cell % 1 == 0:  # 如果是整形
                        cell = int(cell)
                    elif ctype == 3:
                        # 转成datetime对象
                        date = datetime(*xldate_as_tuple(cell, 0))
                        cell = date.strftime('%Y/%m/%d')
                    elif ctype == 4:
                        cell = True if cell == 1 else False
                    # print(cell)
                    d.update({filed[j]:cell})
                    # print(d)
                    d1.update(d)
                # print(d1)
    
                '''
                获取excel文件中测试用例预期结果和描述
                '''
                a = []
                for k in range(len(filed)-2,len(filed)):
                    ctype = sheet.cell(i, k).ctype  # 表格的数据类型
                    cell = sheet.cell_value(i, k)
                    if ctype == 2 and cell % 1 == 0:  # 如果是整形
                        cell = int(cell)
                    elif ctype == 3:
                        # 转成datetime对象
                        date = datetime(*xldate_as_tuple(cell, 0))
                        cell = date.strftime('%Y/%m/%d')
                    elif ctype == 4:
                        cell = True if cell == 1 else False
                    a.append(cell)
                # print(a[0])
                # print(type(a[0]))
    
                '''
                获取cfg.ini配置文件中接口公共信息(ip和port)
                '''
                ip = readConfig.ip  # 获取配置文件中接口ip
                i_port = readConfig.i_port  # 获取配置文件中接口port
                url = "http://" + ip + ":" + i_port + inf_address
                headers = {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
                    "X-Requested-With": "XMLHttpRequest",
                    "Connection": "keep-alive"
                }
                par = d1
    
                '''
                判断请求方式是GET还是POST,并且判断测试用例预期结果与实际响应一致,所有接口请求前先调用登录接口
                '''
                code = "xxx"  #登录接口用户code
                passwd = "xxx" #登录接口用户passwd
                lon.login(code, passwd)
    
                if inf_mode == 'GET':
                    r = s.get(url, params=par)
                    result = r.json()
                    log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s"%(p,inf_name,a[1],str(result).replace('None','null')))
                    # print(inf_name, str(result).replace('None','null'), a[1])
                    if str(result).replace('None','null') == a[0]:
                        log.info("pass")
                        log.info("--------")
                    else:
                        log.info("false")
                        log.info("--------")
                elif inf_mode == 'POST':
                    r = s.post(url, data=par, headers=headers)
                    result = r.json()
                    log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s" % (p, inf_name,a[1],str(result).replace('None', 'null')))
                    # print(inf_name, str(result).replace('None', 'null'), a[1])
                    if str(result).replace('None', 'null') == a[0]:
                        log.info("pass")
                        log.info("--------")
                    else:
                        log.info("false")
                        log.info("--------")
    
                p=p+1
    
    api_data()

    执行结果:

    优化二:

    excel中添加结果列,将每条用例的执行结果写入excel中,因为excel版本是2007以上,采用openpyxl模块去修改excel单元格的值,执行通过用绿色字体标注pass,执行不通过的用例红色字体标注false

    优化api_test.py中部分代码

    #coding:utf-8
    
    import xlrd,os
    import requests
    import openpyxl
    from openpyxl.styles import Font
    # from xlutils.copy import copy
    from datetime import datetime
    from xlrd import xldate_as_tuple
    from config import readConfig
    from common.logger import Log
    from case.user_login import Login
    
    
    '''
    获取测试用例data所在的目录
    '''
    d = os.path.dirname(__file__) #返回当前文件所在目录(common文件夹路径)
    parent_path = os.path.dirname(d) #返回common的父级目录
    data_path = os.path.join(parent_path,'data') #返回data所在目录
    data_path1 = os.listdir(data_path) #返回data目录下所有的文件
    
    s = requests.session()
    lon = Login(s)
    log = Log()
    
    
    def api_data():
        p = 1
    
    
        for filename in data_path1:
            book = xlrd.open_workbook(os.path.join(data_path,filename))
    
            '''
            使用xlwt操作excel,xlwt只支持excel2007以下版本
            '''
    
            # wb = copy(book)
            # ws = wb.get_sheet(1)
    
            '''
            使用openpyxl操作excel,openpyxl支持excel2007以上版本
            '''
            wb = openpyxl.load_workbook(os.path.join(data_path,filename))
            ws = wb.worksheets[1]
            font_green = Font(color="37b400")
            font_red = Font(color="ff0000")
    
    
            '''
            获取excel文件中接口信息
            '''
            table = book.sheet_by_index(0) #通过索引,获取相应的列表,这里表示获取excel的第一个列表
            inf_name = table.row_values(1)[0] #返回接口名称
            inf_address = table.row_values(1)[1] #返回接口地址
            inf_mode = table.row_values(1)[2] #返回请求方式
    
            '''
            获取excel文件中测试用例信息
            '''
            sheet = book.sheet_by_index(1) #通过索引,获取相应的列表,这里表示获取excel的第二个列表
            nrows = sheet.nrows #获取所有行数
            ncols = sheet.ncols #获取所有列数
            filed = sheet.row_values(0)
            # print(filed)
    
            for i in range(1,nrows):
                d1 = {}
                for j in range(0,len(filed)-3):
                    ctype = sheet.cell(i, j).ctype  # 表格的数据类型
                    cell = sheet.cell_value(i, j)
                    d = {}
                    if ctype == 2 and cell % 1 == 0:  # 如果是整形
                        cell = int(cell)
                    elif ctype == 3:
                        # 转成datetime对象
                        date = datetime(*xldate_as_tuple(cell, 0))
                        cell = date.strftime('%Y/%m/%d')
                    elif ctype == 4:
                        cell = True if cell == 1 else False
                    # print(cell)
                    d.update({filed[j]:cell})
                    # print(d)
                    d1.update(d)
                # print(d1)
    
                '''
                获取excel文件中测试用例预期结果和描述
                '''
                a = []
                for k in range(len(filed)-3,len(filed)-1):
                    ctype = sheet.cell(i, k).ctype  # 表格的数据类型
                    cell = sheet.cell_value(i, k)
                    if ctype == 2 and cell % 1 == 0:  # 如果是整形
                        cell = int(cell)
                    elif ctype == 3:
                        # 转成datetime对象
                        date = datetime(*xldate_as_tuple(cell, 0))
                        cell = date.strftime('%Y/%m/%d')
                    elif ctype == 4:
                        cell = True if cell == 1 else False
                    a.append(cell)
                # print(a[0])
                # print(type(a[0]))
    
                '''
                获取cfg.ini配置文件中接口公共信息(ip和port)
                '''
                ip = readConfig.ip  # 获取配置文件中接口ip
                i_port = readConfig.i_port  # 获取配置文件中接口port
                url = "http://" + ip + ":" + i_port + inf_address
                headers = {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
                    "X-Requested-With": "XMLHttpRequest",
                    "Connection": "keep-alive"
                }
                par = d1
    
                '''
                判断请求方式是GET还是POST,并且判断测试用例预期结果与实际响应一致,所有接口请求前先调用登录接口
                '''
                code = "xuxingan"
                passwd = "admin"
                lon.login(code, passwd)
    
    
    
                if inf_mode == 'GET':
                    r = s.get(url, params=par)
                    result = r.json()
                    log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s"%(p,inf_name,a[1],str(result).replace('None','null')))
                    # print(inf_name, str(result).replace('None','null'), a[1])
                    if str(result).replace('None','null') == a[0]:
                        # ws.write(i,ncols-1,'pass'.encode('utf-8'))
                        ws.cell(row=i+1,column=ncols,value='pass').font = font_green
                        log.info("pass")
                        log.info("--------")
                    else:
                        # ws.write(i,ncols-1,'false'.encode('utf-8'))
                        ws.cell(row=i+1, column=ncols, value='false').font = font_red
                        log.info("false")
                        log.info("--------")
                elif inf_mode == 'POST':
                    r = s.post(url, data=par, headers=headers)
                    result = r.json()
                    log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s" % (p, inf_name,a[1],str(result).replace('None', 'null')))
                    # print(inf_name, str(result).replace('None', 'null'), a[1])
                    if str(result).replace('None', 'null') == a[0]:
                        # ws.write(i,ncols-1,'pass'.encode('utf-8'))
                        ws.cell(row=i+1, column=ncols, value='pass').font = font_green
                        log.info("pass")
                        log.info("--------")
                    else:
                        # ws.write(i,ncols-1,'false'.encode('utf-8'))
                        ws.cell(row=i+1, column=ncols, value='false').font = font_red
                        log.info("false")
                        log.info("--------")
                wb.save(os.path.join(data_path, filename))
                p=p+1
    
    
        log.info("总计%s条用例"%p)
    
    api_data()

    执行结果

    总结:

    第一个版本有点粗糙,1.后续加入将每条用例的执行结果写入测试用例excel文件 2.生成自动化测试报告

  • 相关阅读:
    如何保证最少消费一次redis的list队列数据
    如果设置Redis客户端的超时时长?
    REdis一致性方案探讨
    Linux后台开发工具箱-葵花宝典
    REdis主从复制之repl_backlog
    C++之Lambda研究
    Redis-5.0.5集群配置
    REdis之maxmemory解读
    [转载]centos6.3安装启动使用PostgreSQL 9.2
    [转载]linux环境变量设置方法总结(PATH/LD_LIBRARY_PATH)
  • 原文地址:https://www.cnblogs.com/yrxns/p/7659516.html
Copyright © 2011-2022 走看看