接口主动化测试也能轻松+舒畅:Excel助力测试框架的完善 ...

打印 上一主题 下一主题

主题 995|帖子 995|积分 2989

目录:导读

媒介
设计流程图
Excel和效果预览
框架布局
Excel相关
日志封装
正则操作
核心操作
测试操作
测试陈诉发送邮件类
运行


媒介

在进行接口主动化测试时,选择一个得当自己的测试框架非常紧张。
在众多的测试框架中,Excel作为一种简朴易用、广泛应用的工具,可以用来快速构建接口主动化测试框架。通过Excel表格的操作,我们可以轻松地编写和管理测试用例,并进行测试效果的收集和分析。
本文将先容如何用Excel继续完善接口主动化测试框架,让测试变得更加高效、精确,从而包管软件质量的稳固性。
设计流程图

这张图是我的excel接口测试框架的一些设计思绪。
首先读取excel文件,得到测试信息,然后通过封装的requests方法,用unittest进行测试。
其中,接口关联的参数通过正则进行查找和替换,为此我专门开辟了一个全局变量池,用于管理各种各样的变量。
最后通过HTMLrunner生成测试陈诉。假如执行失败,发送测试陈诉效果邮件。

Excel和效果预览

这个时excel的测试测试用例构造布局图。

这个是运行之后生成的HTML测试陈诉。

这个时运行之后生成的excel陈诉。可以看到我故意在预期正则中设置了错误的值,然后用例失败的同时也把失败的预期值标记出来了。

测试失败之后收到的邮件

好了上面就是一些简朴的先容,我们开始进入正题把。
框架布局

首先,要开发如许一个excel接口主动化测试项目必须有一个设计清晰的思绪,如许我们在开发框架的过程中才会明白自己要干什么。
目录/文件说明是否为python包common公共类是core核心类,封装requests等是data测试利用的excel文件存放目录logs日志目录tests测试用例目录是utils工具类,如:日志是config.py设置文件run.py执行文件 Excel相关

用例设计
本次依然采用的是智学网登录接口。利用了智学网中的登录接口和登录验证接口,这两个接口之间有依赖的参数。
设置文件
在项目标根目录创建config.py,把你能想到的设置信息,全部丢在这个文件中进行统一的管理。
  1. #!/usr/bin/env python3
  2. # coding=utf-8
  3. import os
  4. class CF:
  5.     """配置文件"""
  6.     # 项目目录
  7.     BASE_DIR = os.path.abspath(os.path.dirname(__file__))
  8.     # Excel首行配置
  9.     NUMBER = 0
  10.     NAME = 1
  11.     METHOD = 2
  12.     URL = 3
  13.     ROUTE = 4
  14.     HEADERS = 5
  15.     PARAMETER = 6  # 参数
  16.     EXPECTED_CODE = 7  # 预期响应码
  17.     EXPECTED_REGULAR = 8  # 预期正则
  18.     EXPECTED_VALUE = 9  # 预期结果值
  19.     SPEND_TIME = 10  # 响应时间
  20.     TEST_RESULTS = 11  # 测试结果
  21.     EXTRACT_VARIABLE = 12  # 提取变量
  22.     RESPONSE_TEXT = 13  # 响应文本
  23.     # 字体大小
  24.     FONT_SET = "微软雅黑"
  25.     FONT_SIZE = 16
  26.     # 颜色配置
  27.     COLOR_PASSED = "90EE90"
  28.     COLOR_FAILED = "FA8072"
  29.     # 邮箱配置
  30.     EMAIL_INFO = {
  31.         'username': '1084502012@qq.com',
  32.         'password': 2,
  33.         'smtp_host': 'smtp.qq.com',
  34.         'smtp_port': 465
  35.     }
  36.     # 收件人
  37.     ADDRESSEE = ['1084502012@qq.com']
  38. if __name__ == '__main__':
  39.     print(CF.EXPECTED_CODE)
