ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Pytest+Allure+Excel驱动的接口自动化测试框架实战

Pytest+Allure+Excel驱动的接口自动化测试框架实战 1. 为什么选Excel管理接口用例数据驱动的取舍与落地逻辑做接口自动化这几年我见过太多团队的第一版框架长这样一个接口写一个函数断言写死在代码里用例和Python逻辑完全耦合。功能测试同学想加一条用例得改代码接口字段调整得改代码环境切换还是得改代码。用不了多久这套测试代码就变成了祖传代码没人敢碰只有写它的人能维护等这个人一离职整套东西直接废掉。我在设计这套PytestAllureExcel框架时第一个决定就是把用例从代码里剥离出来交给Excel管理。这不是说Excel是最完美的存储介质而是在可维护性、可读性、团队协作这三件事上Excel是目前性价比最高的方案之一。1.1 接口自动化最大的维护成本在哪先说一个容易被忽略的事实接口自动化测试的维护成本大头从来不在写代码上而在需求变更后同步用例这件事上。接口的字段新增、参数名修改、业务规则调整都会让测试用例跟着变。如果用例以代码形式存在每一次变更都要经历改代码→提交→走评审→重新部署的流程效率极低。但把用例放到Excel里之后事情就简单了。产品经理改了接口文档测试同学打开Excel改一行数据保存用例就更新了。没有代码参与没有部署流程甚至可以让不懂代码的业务测试同学直接维护用例数据。我对这个框架的定位就是代码只负责执行逻辑Excel只负责表达业务。1.2 Excel相比YAML、JSON、数据库的优劣势对比很多用Pytest做自动化的人默认会把测试数据写成YAML或JSON文件。我也用过但实际对比下来各有各的问题这里直接列一张对比表说明对比维度ExcelYAML/JSON数据库非技术同学维护难度低打开就能编辑高格式语法容易出错高需要客户端工具可视化对比用例强行列结构清晰中纯文本不易浏览弱要看查询结果版本管理Git差xlsx二进制文件diff困难好纯文本可直接diff差无法直接diff批量编辑能力强拖拽填充方便弱要手动改文本中需要SQL动态参数支持中需要框架配合中需要框架配合强可实时查询看完这张表你就明白了如果你的团队里有业务测试同学参与用例维护Excel是唯一不需要培训就能上手的方案。而YAML/JSON更适合作坊式的个人项目——一个人管代码也管用例不需要和别人协作。当然Excel也不是没有坑。最大的坑就是xlsx文件在Git里没法做行级diff团队成员同时修改同一个用例文件时会产生冲突。我的处理办法是给Excel文件做了模块拆分按接口模块拆成多个xlsx文件比如user_case.xlsx、order_case.xlsx、payment_case.xlsx每个文件对应一个业务模块让团队成员按模块分工最大程度降低冲突概率。1.3 什么样的团队最适合这套方案我说句实话这套方案不是给大厂的核心测试平台团队用的它更适合中小团队、传统行业转型中的测试组、以及需要快速交付的敏捷项目。判断标准就三条团队里有手工/业务测试同学希望他们能参与接口自动化用例维护但不想让他们学代码接口数量在一百个以内用例总量在几百条的规模暂时没有必要上平台化管理系统需要快速出成果今天定方案明天就要能跑通的场景。如果是大团队、几千条用例、多个环境并行跑那建议尽早考虑平台化或者用数据库做数据源。Excel这套方案在用例量上去之后单文件读取性能和多人协作问题会逐渐暴露。这个后面第6章我会展开讲。2. 框架骨架目录结构、依赖与核心模块设计我一直认为一个好的自动化框架应该让人打开项目的第一眼就能看懂哪一层管什么。很多半路出家的框架之所以烂尾就是因为所有代码堆在一起没有分层意识。下面展示我这套框架的整体目录结构然后是每个模块的设计思路。2.1 目录结构与分层逻辑api_test_framework/ ├── config/ │ ├── __init__.py │ ├── settings.py # 环境配置、路径配置、请求超时时间 │ └── env_config.yaml # 各环境的基础URL、数据库连接信息 ├── core/ │ ├── __init__.py │ ├── http_client.py # 基于requests的封装统一处理headers、token │ ├── excel_reader.py # Excel读取与用例解析 │ ├── assertion.py # 断言逻辑封装支持多种断言方式 │ └── allure_report.py # Allure装饰器与报告增强的封装 ├── data/ │ ├── cases/ # Excel用例文件按模块拆分 │ │ ├── user_case.xlsx │ │ ├── order_case.xlsx │ │ └── payment_case.xlsx │ └── test_data/ # 其他测试数据文件CSV、JSON等 ├── testcases/ │ ├── conftest.py # 全局fixture如token管理、日志记录 │ └── test_runner.py # 统一的用例执行入口 ├── utils/ │ ├── __init__.py │ ├── logger.py # 日志封装 │ ├── common.py # 通用工具函数 │ └── regex.py # 正则表达式工具用于参数提取与关联 ├── reports/ # Allure报告输出目录 ├── logs/ # 运行日志 ├── requirements.txt └── pytest.ini这个结构的核心思想是四个词配置分离、数据分离、逻辑封装、统一入口。配置和用例数据都不在代码里硬编码代码层只做读数据、发请求、做断言、出报告这四件事。2.2 依赖管理requirements.txt接口自动化框架的依赖不需要太多多了反而容易出兼容性问题。我的requirements.txt保持最小化requests2.31.0 pytest8.1.1 pytest-allure-adaptor1.7.10 allure-pytest2.13.5 openpyxl3.1.2 pytest-rerunfailures14.0 pytest-xdist3.6.1 PyYAML6.0.1 jsonpath0.82.2这里要特别说明几个选择openpyxl是读写xlsx文件的首选库它不依赖微软Office在Linux服务器上也能跑这一点对CI环境至关重要。不要用xlrd/xlwtxlrd 2.0以后不支持xlsx了xlwt只支持xls老格式。pytest-rerunfailures用于处理网络抖动、服务偶发超时这类不稳定因素但注意要谨慎设置重试次数别把真实bug给重试掉了。pytest-xdist是为了用例并行执行准备的但用并行之前有个前提——用例之间要尽量独立不要有共享状态。这个点我第5章还会细说。2.3 配置文件设计环境切换不能靠改代码多环境支持是接口自动化逃不掉的需求。开发环境、测试环境、预发布环境基础URL不同有些时候连请求头都不一样。我的做法是用settings.py读取env_config.yaml里的配置通过环境变量切换环境。# config/settings.py import os import yaml def load_config(): env os.getenv(TEST_ENV, test) with open(os.path.join(os.path.dirname(__file__), env_config.yaml), r, encodingutf-8) as f: config yaml.safe_load(f) return config[env] CONFIG load_config() BASE_URL CONFIG[base_url] TIMEOUT CONFIG.get(timeout, 10)对应的env_config.yaml长这样test: base_url: https://test-api.example.com timeout: 10 prod: base_url: https://api.example.com timeout: 15运行的时候只要TEST_ENVtest pytest就可以切换环境。如果环境变量没设置默认走test环境避免误操作打到生产环境去。2.4 Excel用例模板字段设计决定框架上限Excel用例表的字段设计是这套框架的灵魂。字段设得好后面解析、执行、报告都会很顺畅字段设得不好会出现各种要么信息缺失、要么无法扩展的尴尬局面。我用过的用例字段设计如下字段名是否必填说明用例编号是唯一标识格式如USER_001用于依赖调用所属模块是对应Allure的feature用例名称是描述这条用例验证的业务场景请求方法是GET/POST/PUT/DELETE等请求路径是接口路径如/api/v1/user/info请求头否JSON格式如{Content-Type: application/json}请求参数是JSON格式支持${变量}引用预期状态码是如200预期响应字段否JSONPath表达式期望值支持多条用分号分隔提取参数否从响应中提取动态参数格式token$.data.token是否执行是yes/no用于临时屏蔽用例依赖用例否指定先执行的用例编号用于接口关联超时时间否单条用例的覆盖超时配置重点解释几个容易被忽视的字段提取参数。接口自动化最常遇到的就是接口关联比如登录拿token、下单拿订单号。我允许在Excel里直接配置从哪个响应字段提取什么变量框架执行完这条用例后把提取结果存入一个全局变量池后续用例通过${token}引用。预期响应字段。很多人做接口断言只用状态码200但这远远不够。我支持用JSONPath表达式写断言比如$.data.user_name张三;$.code0分号分隔多条断言一条用例可以同时校验多个关键字段。断言机制后面会详细展开。3. Pytest如何把Excel用例变成可执行用例parametrize与fixture的配合框架的目录和用例模板定了之后接下来要解决的核心问题是怎么让Pytest动态读取Excel里的每一行并把它变成一条真正的测试用例执行。Pytest里实现测试数据驱动的标准答案是pytest.mark.parametrize但它有个限制参数化无论是在装饰器上还是通过pytest_generate_tests钩子都要求在收集阶段就确定参数列表。这意味着我们必须在用例执行前先把Excel读出来转换成Python列表再传给parametrize。3.1 Excel读取层openpyxl的封装先看core/excel_reader.py的实现。这个模块我封装了一个ExcelReader类职责只有一个读取xlsx文件并返回用例字典列表。# core/excel_reader.py import os from openpyxl import load_workbook class ExcelReader: def __init__(self, file_path): self.file_path file_path def read_cases(self): 读取Excel文件返回用例字典列表 wb load_workbook(self.file_path, data_onlyTrue) sheet wb.active rows list(sheet.iter_rows(values_onlyTrue)) if not rows: return [] headers [str(h).strip() for h in rows[0]] cases [] for row in rows[1:]: # 跳过全空行 if all(cell is None for cell in row): continue case dict(zip(headers, row)) cases.append(case) wb.close() return cases这里有几个细节是踩过坑才加上的data_onlyTrue是关键。不加这个参数openpyxl默认读取公式本身而不是公式的计算结果。如果Excel里某个单元格写的是VLOOKUP(...)默认模式返回的是那一长串公式字符串而不是计算结果。我会在第5章详细讲这个坑。iter_rows(values_onlyTrue)比逐行访问单元格更快对于几百行的用例文件性能差异不大但对上千条用例时就能感觉到差别。跳过全空行避免因为Excel末尾出现多余空行导致生成一堆无效用例。3.2 用pytest_generate_tests动态注册用例Pytest官方推荐的参数化动态生成方式就是实现pytest_generate_tests钩子。我在testcases/conftest.py里写了一个全局钩子遍历指定目录下的所有Excel文件读取用例并生成参数# testcases/conftest.py import glob import os import pytest from core.excel_reader import ExcelReader CASE_DIR os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), data, cases) def pytest_generate_tests(metafunc): if case_data in metafunc.fixturenames: cases [] for xlsx_file in glob.glob(os.path.join(CASE_DIR, *.xlsx)): reader ExcelReader(xlsx_file) file_cases reader.read_cases() # 只收集 是否执行 为 yes 的用例 cases.extend([c for c in file_cases if str(c.get(是否执行, )).lower() in (yes, y, 是, true)]) metafunc.parametrize(case_data, cases, ids[c[用例编号] for c in cases])这样所有test_runner.py里的用例函数只要声明形参case_dataPytest就会自动把Excel里的每一行用例传给这个参数而且每条参数化的用例ID就是Excel里的用例编号在测试报告中清晰可读。3.3 统一执行入口test_runner.py有了动态参数生成执行入口就变得非常简洁了# testcases/test_runner.py import pytest import allure from core.http_client import HttpClient from core.assertion import Assertion from utils.logger import logger allure.title(接口用例{case_data[用例名称]}) def test_api_case(case_data, token_fixture): 统一执行入口读取Excel中配置的接口用例 allure.dynamic.feature(case_data.get(所属模块, 未分类模块)) allure.dynamic.story(case_data.get(用例名称, 接口用例)) client HttpClient(token_fixture) response client.send_request(case_data) # 状态码断言 Assertion.assert_status_code(response.status_code, int(case_data[预期状态码])) # 响应字段断言 expected_fields case_data.get(预期响应字段) if expected_fields: Assertion.assert_response_fields(response.json(), expected_fields) # 提取参数并写入全局变量池 extract_expr case_data.get(提取参数) if extract_expr: token_fixture.update_variables(extract_expr, response.json())这个test_api_case是所有用例的统一入口不管Excel里配置了几百条接口用例跑起来都是同一个函数被不同参数调用。好处显而易见新增接口用例不需要写任何代码只需要在Excel里加一行。3.4 token管理session级fixture只初始化一次接口测试中绝大多数接口需要登录态。如果每条用例都去调一次登录接口既浪费资源又可能触发服务端风控。我用conftest.py里的token_fixture做session级别的处理# testcases/conftest.py import pytest import requests from config.settings import BASE_URL pytest.fixture(scopesession) def token_fixture(): session级别的token管理一次性登录后续用例复用 login_data { username: test_user, password: test_password } resp requests.post(f{BASE_URL}/api/v1/login, jsonlogin_data, timeout10) token resp.json()[data][token] variable_pool {token: token} yield variable_pool这个fixture返回的是一个字典对象variable_pool既承载token又可以作为全局变量池让每条用例提取的参数都往里面塞。scopesession保证了整个测试会话只登录一次效率上非常划算。这里有一个实践建议不建议把登录逻辑做成每个测试脚本自己调用的公共函数而是就用fixture来做。fixture的好处是作用域清晰、生命周期受Pytest管理而且测试报告里能明确看到setup阶段做了什么。3.5 请求客户端封装把Excel配置翻译成HTTP请求core/http_client.py是框架的翻译官职责是把Excel用例字典转换成requests请求# core/http_client.py import json import requests from config.settings import BASE_URL, TIMEOUT class HttpClient: def __init__(self, variable_pool): self.variable_pool variable_pool self.session requests.Session() def _resolve_variables(self, value): 将字符串中的 ${变量名} 替换为变量池中的实际值 if isinstance(value, str): for key, val in self.variable_pool.items(): value value.replace(${%s} % key, str(val)) return value def send_request(self, case_data): url f{BASE_URL}{self._resolve_variables(case_data[请求路径])} method case_data[请求方法].upper() headers self._resolve_variables(case_data.get(请求头) or {}) params self._resolve_variables(case_data.get(请求参数) or {}) headers json.loads(headers) if isinstance(headers, str) else headers params json.loads(params) if isinstance(params, str) else params timeout int(case_data.get(超时时间, TIMEOUT)) response self.session.request(methodmethod, urlurl, headersheaders, jsonparams, timeouttimeout) return response注意_resolve_variables这个方法它支撑了整套框架的参数关联能力。比如上一条用例提取了order_id变量下一条用例的请求参数里写{order_id: ${order_id}}这里就会自动替换成真正的订单号。4. Allure报告从能跑出结果到领导看得懂很多人的自动化框架跑得通但报告一团糟——一堆用数字编号的测试item失败原因只有一行assert error没请求详情、没响应内容、没截图虽然接口测试不需要截图但至少要有请求数据。这样的报告发到群里开发不认领导看不懂自动化测试的价值直接打对折。Allure报告的价值就在于它能把执行结果组织成有层级、有上下文、有可读性的展示。下面是我在配置和使用Allure时的关键经验。4.1 Allure环境准备安装与Pytest接入Allure本身是Java写的命令行工具需要先装JRE。安装步骤分两步第一步下载Allure命令行工具。macOS下可以直接brew install allureLinux下从GitHub Releases下载zip包后解压把bin目录加到PATH里。Windows下用choco install allure或者手动配置环境变量都行。装完可以用allure --version验证。第二步安装pytest的Allure插件。我用的是allure-pytest在requirements.txt里已经包含。然后在pytest.ini里配置报告生成参数# pytest.ini [pytest] addopts -vs --alluredir./reports/allure-results --clean-alluredir testpaths testcases--alluredir指定原始执行结果json文件的输出目录--clean-alluredir是每次执行前清理上次的残留结果避免报告混淆。执行完测试后在项目根目录运行allure generate ./reports/allure-results -o ./reports/allure-report --cleanallure generate把原始结果渲染成静态HTML站点。我自己习惯在命令后面加--clean每次生成全新报告避免旧数据残留。4.2 把Excel字段映射到Allure报告结构Allure报告的结构是Suite Feature Story Test Case。大多数团队用不到Suite层我通常只映射三层Feature对应Excel里的所属模块一个模块的用例聚合在一起展示Story对应Excel里的用例名称或者业务场景Test Case标题显示用例编号名称方便和用例管理表对应。在test_runner.py里我已经用了allure.title()动态格式化标题并且用allure.dynamic.feature()和allure.dynamic.story()动态指定层级。这里有个细节值得注意allure.feature是静态装饰器没法在运行时根据参数变化。所以对于从Excel读取的用例一定要用allure.dynamic.*系列才能做到逐条动态绑定。如果你不用dynamic系列会出现所有用例都被分到同一个feature里报告看起来就像一锅粥完全没有模块维度。4.3 让请求和响应自动附着到用例详情接口自动化报告里最有价值的部分不是这条用例过了而是**这条用例实际发了什么请求、服务端返回了什么**。这样一旦用例失败开发可以直接从报告里复制请求复现问题不需要再让测试去抓日志。Allure实现这个功能的方式是attachment。我封装了一个简单的方法# core/allure_report.py import allure import json def attach_request_info(method, url, headers, params): 将请求信息附加到Allure报告中 payload { method: method, url: url, headers: headers, params: params } allure.attach(json.dumps(payload, ensure_asciiFalse, indent2), name请求信息, attachment_typeallure.attachment_type.JSON) def attach_response_info(response): 将响应信息附加到Allure报告中 try: body response.json() except ValueError: body response.text allure.attach(json.dumps(body, ensure_asciiFalse, indent2), name响应信息, attachment_typeallure.attachment_type.JSON)然后在http_client.py的send_request方法末尾调用这两个函数让每次请求都自动带上附件信息。这样报告里的每条用例点开都能看到完整的请求和响应内容。4.4 环境信息与分类让报告更专业默认的Allure报告只有用例列表和结果统计但一个能拿去汇报的报告最好带上测试环境、执行时间、执行人、被测系统版本这些上下文信息。Allure提供environment.properties文件来定义这些# reports/allure-results/environment.properties Environment测试环境 Browser无接口测试 Owner测试组张三 BaseURLhttps://test-api.example.com AppVersion2.3.1只要在allure generate之前生成这个文件生成的报告首页就会有一块Environment信息区域看起来非常专业。另外一个实用配置是categories.json它用来把失败用例按类型分类。默认类别是Product defects和Test defects我一般会自定义成更贴近业务的说法[ { name: 接口返回异常, matchedStatuses: [failed], messageRegex: .*StatusCodeError.*|.*response.*code.* }, { name: 断言失败, matchedStatuses: [failed], messageRegex: .*AssertionError.*|.*预期.* }, { name: 网络超时, matchedStatuses: [broken], messageRegex: .*Timeout.*|.*timed out.* } ]把categories.json放到allure-results目录下重新生成报告后首页的趋势图和用例列表都会按这些类别聚合分析问题类型时一目了然。5. 落地过程中的真实坑编码、函数、并发这几个绕不开的问题框架方案看着挺完美真跑起来才会发现一堆细节问题。这一章的每个坑都是我实际踩过的有些甚至困扰了我大半天时间才找到原因。写出来帮你直接避开。5.1 Excel里的公式单元格openpyxl读出的是公式还是结果?这是最容易踩的坑也是我前文提过用data_onlyTrue的原因但这里要展开讲清楚。背景测试同学在Excel里维护用例时特别喜欢用函数。比如预期状态码列有时候会因为前一行是200、后一行也是200就直接下拉填充了也有同学用IF(A2,,200)这种函数来做条件判断。问题openpyxl读取xlsx文件时如果不指定data_onlyTrue读到的单元格值就是公式字符串本身比如IF(A2,,200)而不是计算后的200。如果你把这个值直接转成int做断言会直接抛ValueError而且报错信息很不直观你会误以为是Excel数据格式问题找半天才发现是公式没被计算。解决方案在load_workbook()时传入data_onlyTrue。注意这个参数读取的是Excel文件保存时缓存的计算结果前提是Excel文件在保存时已经计算过公式。如果文件是用纯代码生成、从未用Office/WPS打开保存过缓存里可能连计算结果都没有data_onlyTrue读出来会是None。我在团队里立了一条规矩用例文件里尽量别用公式需要动态计算的值在生成用例时就计算好。如果确实要用至少保存前打开一次Excel文件让公式算出来并缓存再提交到Git。5.2 函数文本里的大坑Excel自动识别导致的类型翻转Excel用户都知道输入001会被自动变成1输入1-2会被自动识别成日期。这在维护用例数据时非常坑。举一个我实际遇到的案例测试同学在请求参数列里输入{mobile: 0013800001111}因为手机号太长Excel自动转成了科学计数法显示保存后单元格里的值就变成了13800001111E13之类的数字。openpyxl读出来之后这个值已经跟原本想表达的含义完全不同了。解决这个问题的标准做法是在Excel模板里把请求参数、请求头这些列设置为文本格式。新建Excel后全选工作表右键设置单元格格式为文本再开始录入用例。更稳妥的做法是在代码读取时加一道保险def safe_str(value): 数字或科学计数法转回字符串避免Excel类型转换导致的数据失真 if value is None: return if isinstance(value, float) and value.is_integer(): return str(int(value)) return str(value)另外如果需要在请求参数里明确保留前导零的编号比如工号00123建议在Excel里录入时先加单引号前缀比如00123这样Excel会强制按文本存储openpyxl读出来就是00123。5.3 布尔值、数字与JSON序列化的隐性问题Excel布尔判断读出来是True/False这在Python里没问题但JSON序列化时布尔值会变成true/false恰好是JSON标准。真正麻烦的是数字。Excel里数字可能是int比如200也可能是float比如200.0如果断言代码里写死了expected_code int(case_data[预期状态码])而Excel单元格里恰好存的是200.0int()转换后是200没毛病但如果你直接比较status_code case_data[预期状态码]就会拿200和200.0做比较。Python里200 200.0返回True但如果是浮点数参与断言逻辑经常会有意想不到的问题。我的建议是所有从Excel读出的期望值都经过一个统一的类型转换函数显式地转成目标类型不要依赖Python的隐式转换。另外请求参数列的值从Excel读出来是字符串必须json.loads()成字典对象否则requests发请求时可能把字符串当raw body传出去服务端解析直接报错。5.4 Excel文件被占用程序读取失败Windows环境下跑自动化脚本时经常遇到的情况是测试同学正开着Excel文件在维护用例脚本执行读取时报错PermissionError: [Errno 13] Permission denied。这个问题没有优雅的代码级解法只能从流程上规避。我做了两件事读取用例文件时先尝试打开如果失败则抛一个明确的业务异常用例文件被占用请先关闭Excel文件后重试而不是让Python抛一个晦涩的PermissionError在团队内明确约定用例文件修改后立即保存并关闭不要长期占着文件不撒手尤其是定好定时任务跑自动化期间不要让同事去编辑用例文件。class ExcelFileLockedError(Exception): pass def safe_load_workbook(file_path): try: return load_workbook(file_path, data_onlyTrue) except PermissionError: raise ExcelFileLockedError(f用例文件 {file_path} 被占用请关闭Excel后重试)5.5 用例执行顺序与xdist并行问题Pytest默认按文件收集顺序和文件内书写顺序执行用例但这里的顺序是参数化生成的顺序。如果Excel文件里用例之间有依赖关系比如先创建用户再查询用户光靠Excel行序其实是不够的。我在实践中的解决方案是方案一不用并行在conftest.py中给test_api_case设置一个排序因子通过pytest_collection_modifyitems钩子调整用例执行顺序。使用Excel里的用例编号和依赖用例字段用拓扑排序保证依赖用例先执行。方案二用xdist并行并行的前提是用例之间完全独立。我的建议是从设计上就尽量避免硬依赖。如果确实需要关联数据比如token、订单号在fixture层面session级一次性准备好而不是靠用例之间的执行顺序来传递。实践中我的框架默认不开xdist只有在用例数量很大、且用例本身做到了高度独立时才启用。在依赖没理清之前并行执行带来的随机性失败排查起来会非常痛苦。5.6 敏感信息别直接平铺在Excel里最后一个落地问题账号密码、token、密钥这类敏感信息尽量不要直接写在Excel用例文件里。Excel文件是二进制格式如果用Git管理历史版本里的敏感信息很难彻底清除一旦仓库泄露就是安全事故。我的做法是Excel里通过${login_username}这类变量引用敏感数据真实值的来源从环境变量读取或者在conftest.py里统一设置settings.py里集中管理使用os.getenv()获取本地开发放.env文件CI/CD平台配置环境变量。6. 从单机到CI框架进入持续集成的正确姿势本地跑通之后下一步肯定是放进CI流水线里让每次代码提交都自动触发接口测试。这一章分享一下我在Jenkins/GitLab CI里落地这套框架的经验以及几个让自动化效率真正提升的进阶技巧。6.1 定时执行与报告归档CI平台都可以配置定时的触发规则。我的建议是接口自动化不要只在提交代码时跑最好配一个每日凌晨的定时任务跑全套用例。这样每天早晨团队上班前报告已经生成好测试同学打开就能看结果。GitLab CI的.gitlab-ci.yml大致是这样stages: - test_api api_test: stage: test_api script: - pip install -r requirements.txt - TEST_ENVtest pytest -v --alluredir./reports/allure-results - allure generate ./reports/allure-results -o ./reports/allure-report --clean artifacts: paths: - reports/allure-report/ expire_in: 30 days only: - schedulesJenkins里有Build periodically插件可以配置定时构建比如H 2 * * *表示每天早上2点注意用H而不是固定时间这样避免多个项目同时触发导致机器负载高峰。报告生成后用HTML Publisher插件发布。我自己更喜欢把Allure报告单独挂到一个Nginx静态目录下通过URL直接访问比塞到Jenkins的job页面里更直观。6.2 增强断言能力jsonpath与软断言前文的断言还是简单的状态码和字段相等实际工作中还有两种更常见的断言需求JSONPath断言。比如需要断言返回的data数组中第一个元素的name是admin普通写法要一层层取数据太啰嗦。我用jsonpath库来简化import jsonpath def assert_jsonpath(actual_json, expression, expected): result jsonpath.jsonpath(actual_json, expression) assert result and result[0] expected, fJSONPath断言失败: {expression} 实际结果为 {result} 期望为 {expected}配合Excel里的预期响应字段列可以直接写$.data[0].nameadmin非常灵活。软断言。一条用例可能要校验十几个字段如果第一个断言就失败后面的断言不会执行这样你只能看到一个错误修复后跑一遍又看到第二个错误。pytest-assume支持把一系列的断言收集起来最后统一报告import pytest def test_multi_assertions(): pytest.assume(1 2, 第一次断言失败) pytest.assume(3 3, 第二次断言成功) pytest.assume(4 4, 第三次断言成功)在Excel驱动的框架里字段断言可以全部走软断言机制一条用例里所有预期字段都会被校验最后一次性展示所有失败项。这对排查接口多个字段同时异常的场景帮助非常大。6.3 从Excel到平台化什么时候该移步最后聊一个务实的话题这套Excel框架确实好用但不是一个项目能用十年的方案。我见过一些团队用例量从几百涨到五千Excel文件打开都卡Git仓库的二进制冲突不断CI跑一次要两个小时。这时候就不要硬扛了该考虑迁移了。什么时候触发迁移信号我自己的标准是接口用例数超过2000条Excel读取和报告生成开始明显变慢团队成员超过10人都在维护用例编辑冲突频繁需要做更精细的权限控制、用例版本管理、环境差异管理需要把用例和研发的接口文档平台打通实现联动的需求。迁移路径一般有两类一是从Excel转向数据库CMS的平台化测试管理工具二是直接引入现成的接口测试平台。不管是哪条路我做Excel框架时的分层设计习惯在这里帮了大忙因为用例数据从来不在代码里而是外置的数据文件所以迁移到平台时只需要把Excel数据导入平台数据库执行层全部复用。核心断言逻辑、请求封装、报告产出这些代码基本不需要改动。这也是我想传达的最终经验框架的输入输出边界要清晰数据格式要可迁移。Excel只是当前阶段最合适的载体但设计上永远给它留好退路。所谓框架的生命力不在于选用了什么技术栈而在于当你需要换技术栈的时候迁移成本是不是足够低。
返回列表