• 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封装读取测试用例的数据,执行测试用例和校验测试结果

    1. #coding:utf-8
    2. import xlrd,os
    3. import requests
    4. from datetime import datetime
    5. from xlrd import xldate_as_tuple
    6. from config import readConfig
    7. from common.logger import Log
    8. '''
    9. 获取测试用例data所在的目录
    10. '''
    11. d = os.path.dirname(__file__) #返回当前文件所在目录(common文件夹路径)
    12. parent_path = os.path.dirname(d) #返回common的父级目录
    13. data_path = os.path.join(parent_path,'data') #返回data所在目录
    14. data_path1 = os.listdir(data_path) #返回data目录下所有的文件
    15. log = Log()
    16. def api_data():
    17. for filename in data_path1:
    18. book = xlrd.open_workbook(os.path.join(data_path,filename))
    19. '''
    20. 获取excel文件中接口信息
    21. '''
    22. table = book.sheet_by_index(0) #通过索引,获取相应的列表,这里表示获取excel的第一个列表
    23. inf_name = table.row_values(1)[0] #返回接口名称
    24. inf_address = table.row_values(1)[1] #返回接口地址
    25. inf_mode = table.row_values(1)[2] #返回请求方式
    26. '''
    27. 获取excel文件中测试用例信息
    28. '''
    29. sheet = book.sheet_by_index(1) #通过索引,获取相应的列表,这里表示获取excel的第二个列表
    30. nrows = sheet.nrows #获取所有行数
    31. filed = sheet.row_values(0)
    32. # print(filed)
    33. for i in range(1,nrows):
    34. d1 = {}
    35. for j in range(0,len(filed)-2):
    36. ctype = sheet.cell(i, j).ctype # 表格的数据类型
    37. cell = sheet.cell_value(i, j)
    38. d = {}
    39. if ctype == 2 and cell % 1 == 0: # 如果是整形
    40. cell = int(cell)
    41. elif ctype == 3:
    42. # 转成datetime对象
    43. date = datetime(*xldate_as_tuple(cell, 0))
    44. cell = date.strftime('%Y/%m/%d')
    45. elif ctype == 4:
    46. cell = True if cell == 1 else False
    47. # print(cell)
    48. d.update({filed[j]:cell})
    49. # print(d)
    50. d1.update(d)
    51. # print(d1)
    52. '''
    53. 获取excel文件中测试用例预期结果和描述
    54. '''
    55. a = []
    56. for k in range(len(filed)-2,len(filed)):
    57. ctype = sheet.cell(i, k).ctype # 表格的数据类型
    58. cell = sheet.cell_value(i, k)
    59. if ctype == 2 and cell % 1 == 0: # 如果是整形
    60. cell = int(cell)
    61. elif ctype == 3:
    62. # 转成datetime对象
    63. date = datetime(*xldate_as_tuple(cell, 0))
    64. cell = date.strftime('%Y/%m/%d')
    65. elif ctype == 4:
    66. cell = True if cell == 1 else False
    67. a.append(cell)
    68. # print(a[0])
    69. # print(type(a[0]))
    70. '''
    71. 获取cfg.ini配置文件中接口公共信息(ip和port)
    72. '''
    73. ip = readConfig.ip # 获取配置文件中接口ip
    74. i_port = readConfig.i_port # 获取配置文件中接口port
    75. url = "http://" + ip + ":" + i_port + inf_address
    76. headers = {
    77. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
    78. "X-Requested-With": "XMLHttpRequest",
    79. "Connection": "keep-alive"
    80. }
    81. par = d1
    82. '''
    83. 判断请求方式是GET还是POST,并且判断测试用例预期结果与实际响应一致
    84. '''
    85. if inf_mode == 'GET':
    86. r = requests.get(url, params=par)
    87. result = r.json()
    88. # log.info("---编号%s,接口名称%s---")%(i,inf_name)
    89. print(inf_name, str(result).replace('None','null'), a[1])
    90. if str(result).replace('None','null') == a[0]:
    91. log.info("pass")
    92. log.info("--------")
    93. else:
    94. log.info("false")
    95. log.info("--------")
    96. elif inf_mode == 'POST':
    97. r = requests.post(url, data=par, headers=headers)
    98. result = r.json()
    99. print(inf_name, str(result).replace('None', 'null'), a[1])
    100. if str(result).replace('None', 'null') == a[0]:
    101. print('pass')
    102. print('--------')
    103. else:
    104. print('false')
    105. print('--------')
    106. api_data()

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

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

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

    1. # coding:utf-8
    2. import os
    3. import configparser
    4. cur_path = os.path.dirname(os.path.realpath(__file__))
    5. configPath = os.path.join(cur_path, "cfg.ini")
    6. conf = configparser.ConfigParser()
    7. conf.read(configPath,encoding='utf-8')
    8. smtp_server = conf.get("email", "smtp_server")
    9. sender = conf.get("email", "sender")
    10. psw = conf.get("email", "psw")
    11. receiver = conf.get("email", "receiver")
    12. port = conf.get("email", "port")
    13. ip = conf.get("interface","ip")
    14. i_port = conf.get("interface","port")

    优化:

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

    1. #coding:utf-8
    2. import requests
    3. from common.logger import Log
    4. class Login():
    5. log = Log()
    6. def __init__(self,s):
    7. self.s = s
    8. def login(self,code,passwd):
    9. url = "http://192.168.20.100:8081/backend/system/user/login"
    10. headers = {"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8",
    11. "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",
    12. "X-Requested-With":"XMLHttpRequest",
    13. "Cookie":"JSESSIONID=92D7FB4C7FB917B7D2E8DC429A63443F",
    14. "Connection":"keep-alive"
    15. }
    16. d = {"code":code,"passwd":passwd}
    17. res = self.s.post(url,headers=headers,data=d)
    18. result1 = res.text #字节输出
    19. self.log.info(u"调用登录方法,获取结果:%s"%result1)
    20. return res.json()

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

    1.在请求接口前首先调用登录接口2.加入执行用例的编号(p),每循环一次自增1

    1. #coding:utf-8
    2. import xlrd,os
    3. import requests
    4. from datetime import datetime
    5. from xlrd import xldate_as_tuple
    6. from config import readConfig
    7. from common.logger import Log
    8. from case.user_login import Login
    9. '''
    10. 获取测试用例data所在的目录
    11. '''
    12. d = os.path.dirname(__file__) #返回当前文件所在目录(common文件夹路径)
    13. parent_path = os.path.dirname(d) #返回common的父级目录
    14. data_path = os.path.join(parent_path,'data') #返回data所在目录
    15. data_path1 = os.listdir(data_path) #返回data目录下所有的文件
    16. s = requests.session()
    17. lon = Login(s)
    18. log = Log()
    19. def api_data():
    20. p = 1
    21. for filename in data_path1:
    22. book = xlrd.open_workbook(os.path.join(data_path,filename))
    23. '''
    24. 获取excel文件中接口信息
    25. '''
    26. table = book.sheet_by_index(0) #通过索引,获取相应的列表,这里表示获取excel的第一个列表
    27. inf_name = table.row_values(1)[0] #返回接口名称
    28. inf_address = table.row_values(1)[1] #返回接口地址
    29. inf_mode = table.row_values(1)[2] #返回请求方式
    30. '''
    31. 获取excel文件中测试用例信息
    32. '''
    33. sheet = book.sheet_by_index(1) #通过索引,获取相应的列表,这里表示获取excel的第二个列表
    34. nrows = sheet.nrows #获取所有行数
    35. filed = sheet.row_values(0)
    36. # print(filed)
    37. for i in range(1,nrows):
    38. d1 = {}
    39. for j in range(0,len(filed)-2):
    40. ctype = sheet.cell(i, j).ctype # 表格的数据类型
    41. cell = sheet.cell_value(i, j)
    42. d = {}
    43. if ctype == 2 and cell % 1 == 0: # 如果是整形
    44. cell = int(cell)
    45. elif ctype == 3:
    46. # 转成datetime对象
    47. date = datetime(*xldate_as_tuple(cell, 0))
    48. cell = date.strftime('%Y/%m/%d')
    49. elif ctype == 4:
    50. cell = True if cell == 1 else False
    51. # print(cell)
    52. d.update({filed[j]:cell})
    53. # print(d)
    54. d1.update(d)
    55. # print(d1)
    56. '''
    57. 获取excel文件中测试用例预期结果和描述
    58. '''
    59. a = []
    60. for k in range(len(filed)-2,len(filed)):
    61. ctype = sheet.cell(i, k).ctype # 表格的数据类型
    62. cell = sheet.cell_value(i, k)
    63. if ctype == 2 and cell % 1 == 0: # 如果是整形
    64. cell = int(cell)
    65. elif ctype == 3:
    66. # 转成datetime对象
    67. date = datetime(*xldate_as_tuple(cell, 0))
    68. cell = date.strftime('%Y/%m/%d')
    69. elif ctype == 4:
    70. cell = True if cell == 1 else False
    71. a.append(cell)
    72. # print(a[0])
    73. # print(type(a[0]))
    74. '''
    75. 获取cfg.ini配置文件中接口公共信息(ip和port)
    76. '''
    77. ip = readConfig.ip # 获取配置文件中接口ip
    78. i_port = readConfig.i_port # 获取配置文件中接口port
    79. url = "http://" + ip + ":" + i_port + inf_address
    80. headers = {
    81. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
    82. "X-Requested-With": "XMLHttpRequest",
    83. "Connection": "keep-alive"
    84. }
    85. par = d1
    86. '''
    87. 判断请求方式是GET还是POST,并且判断测试用例预期结果与实际响应一致,所有接口请求前先调用登录接口
    88. '''
    89. code = "xxx" #登录接口用户code
    90. passwd = "xxx" #登录接口用户passwd
    91. lon.login(code, passwd)
    92. if inf_mode == 'GET':
    93. r = s.get(url, params=par)
    94. result = r.json()
    95. log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s"%(p,inf_name,a[1],str(result).replace('None','null')))
    96. # print(inf_name, str(result).replace('None','null'), a[1])
    97. if str(result).replace('None','null') == a[0]:
    98. log.info("pass")
    99. log.info("--------")
    100. else:
    101. log.info("false")
    102. log.info("--------")
    103. elif inf_mode == 'POST':
    104. r = s.post(url, data=par, headers=headers)
    105. result = r.json()
    106. log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s" % (p, inf_name,a[1],str(result).replace('None', 'null')))
    107. # print(inf_name, str(result).replace('None', 'null'), a[1])
    108. if str(result).replace('None', 'null') == a[0]:
    109. log.info("pass")
    110. log.info("--------")
    111. else:
    112. log.info("false")
    113. log.info("--------")
    114. p=p+1
    115. api_data()

    执行结果:

    优化二:

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

    1. #coding:utf-8
    2. import xlrd,os
    3. import requests
    4. import openpyxl
    5. from openpyxl.styles import Font
    6. # from xlutils.copy import copy
    7. from datetime import datetime
    8. from xlrd import xldate_as_tuple
    9. from config import readConfig
    10. from common.logger import Log
    11. from case.user_login import Login
    12. '''
    13. 获取测试用例data所在的目录
    14. '''
    15. d = os.path.dirname(__file__) #返回当前文件所在目录(common文件夹路径)
    16. parent_path = os.path.dirname(d) #返回common的父级目录
    17. data_path = os.path.join(parent_path,'data') #返回data所在目录
    18. data_path1 = os.listdir(data_path) #返回data目录下所有的文件
    19. s = requests.session()
    20. lon = Login(s)
    21. log = Log()
    22. def api_data():
    23. p = 1
    24. for filename in data_path1:
    25. book = xlrd.open_workbook(os.path.join(data_path,filename))
    26. '''
    27. 使用xlwt操作excel,xlwt只支持excel2007以下版本
    28. '''
    29. # wb = copy(book)
    30. # ws = wb.get_sheet(1)
    31. '''
    32. 使用openpyxl操作excel,openpyxl支持excel2007以上版本
    33. '''
    34. wb = openpyxl.load_workbook(os.path.join(data_path,filename))
    35. ws = wb.worksheets[1]
    36. font_green = Font(color="37b400")
    37. font_red = Font(color="ff0000")
    38. '''
    39. 获取excel文件中接口信息
    40. '''
    41. table = book.sheet_by_index(0) #通过索引,获取相应的列表,这里表示获取excel的第一个列表
    42. inf_name = table.row_values(1)[0] #返回接口名称
    43. inf_address = table.row_values(1)[1] #返回接口地址
    44. inf_mode = table.row_values(1)[2] #返回请求方式
    45. '''
    46. 获取excel文件中测试用例信息
    47. '''
    48. sheet = book.sheet_by_index(1) #通过索引,获取相应的列表,这里表示获取excel的第二个列表
    49. nrows = sheet.nrows #获取所有行数
    50. ncols = sheet.ncols #获取所有列数
    51. filed = sheet.row_values(0)
    52. # print(filed)
    53. for i in range(1,nrows):
    54. d1 = {}
    55. for j in range(0,len(filed)-3):
    56. ctype = sheet.cell(i, j).ctype # 表格的数据类型
    57. cell = sheet.cell_value(i, j)
    58. d = {}
    59. if ctype == 2 and cell % 1 == 0: # 如果是整形
    60. cell = int(cell)
    61. elif ctype == 3:
    62. # 转成datetime对象
    63. date = datetime(*xldate_as_tuple(cell, 0))
    64. cell = date.strftime('%Y/%m/%d')
    65. elif ctype == 4:
    66. cell = True if cell == 1 else False
    67. # print(cell)
    68. d.update({filed[j]:cell})
    69. # print(d)
    70. d1.update(d)
    71. # print(d1)
    72. '''
    73. 获取excel文件中测试用例预期结果和描述
    74. '''
    75. a = []
    76. for k in range(len(filed)-3,len(filed)-1):
    77. ctype = sheet.cell(i, k).ctype # 表格的数据类型
    78. cell = sheet.cell_value(i, k)
    79. if ctype == 2 and cell % 1 == 0: # 如果是整形
    80. cell = int(cell)
    81. elif ctype == 3:
    82. # 转成datetime对象
    83. date = datetime(*xldate_as_tuple(cell, 0))
    84. cell = date.strftime('%Y/%m/%d')
    85. elif ctype == 4:
    86. cell = True if cell == 1 else False
    87. a.append(cell)
    88. # print(a[0])
    89. # print(type(a[0]))
    90. '''
    91. 获取cfg.ini配置文件中接口公共信息(ip和port)
    92. '''
    93. ip = readConfig.ip # 获取配置文件中接口ip
    94. i_port = readConfig.i_port # 获取配置文件中接口port
    95. url = "http://" + ip + ":" + i_port + inf_address
    96. headers = {
    97. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0",
    98. "X-Requested-With": "XMLHttpRequest",
    99. "Connection": "keep-alive"
    100. }
    101. par = d1
    102. '''
    103. 判断请求方式是GET还是POST,并且判断测试用例预期结果与实际响应一致,所有接口请求前先调用登录接口
    104. '''
    105. code = "xuxingan"
    106. passwd = "admin"
    107. lon.login(code, passwd)
    108. if inf_mode == 'GET':
    109. r = s.get(url, params=par)
    110. result = r.json()
    111. log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s"%(p,inf_name,a[1],str(result).replace('None','null')))
    112. # print(inf_name, str(result).replace('None','null'), a[1])
    113. if str(result).replace('None','null') == a[0]:
    114. # ws.write(i,ncols-1,'pass'.encode('utf-8'))
    115. ws.cell(row=i+1,column=ncols,value='pass').font = font_green
    116. log.info("pass")
    117. log.info("--------")
    118. else:
    119. # ws.write(i,ncols-1,'false'.encode('utf-8'))
    120. ws.cell(row=i+1, column=ncols, value='false').font = font_red
    121. log.info("false")
    122. log.info("--------")
    123. elif inf_mode == 'POST':
    124. r = s.post(url, data=par, headers=headers)
    125. result = r.json()
    126. log.info("编号:%s,接口名称:%s,测试点:%s,响应:%s" % (p, inf_name,a[1],str(result).replace('None', 'null')))
    127. # print(inf_name, str(result).replace('None', 'null'), a[1])
    128. if str(result).replace('None', 'null') == a[0]:
    129. # ws.write(i,ncols-1,'pass'.encode('utf-8'))
    130. ws.cell(row=i+1, column=ncols, value='pass').font = font_green
    131. log.info("pass")
    132. log.info("--------")
    133. else:
    134. # ws.write(i,ncols-1,'false'.encode('utf-8'))
    135. ws.cell(row=i+1, column=ncols, value='false').font = font_red
    136. log.info("false")
    137. log.info("--------")
    138. wb.save(os.path.join(data_path, filename))
    139. p=p+1
    140. log.info("总计%s条用例"%p)
    141. api_data()

    执行结果

    总结:

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

    Python接口自动化测试零基础入门到精通(2023最新版)

  • 相关阅读:
    Leetcode刷题详解——四数之和
    SpringBoot SpringBoot 开发实用篇 1 热部署 1.3 热部署范围配置
    分布式事务最经典的八种解决方案
    Java面试题16-线程池的底层工作原理
    Tomcat经验1
    SpringBoot整合SQLite
    基于ResNet34的花朵分类
    Python调用pyttsx3实现离线文字转语音
    SpringBoot - SpringBoot配置说明
    外贸开发信主题怎么写?营销邮件标题推荐?
  • 原文地址:https://blog.csdn.net/wqda125/article/details/133869077