复制代码
读取/写入excel
​ 在common目录中新建excelset.py文件,在这个文件中我们要实现,读取excel中的用例,写入测试效果并绘制相应的颜色,写入测试耗费时长。
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. import shutil
  4. import openpyxl
  5. from config import CF
  6. from openpyxl.styles import Font
  7. from openpyxl.styles import PatternFill
  8. from common.variables import VariablePool
  9. class ExcelSet:
  10.     """Excel配置"""
  11.     def __init__(self):
  12.         shutil.copyfile(VariablePool.get('excel_input'), VariablePool.get('excel_output'))
  13.         self.path = VariablePool.get('excel_output')
  14.         self.wb = openpyxl.load_workbook(self.path)
  15.         self.table = self.wb.active
  16.     def get_cases(self, min_row=2):
  17.         """获取用例"""
  18.         all_cases = []
  19.         for row in self.table.iter_rows(min_row=min_row):
  20.             all_cases.append((self.table.cell(min_row, CF.NAME + 1).value,
  21.                               min_row, [cell.value for cell in row]))
  22.             min_row += 1
  23.         return all_cases
  24.     def write_color(self, row_n, col_n, color=CF.COLOR_FAILED):
  25.         """写入颜色"""
  26.         cell = self.table.cell(row_n, col_n + 1)
  27.         fill = PatternFill("solid", fgColor=color)
  28.         cell.fill = fill
  29.     def write_results(self, row_n, col_n, value, color=True):
  30.         """写入结果"""
  31.         cell = self.table.cell(row_n, col_n + 1)
  32.         cell.value = value
  33.         font = Font(name=CF.FONT_SET, size=CF.FONT_SIZE)
  34.         cell.font = font
  35.         if color:
  36.             if value.lower() in ("fail", 'failed'):
  37.                 fill = PatternFill("solid", fgColor=CF.COLOR_FAILED)
  38.                 cell.fill = fill
  39.             elif value.lower() in ("pass", "ok"):
  40.                 fill = PatternFill("solid", fgColor=CF.COLOR_PASSED)
  41.                 cell.fill = fill
  42.         self.wb.save(self.path)
  43. excel_set = ExcelSet()
  44. if __name__ == '__main__':
  45.     print(excel_set.get_cases())
复制代码
日志封装

logger.py
在一个项目中日志是必不可少的东西,可以第一时间反馈标题。
  1. #!/usr/bin/env python3
  2. # coding=utf-8
  3. import os
  4. import logging
  5. from config import CF
  6. from datetime import datetime
  7. class Logger:
  8.     def __init__(self):
  9.         self.logger = logging.getLogger()
  10.         if not self.logger.handlers:
  11.             self.logger.setLevel(logging.DEBUG)
  12.             # 创建一个handler,用于写入日志文件
  13.             fh = logging.FileHandler(self.log_path, encoding='utf-8')
  14.             fh.setLevel(logging.DEBUG)
  15.             # 创建一个handler,用于输出到控制台
  16.             ch = logging.StreamHandler()
  17.             ch.setLevel(logging.INFO)
  18.             # 定义handler的输出格式
  19.             formatter = logging.Formatter(self.fmt)
  20.             fh.setFormatter(formatter)
  21.             ch.setFormatter(formatter)
  22.             # 给logger添加handler
  23.             self.logger.addHandler(fh)
  24.             self.logger.addHandler(ch)
  25.     @property
  26.     def log_path(self):
  27.         logs_path = os.path.join(CF.BASE_DIR, 'logs')
  28.         if not os.path.exists(logs_path):
  29.             os.makedirs(logs_path)
  30.         now_month = datetime.now().strftime("%Y%m")
  31.         return os.path.join(logs_path, '{}.log'.format(now_month))
  32.     @property
  33.     def fmt(self):
  34.         return '%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s'
  35. log = Logger().logger
  36. if __name__ == '__main__':
  37.     log.info("你好")
复制代码
正则操作

regular.py
在接口关联参数的提取和传参中的起到了决定性的作用。
  1. #!/usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. import re
  4. from utils.logger import log
  5. from common.variables import VariablePool
  6. from core.serialize import is_json_str
  7. class Regular:
  8.     """正则类"""
  9.     def __init__(self):
  10.         self.reg = re.compile
  11.     def finds(self, string):
  12.         return self.reg(r'\{{(.*?)}\}').findall(string)
  13.     def subs(self, keys, string):
  14.         result = None
  15.         log.info("提取变量:{}".format(keys))
  16.         for i in keys:
  17.             if VariablePool.has(i):
  18.                 log.info("替换变量:{}".format(i))
  19.                 comment = self.reg(r"\{{%s}}" % i)
  20.                 result = comment.sub(VariablePool.get(i), string)
  21.         log.info("替换结果:{}".format(result))
  22.         return result
  23.     def find_res(self, exp, string):
  24.         """在结果中查找"""
  25.         if is_json_str(string):
  26.             return self.reg(r'"%s":"(.*?)"' % exp).findall(string)[0]
  27.         else:
  28.             return self.reg(r'%s' % exp).findall(string)[0]
  29. if __name__ == '__main__':
  30.     a = "{'data': {'loginName': 18291900215, 'password': '{{dd636482aca022}}', 'code': None, 'description': 'encrypt'}}"
  31.     print(Regular().finds(a))
复制代码
核心操作

界说变量池
variables.py
全局变量池来了,是不是很简朴,但是作用确实很巨大的。
  1. #!/usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. class VariablePool:
  4.     """全局变量池"""
  5.     @staticmethod
  6.     def get(name):
  7.         """获取变量"""
  8.         return getattr(VariablePool, name)
  9.     @staticmethod
  10.     def set(name, value):
  11.         """设置变量"""
  12.         setattr(VariablePool, name, value)
  13.     @staticmethod
  14.     def has(name):
  15.         return hasattr(VariablePool, name)
  16. if __name__ == '__main__':
  17.     VariablePool.set('name', 'wxhou')
  18.     print(VariablePool.get('name'))
复制代码
封装requests
request.py
最最核心的部分,对于python requests库的二次封装。用以实现接口的请求和返回效果的获取。
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. import urllib3
  4. import requests
  5. from config import CF
  6. from utils.logger import log
  7. from common.regular import Regular
  8. from common.setResult import replace_param
  9. from core.serialize import deserialization
  10. from requests.exceptions import RequestException
  11. from common.variables import VariablePool
  12. urllib3.disable_warnings()
  13. class HttpRequest:
  14.     """二次封装requests方法"""
  15.     http_method_names = 'get', 'post', 'put', 'delete', 'patch', 'head', 'options'
  16.     def __init__(self):
  17.         self.r = requests.session()
  18.         self.reg = Regular()
  19.     def send_request(self, case, **kwargs):
  20.         """发送请求
  21.         :param case: 测试用例
  22.         :param kwargs: 其他参数
  23.         :return: request响应
  24.         """
  25.         if case[CF.URL]:
  26.             VariablePool.set('url', case[CF.URL])
  27.         if case[CF.HEADERS]:
  28.             VariablePool.set('headers', deserialization(case[CF.HEADERS]))
  29.         method = case[CF.METHOD].upper()
  30.         url = VariablePool.get('url') + case[CF.ROUTE]
  31.         self.r.headers = VariablePool.get('headers')
  32.         params = replace_param(case)
  33.         if params: kwargs = params
  34.         try:
  35.             log.info("Request Url: {}".format(url))
  36.             log.info("Request Method: {}".format(method))
  37.             log.info("Request Data: {}".format(kwargs))
  38.             def dispatch(method, *args, **kwargs):
  39.                 if method in self.http_method_names:
  40.                     handler = getattr(self.r, method)
  41.                     return handler(*args, **kwargs)
  42.                 else:
  43.                     raise AttributeError('request method is ERROR!')
  44.             response = dispatch(method.lower(), url, **kwargs)
  45.             log.info(response)
  46.             log.info("Response Data: {}".format(response.text))
  47.             return response
  48.         except RequestException as e:
  49.             log.exception(format(e))
  50.         except Exception as e:
  51.             raise e
复制代码
序列化与反序列化
serialize.py
  1. #!/usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. import json
  4. from json.decoder import JSONDecodeError
  5. def deserialization(content: json):
  6.     """
  7.     反序列化
  8.         json对象 -> python数据类型
  9.     """
  10.     return json.loads(content)
  11. def serialization(content, ensure_ascii=True):
  12.     """
  13.     序列化
  14.         python数据类型 -> json对象
  15.     """
  16.     return json.dumps(content, ensure_ascii=ensure_ascii)
  17. def is_json_str(string):
  18.     """判断是否是json格式字符串"""
  19.     if isinstance(string, str):
  20.         try:
  21.             json.loads(string)
  22.             return True
  23.         except JSONDecodeError:
  24.             return False
  25.     return False
  26. if __name__ == '__main__':
  27.     a = "{'data': {'loginName': 18291900215, 'password': 'dd636482aca022', 'code': None, 'description': 'encrypt'}}"
  28.     print(is_json_str(a))
复制代码
查抄效果
checkResult.py
在这个文件中,我们将对测试返回的效果进行预期的验证。
  1. #!/usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. import re
  4. from config import CF
  5. from utils.logger import log
  6. from requests import Response
  7. from common.excelset import excel_set
  8. def check_result(r: Response, number, case):
  9.     """获取结果"""
  10.     results = []
  11.     excel_set.write_results(number, CF.SPEND_TIME, r.elapsed.total_seconds(), color=False)
  12.     if case[CF.EXPECTED_CODE]:
  13.         res = int(case[CF.EXPECTED_CODE]) == r.status_code
  14.         results.append(res)
  15.         if not res: excel_set.write_color(number, CF.EXPECTED_CODE)
  16.         log.info(f"预期响应码:{case[CF.EXPECTED_CODE]},实际响应码:{r.status_code}")
  17.     if case[CF.EXPECTED_VALUE]:
  18.         res = case[CF.EXPECTED_VALUE] in r.text
  19.         results.append(res)
  20.         if not res: excel_set.write_color(number, CF.EXPECTED_VALUE)
  21.         log.info(f"预期响应值:{case[CF.EXPECTED_VALUE]},实际响应值:{r.text}")
  22.     if case[CF.EXPECTED_REGULAR]:
  23.         res = r'%s' % case[CF.EXPECTED_REGULAR]
  24.         ref = re.findall(res, r.text)
  25.         results.append(ref)
  26.         if not ref: excel_set.write_color(number, CF.EXPECTED_REGULAR)
  27.         log.info(f"预期正则:{res},响应{ref}")
  28.     if all(results):
  29.         excel_set.write_results(number, CF.TEST_RESULTS, 'Pass')
  30.         log.info(f"用例【{case[CF.NAME]}】测试成功!")
  31.     else:
  32.         excel_set.write_results(number, CF.TEST_RESULTS, 'Failed')
  33.         assert all(results), f"用例【{case[CF.NUMBER]}{case[CF.NAME]}】测试失败:{results}"
复制代码
设置参数
setResult.py
在这个文件中我们实现了接口返回值的提取,实现了接口通报参数的函数。
  1. #!/usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. from requests import Response
  4. from utils.logger import log
  5. from common.regular import Regular
  6. from common.excelset import excel_set
  7. from common.variables import VariablePool
  8. from core.serialize import is_json_str, deserialization
  9. from config import CF
  10. reg = Regular()
  11. def get_var_result(r: Response, number, case):
  12.     """替换变量"""
  13.     if case[CF.EXTRACT_VARIABLE]:
  14.         for i in case[CF.EXTRACT_VARIABLE].split(','):
  15.             result = reg.find_res(i, r.text)
  16.             VariablePool.set(i, result)
  17.             log.info(f"提取变量{i}={result}")
  18.             if not VariablePool.get(i):
  19.                 excel_set.write_results(number, CF.EXTRACT_VARIABLE, f"提变量{i}失败")
  20.     excel_set.write_results(number, CF.RESPONSE_TEXT,
  21.                             f"ResponseCode:{r.status_code}\nResponseText:{r.text}")
  22. def replace_param(case):
  23.     """传入参数"""
  24.     if case[CF.PARAMETER]:
  25.         if is_json_str(case[CF.PARAMETER]):
  26.             is_extract = reg.finds(case[CF.PARAMETER])
  27.             if is_extract:
  28.                 return deserialization(reg.subs(is_extract, case[CF.PARAMETER]))
  29.     return deserialization(case[CF.PARAMETER])
复制代码
测试操作

test_api.py
我们采用unittest进行测试,在前置条件和后置条件中我们对封装的HttpRequest方法进行了初始化和关闭会话操作。
利用parameterized库中的expend方法对excel中的用例进行参数化读取执行。
  1. #!/usr/bin/env python
  2. # coding=utf-8
  3. import unittest
  4. from parameterized import parameterized
  5. from common.excelset import excel_set
  6. from core.request import HttpRequest
  7. from common.checkResult import check_result
  8. from common.setResult import get_var_result
  9. class TestApi(unittest.TestCase):
  10.     """测试接口"""
  11.     @classmethod
  12.     def setUpClass(cls) -> None:
  13.         cls.req = HttpRequest()
  14.     @classmethod
  15.     def tearDownClass(cls) -> None:
  16.         cls.req.r.close()
  17.     @parameterized.expand(excel_set.get_cases())
  18.     def test_api(self, name, number, case):
  19.         """
  20.         测试excel接口用例
  21.         """
  22.         r = self.req.send_request(case)
  23.         get_var_result(r, number, case)
  24.         check_result(r, number, case)
  25. if __name__ == '__main__':
  26.     unittest.main(verbosity=2)
复制代码
测试陈诉发送邮件类

run.py
  1. #!/usr/bin/env python3
  2. # -*- coding:utf-8 -*-
  3. import os
  4. import platform
  5. import argparse
  6. import unittest
  7. from common.variables import VariablePool
  8. from utils.send_mail import send_report_mail
  9. from utils.HTMLTestRunner import HTMLTestRunner
  10. def running(path):
  11.     """运行"""
  12.     test_case = unittest.defaultTestLoader.discover('tests', 'test*.py')
  13.     with open(path, 'wb') as fp:
  14.         runner = HTMLTestRunner(stream=fp,
  15.                                 title='Excel接口测试',
  16.                                 description="用例执行情况",
  17.                                 verbosity=2)
  18.         result = runner.run(test_case)
  19.     if result.failure_count:
  20.         send_report_mail(path)
  21. def file_path(arg):
  22.     """获取输入的文件路径"""
  23.     if 'Windows' in platform.platform():
  24.         _dir = os.popen('chdir').read().strip()
  25.     else:
  26.         _dir = os.popen('pwd').read().strip()
  27.     if _dir in arg:
  28.         return arg
  29.     return os.path.join(_dir, arg)
  30. def main():
  31.     """主函数"""
  32.     parser = argparse.ArgumentParser(description="运行Excel接口测试")
  33.     parser.add_argument('-i', type=str, help='原始文件')
  34.     parser.add_argument('-o', type=str, default='report.xlsx', help="输出文件")
  35.     parser.add_argument('-html', type=str, default='report.html', help="报告文件")
  36.     args = parser.parse_args()
  37.     VariablePool.set('excel_input', file_path(args.i))
  38.     VariablePool.set('excel_output', file_path(args.o))
  39.     VariablePool.set('report_path', file_path(args.html))
  40.     running(VariablePool.get('report_path'))
  41. if __name__ == '__main__':
  42.     main()
复制代码
运行

   值得注意的是,运行测试时要关闭office打开该excel文件。
  最后的文件中我是利用了argparse进行了命令行管理,意味着我们可以通过命令行进行测试而无需关心excel在那个目录下存放着。
  1. python run.py -i data\usercase.xlsx
复制代码
输入下面的命令执行一下。
  1. INFO 2020-07-30 22:07:52,713 request.py:40 Request Url: https://www.zhixue.com/weakPwdLogin/?from=web_login
  2. INFO 2020-07-30 22:07:52,714 request.py:41 Request Method: POST
  3. INFO 2020-07-30 22:07:52,715 request.py:42 Request Data: {'data': {'loginName': 18291900215, 'password': 'dd636482aca022', 'code': None, 'descriptio
  4. n': 'encrypt'}}
  5. INFO 2020-07-30 22:08:17,204 request.py:55 <Response [200]>
  6. INFO 2020-07-30 22:08:17,204 request.py:56 Response Data: {"data":"1500000100070008427","result":"success"}
  7. INFO 2020-07-30 22:08:17,207 setResult.py:20 提取变量data=1500000100070008427
  8. INFO 2020-07-30 22:08:17,307 checkResult.py:18 预期响应码:200,实际响应码:200
  9. INFO 2020-07-30 22:08:17,308 checkResult.py:23 预期响应值:"result":"success",实际响应值:{"data":"1500000100070008427","result":"success"}
  10. INFO 2020-07-30 22:08:17,310 checkResult.py:29 预期正则:[\d]{16},响应['1500000100070008']
  11. INFO 2020-07-30 22:08:17,356 checkResult.py:32 用例【登录】测试成功!
  12. ok test_api_0__ (test_api.TestApi)
  13. INFO 2020-07-30 22:08:17,358 regular.py:20 提取变量:['data']
  14. INFO 2020-07-30 22:08:17,359 regular.py:23 替换变量:data
  15. INFO 2020-07-30 22:08:17,361 regular.py:26 替换结果:{"data": {"userId": "1500000100070008427"}}
  16. INFO 2020-07-30 22:08:17,363 request.py:40 Request Url: https://www.zhixue.com/loginSuccess/
  17. INFO 2020-07-30 22:08:17,366 request.py:41 Request Method: POST
  18. INFO 2020-07-30 22:08:17,367 request.py:42 Request Data: {'data': {'userId': '1500000100070008427'}}
  19. INFO 2020-07-30 22:08:20,850 request.py:55 <Response [200]>
  20. INFO 2020-07-30 22:08:20,851 request.py:56 Response Data: {"result":"success"}
  21. INFO 2020-07-30 22:08:20,932 checkResult.py:18 预期响应码:200,实际响应码:200
  22. INFO 2020-07-30 22:08:20,933 checkResult.py:23 预期响应值:"result":"success",实际响应值:{"result":"success"}
  23. INFO 2020-07-30 22:08:20,935 checkResult.py:29 预期正则:11,响应[]
  24. F  test_api_1__ (test_api.TestApi)
  25. Time Elapsed: 0:00:28.281434
  26. 测试结果邮件发送成功!
复制代码
执行规则
  1. (venv) C:\Users\hoou\PycharmProjects\httptest-excel>python run.py -h
  2. usage: run.py [-h] [-i I] [-o O] [-html HTML]
  3. 运行Excel接口测试
  4. optional arguments:
  5.   -h, --help  show this help message and exit
  6.   -i I        原始文件
  7.   -o O        输出文件
  8.   -html HTML  报告文件
复制代码
在命令行输入python run.py excel路径 新excel路径 陈诉路径
假如不输入新excel路径和陈诉路径,会在run.py所在目录生成两个report.xlsx,report.html。
本篇的excel测试框架就完成了。
这篇贴子到这里就竣事了,最后,盼望看这篇帖子的朋友可以或许有所收获。欢迎留言,或是关注我的专栏和我交流。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表