ARTICLE DETAIL

资讯详情

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

从零搭建接口自动化测试框架:pytest+requests+Allure实战指南

从零搭建接口自动化测试框架:pytest+requests+Allure实战指南 1. 先说结论一个接口自动化测试框架到底该解决什么问题很多人看到从零到一落地接口自动化测试框架这种标题第一反应是去搜 pytest、requests、Allure 这些工具怎么用。但真正在项目里踩过坑之后你会发现工具只是最表层的东西。接口自动化测试框架能不能落地取决于你一开始有没有想清楚一个问题这个框架是给谁用的要在这个团队里承担什么角色。我见过太多反例。有人用 Postman 导出一堆脚本放到 GitLab 里就当自动化资产了有人把几百个用例全塞进一个 test_login.py 里跑一次全量要两小时报错信息根本没法定位还有人选择了一个特别重的平台型框架结果光部署环境就折腾了两周最后因为没人维护不了了之。这些问题的本质都不是技术不够而是把框架理解成了工具的堆砌。我这里说的接口自动化测试框架指的是一套完整的工作流从接口定义和测试数据的管理到用例的执行、断言、失败重试再到测试报告的产出和 CI 流水线的触发全链路都有清晰的规范和可复用的代码底座。它能回答三个问题接口什么时候变了、哪个接口先挂的、挂在了哪个环境上。如果一套框架跑完连这三个问题都回答不了那它充其量是个脚本集合。这篇东西我会尽量贴合实际项目落地时的真实路径来讲包括技术选型、目录设计、核心代码怎么写、怎么接 CI以及那些网上教程不会告诉你的坑。适合正在从 0 搭建框架的测试开发、刚转行做自动化测试的后端开发以及团队里已经有脚本但不知道下一步怎么规划的人。2. 技术选型为什么多数团队最终都走到 Python pytest requests Allure 这条路上选型这事儿我建议先看一眼你们团队的现状再定。如果团队里全是 Java 背景的人那你硬上一套 Python 方案后续维护就是一场灾难。但如果你问我的个人倾向在完全没有历史包袱的情况下Python pytest requests Allure 这套组合几乎是目前性价比最高的接口测试技术栈。2.1 主流方案横向对比先看清楚每家的底牌我把市面上常见的接口自动化方案分成三类。第一类是工具型代表是 Postman Newman、JMeter。这类方案上手最快但可编程能力弱复杂的断言、动态签名、多接口串联业务流一旦多起来脚本就变成一坨浆糊。适合临时验证接口不适合当长期框架底座。第二类是代码型代表是 Python pytest requests、Java TestNG/RestAssured、Java JUnit5 HttpClient。这类方案最大的优势是灵活什么业务场景都能覆盖生态成熟问题排查有大量社区积累。缺点是需要团队有代码能力初期投入比工具型大。第三类是平台型代表是某些企业级测试平台、低代码自动化平台。这类方案通常自带 Web 界面、用例管理、报告展示看起来很完善。但它的问题在于改造成本高平台自身的 bug 和局限性你很难绕开而且一旦平台停更整个自动化资产就烂在手里了。我给的选型建议是这样的团队没有代码能力先别急着搭框架要么补能力要么先用工具型方案跑通流程团队有代码基础但没统一规范果断上代码型方案团队规模大到用例上千、需要多人协作才值得考虑平台型而且要确认平台有没有二次开发接口。2.2 这套 Python 技术栈里每个角色都在干什么pytest 是整个框架的执行引擎它负责收集用例、控制执行顺序、处理 fixture 的前置后置逻辑、生成 JUnitXML 格式的测试结果。它还有丰富的插件生态比如 pytest-xdist 可以做分布式执行pytest-ordering 可以控制用例顺序pytest-assume 可以实现软断言。requests 是 HTTP 请求的客户端库。它封装了连接池、Session 会话保持、代理、SSL 校验、超时控制这些底层细节让我们可以用几行代码就完成一次完整的 HTTP 调用。在框架里它通常会被二次封装把鉴权、日志、重试的公共逻辑收拢到一个地方。Allure 是报告层。它读取 pytest 生成的 result.json 文件渲染出一份带请求响应日志、测试步骤、失败截图、缺陷分类的静态网页报告。相比 HTMLTestRunner 那种单文件报告Allure 的信息密度和美观程度都高了一个档次而且支持按功能模块、严重程度、执行历史做聚合分析。你可能注意到我没把 YAML 或者 JSON 单独列出来。因为它们不算独立的角色而是测试数据的载体。在框架里接口的路径、请求参数、预期结果这些信息通常都抽到配置文件或者数据文件里不让它们散落在代码里。这也是后面整框架的核心思路之一。3. 框架落地从目录结构到关键模块的完整设计思路选型定了以后下一步就是搭工程。这一步我踩过最大的坑是一上来就想要一个完美结构。标准答案式的目录设计在真实项目里往往跑不动因为接口的复杂度、团队的协作方式、被测系统的特点都不一样。所以我建议你先把下面这套结构搭起来跑通一条完整的端到端用例再根据自己的项目调整。3.1 一个可以直接抄的目录结构我目前项目里通用的接口测试框架目录长这样api_test_framework/ ├── config/ │ ├── __init__.py │ ├── base_config.py │ ├── dev.yaml │ ├── staging.yaml │ └── prod.yaml ├── common/ │ ├── __init__.py │ ├── http_client.py │ ├── encrypt_utils.py │ ├── log_utils.py │ ├── assert_utils.py │ ├── read_data.py │ └── yaml_utils.py ├── test_cases/ │ ├── __init__.py │ ├── conftest.py │ ├── test_user_module.py │ ├── test_order_module.py │ └── test_pay_module.py ├── test_data/ │ ├── user_normal.yaml │ ├── user_exception.yaml │ ├── order_normal.yaml │ └── order_exception.yaml ├── reports/ │ ├── allure_results/ │ └── logs/ ├── run.py ├── pytest.ini ├── requirements.txt └── conftest.py这个结构的核心思想是配置、公共逻辑、用例、数据、报告五层分离。config 目录只管环境相关的东西common 目录放那些跟具体业务无关、但每个用例都会用到的工具函数test_cases 目录只放用例本身test_data 目录用外部文件维护输入数据reports 目录是运行产物不入库。3.2 配置管理为什么要区分 base_config 和环境文件接口测试最烦的一件事就是环境切换。开发环境、测试环境、预发环境的域名不同、数据库不同、甚至有些接口的参数规则都不同。如果你的框架里到处硬编码http://10.0.0.1:8080每次切环境都要全局搜索替换一遍那离框架就远了。我的做法是搞一个 base_config.py 作为配置加载入口它能自动读取当前运行环境对应的 YAML 文件。同时支持环境变量覆盖比如本地调试时临时想访问某个 feature 分支的环境不用改文件直接export API_ENVfeature-xxx就行。# config/base_config.py import os import yaml class BaseConfig: def __init__(self, envNone): self.env env or os.getenv(API_ENV, dev) config_path os.path.join( os.path.dirname(__file__), f{self.env}.yaml ) with open(config_path, r, encodingutf-8) as f: self.data yaml.safe_load(f) property def base_url(self) - str: return self.data[base_url] property def db(self) - dict: return self.data.get(database, {}) def get_env_info(self): return self.data.get(env_info, {})每个环境文件里除了 base_url 之外我还会放数据库连接信息、Redis 连接信息、特殊账号密码、环境标记比如不允许写操作这种开关以及一些只在特定环境生效的配置项。提示不要把密码和密钥明文放进配置文件哪怕只是测试环境。敏感信息建议放到 CI 的变量管理里或者用加密工具处理后再加载。3.3 requests 二次封装把鉴权、超时、日志、重试收拢到一处requests 库本身是很简单但真实项目里你会发现如果每个用例都直接调requests.get()会有几个问题第一每个请求都要硬编码鉴权信息第二出了问题想看请求日志必须每个调用点都加一遍第三网络抖动、服务偶发 5xx 的时候没有统一的重试机制。所以我习惯把 requests 封装成一个 HttpClient 类。这个类负责整合三件事请求前的公共参数准备比如统一加 token、trace_id、请求来源标识请求中的超时控制、重试策略、SSL 校验开关请求后的日志记录把 method、url、请求体、响应状态码、响应体、耗时全部打到日志文件里。# common/http_client.py import time import logging import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry logger logging.getLogger(__name__) class HttpClient: def __init__(self, base_url, tokenNone, timeout10, retry_times3): self.base_url base_url.rstrip(/) self.token token self.timeout timeout self.session requests.Session() retry Retry( totalretry_times, backoff_factor0.5, status_forcelist[500, 502, 503, 504], allowed_methods[GET, POST, PUT, DELETE], ) adapter HTTPAdapter(max_retriesretry) self.session.mount(http://, adapter) self.session.mount(https://, adapter) def _prepare_headers(self): headers { Content-Type: application/json;charsetUTF-8, User-Agent: API-AutoTest-Framework/1.0, } if self.token: headers[Authorization] fBearer {self.token} return headers def request(self, method, path, **kwargs): url f{self.base_url}{path} headers self._prepare_headers() kwargs.setdefault(headers, headers) kwargs.setdefault(timeout, self.timeout) kwargs.setdefault(verify, False) start time.time() response self.session.request(method, url, **kwargs) cost round((time.time() - start) * 1000, 2) logger.info( fHTTP {method.upper()} {url} | status{response.status_code} f| cost{cost}ms | request{str(kwargs.get(data) or kwargs.get(json) or )[:500]} f| response{str(response.text)[:1000]} ) return response def get(self, path, paramsNone, **kwargs): return self.request(GET, path, paramsparams, **kwargs) def post(self, path, jsonNone, dataNone, **kwargs): return self.request(POST, path, jsonjson, datadata, **kwargs) def put(self, path, jsonNone, **kwargs): return self.request(PUT, path, jsonjson, **kwargs) def delete(self, path, **kwargs): return self.request(DELETE, path, **kwargs)这里有个细节值得说下我没有在request()方法里做太多魔法参数处理。因为不同的接口对请求格式的要求差异太大了有人要 form 格式有人要 raw JSON有人要文件上传过度封装反而会把简单的事情搞复杂。所以我的 HttpClient 只统一处理了公共部分剩下的最终还是要透传给 requests 去处理。3.4 fixture 的设计登录态和公共数据准备该放哪一层pytest 的 fixture 是框架里最灵活也最容易写乱的部分。我的经验是分三层来管理。第一层是全局 conftest.py放所有用例都需要的 fixture比如一个临时的测试报告目录、一个接口请求客户端实例。第二层是每个模块目录下的 conftest.py放这个模块独享的前置条件比如 user 模块需要先建一个测试用户。第三层是用例函数内的局部依赖。# test_cases/conftest.py import pytest from common.http_client import HttpClient from config.base_config import BaseConfig pytest.fixture(scopesession) def config(): cfg BaseConfig() return cfg pytest.fixture(scopesession) def client(config): token login_and_get_token(config) return HttpClient(config.base_url, tokentoken) pytest.fixture() def new_user(client, config): 每个用例前新建一个随机用户用例结束后删除 payload { username: fauto_{uuid4().hex[:8]}, password: Test123456, } resp client.post(/api/v1/users, jsonpayload) assert resp.status_code 201 yield resp.json() client.delete(f/api/v1/users/{resp.json()[id]})fixture 的 scope 一定要想清楚。session 级别的 fixture 只初始化一次效率最高但如果中间状态被污染后续用例全挂。function 级别的 fixture 每个用例都走一遍稳定但慢。我的建议是登录态这类尽量靠自动续期的 token 机制放到 session 级但涉及数据增删改的公共数据宁可用 function 级别保证隔离性。4. 用例层的实操数据驱动、断言设计、日志链路一个都不能少框架的骨架搭完了真正决定它好不好用的是用例层的设计。这部分我要重点讲讲数据驱动怎么写、断言怎么设计才不坑、失败的时候怎么快速定位问题。4.1 数据驱动把一条用例变成几十条用例而不是复制粘贴很多接口用例其实只有请求参数和期望值不同比如一个登录接口要测账号不存在、密码错误、密码为空、账号被锁定、验证码错误等一堆场景。如果每一条都写成一个 test 函数代码会膨胀得非常厉害。我的做法是引入一个ddt风格的数据驱动机制不过现在 pytest 有更原生优雅的方案——pytest.mark.parametrize结合外部 YAML 数据文件。import pytest from common.read_data import load_yaml case_data load_yaml(test_data/user_login.yaml) class TestUserLogin: pytest.mark.parametrize(case, case_data[normal_login]) def test_normal_login(self, client, case): resp client.post(/api/v1/login, jsoncase[request]) assert resp.status_code case[expected][status_code] assert resp.json()[code] case[expected][code]# test_data/user_login.yaml normal_login: - case_name: 正确账号密码登录 request: username: test001 password: Test123456 expected: status_code: 200 code: 000000 - case_name: 测试环境预置账号登录 request: username: pre_user_01 password: Pre123456 expected: status_code: 200 code: 000000这里要注意参数化用例的标题和归组信息非常影响报告的可读性。我建议在 case 里一定加case_name字段然后把它体现在 pytest 的用例 ID 里pytest.mark.parametrize( case, case_data[normal_login], ids[c[case_name] for c in case_data[normal_login]], )这样 Allure 报告里每个用例都有一条清晰的中文名称而不是test_normal_login[case_data0]这种看不懂的标题。数据驱动的关键不在于数据放在 YAML 里这件事而在于格式约定。我一般会让 YAML 统一采用request和expected两个顶层字段结构这样不管接口怎么变用例的读法都是一致的。后期加新用例只加数据不改代码这才是数据驱动真正省成本的地方。4.2 断言设计别只断言状态码要考虑接口的契约边界断言是接口测试里最容易被低估的部分。很多初学者只断言response.status_code 200这基本等于没测。一个 200 的响应体里完全可能藏着业务失败。但断言也不能走另一个极端把整个响应体全字段比对那又太脆了后端加了个不影响功能的新字段你的用例就红了。我总结的断言分级是这样的第一层HTTP 状态码。这个是硬性约束4xx 和 5xx 直接挂。但注意有些框架对 4xx 的业务错误也会返回 200所以不能只看状态码。第二层业务状态码。比如code 000000表示成功code 100001表示重复提交。这一层的断言要覆盖成功和预期失败两种情况。第三层核心字段。选几个跟这条业务最关键的字段做等值断言比如用户信息接口的username、role这两个字段。第四层响应结构。校验字段是否存在、类型是否正确、数组长度是否在预期范围。这层通常用 JSON Schema 或者自定义的递归校验函数来做。我通常会封装一个AssertUtils专门处理实际值和期望值的比对逻辑。支持精确匹配、正则匹配、类型校验、包含关系让用例里写断言的时候非常简洁。# common/assert_utils.py import re import logging logger logging.getLogger(__name__) def assert_status_code(resp, expected_code): assert resp.status_code expected_code, ( f状态码断言失败: expect{expected_code}, actual{resp.status_code}, fresponse_body{resp.text[:500]} ) def assert_biz_code(resp, expected_code): body resp.json() actual_code body.get(code) assert str(actual_code) str(expected_code), ( f业务码断言失败: expect{expected_code}, actual{actual_code}, fresponse_body{resp.text[:500]} ) def assert_field_equal(resp, field_path, expected): 从嵌套结构中提取字段field_path 用点号分隔如 data.user.name body resp.json() current body for part in field_path.split(.): if isinstance(current, list): current current[int(part)] else: current current[part] if hasattr(expected, match) and isinstance(expected, re.Pattern): assert expected.match(str(current)), ( f正则断言失败: field{field_path}, expect_pattern{expected.pattern}, factual{current} ) else: assert current expected, ( f字段断言失败: field{field_path}, expect{expected}, actual{current} )断言信息里一定要带上实际返回的响应体这是定位问题的关键。没有响应体的断言失败信息等于让排查问题的人去猜服务器到底返回了什么。4.3 日志链路把 trace_id 从请求带进断言和用例上下文在真实业务链路里一个接口报错往往需要配合后端日志定位。现在很多公司内部都会做全链路追踪会给每个请求生成一个 trace_id。这个 trace_id 在接口测试里非常重要。我的做法是在 HttpClient 里自动生成或者透传 trace_id把它放在请求头里然后在用例执行完以后把 trace_id 和断言结果一起打日志。这样一旦用例失败测试人员可以直接拿着 trace_id 去找后端要日志定位时间能从小时级缩短到分钟级。# common/trace_utils.py import uuid def gen_trace_id(prefixapitest): return f{prefix}-{uuid.uuid4().hex[:16]}然后在上面的 HttpClinent 中_prepare_headers里加一行headers[X-Trace-Id] gen_trace_id()。如果你要支持外部传入可以在 request 方法的 kwargs 里先检查有没有trace_id有就用没有再生成。还有个很实用的技巧把 trace_id 放到 pytest 的 fixture 里用 yield 把 trace_id 返回给用例这样断言失败时可以看到当前用例的 trace_id。你也可以把 trace_id 和用例名绑定写进日志文件名比如logs/test_login_20240721_153012.log方便按用例去翻日志。5. 进阶机制失败重试、并发加速、Hook 钩子这些能力什么时候该加框架跑通基本流程之后下一步就是优化执行体验。这节讲的几个机制不是一上来就要全加的得看你的项目情况。5.1 失败重试必须区分用例真的失败和环境抖动失败重试是最容易做坏的一个机制。如果你不管三七二十一所有用例失败都重试三次那最后报告里的绿是假的绿。有些用例是环境问题重试通过了但同步掩盖掉了真实的逻辑漏洞。我的建议是只对特定异常类型做重试。比如连接超时、读超时、502、503、504 这些网络层问题可以重试。但断言失败、业务码不对、参数校验返回 4xx这些属于功能问题重试没有意义。pytest 有自带的pytest-rerunfailures插件用起来很简单pip install pytest-rerunfailures然后在 pytest.ini 里配置[pytest] addopts -p no:cacheprovider --reruns 2 --reruns-delay 1 --only-rerun ConnectionError|ReadTimeout|502|503|504注意--only-rerun参数它接受一个正则表达式匹配错误信息。只有匹配到的错误类型才会触发重试。这个参数很值得花时间调好否则重试机制的副作用会比收益还大。5.2 pytest-xdist 并行执行把全量回归时间从 40 分钟压到 8 分钟接口测试的执行瓶颈通常不在 CPU而在网络 IO。一次请求要等几十到几百毫秒几百个用例跑下来单线程执行确实很浪费时间。pytest-xdist 可以把测试分发到多个 worker 上并行执行对纯接口测试来说效果非常明显。pip install pytest-xdist pytest -n 4 --dist loadscope这里有个关键点--dist loadscope是按模块分配用例同一个模块的用例不会拆到不同 worker 上。如果你的用例之间有共享状态比如模块级的前置数据这个模式能最大程度保持模块内的执行顺序稳定性。但并行也带来了新问题数据隔离。多个 worker 同时创建测试数据非常容易撞上唯一约束。我的解决方案是让所有测试数据生成时带上随机后缀比如用uuid4().hex[:8]避免冲突。另外涉及修改共享资源的接口比如统计类、配置类接口最好打个并行锁或者串行执行避免互相覆盖。5.3 Hook 钩子前后置动作、失败通知、环境巡检放哪里pytest 的 hook 函数给框架赋予了极强的扩展能力。我最常用的几个 hook 是# conftest.py import os import pytest import requests pytest.hookimpl(tryfirstTrue) def pytest_runtest_setup(item): 用例执行前检查环境健康状态环境不可用直接跳过 if not check_environment_health(): pytest.skip(环境不健康跳过用例) pytest.hookimpl(tryfirstTrue) def pytest_runtest_makereport(item, call): if call.when call and call.excinfo is not None: # 用例失败后可以把失败信息推送到企业微信/钉钉 send_failure_alert(item.name, str(call.excinfo.value))环境健康检查这个 hook 非常实用。我遇到过测试环境某个核心服务挂了导致所有用例全部超时跑挂最后花了半小时才发现是环境问题。后来我在 pytest_runtest_setup 里加了一个轻量级的健康检查接口探测环境挂了就直接 skip不浪费执行时间还能在告警里第一时间暴露环境问题。不过这里要提醒一句hook 逻辑别写太重。健康检查请求本身会拖慢执行速度如果每个用例都做一次全量回归的时间又会涨上去。我的做法是做个缓存一分钟内只探测一次。6. 报告与 CI 集成Allure 接入、历史趋势、流水线失败即红灯到这里框架的核心部分已经完整了。但离落地还有最后一步让团队能方便地看到测试结果让自动化测试真正跑在版本发布的流程里。6.1 Allure 报告接入的细节不是装个插件就完事Allure 的接入分为两步执行时生成结果文件执行后渲染成 HTML 报告。# 安装 pip install allure-pytest # 执行测试并指定结果目录 pytest -n 4 --alluredirreports/allure_results # 生成并打开报告 allure generate reports/allure_results -o reports/allure_report --clean allure open reports/allure_report在 CI 或者本地脚本里我更建议直接用allure serve或者把 generate 命令写进 run.py。有几个细节是经常被忽略的第一--clean参数不能少否则老的 result 文件还在报告里会出现历史残留用例。第二Allure 支持给用例加标签这个信息在报告里做按模块筛选、按严重程度筛选非常有用。import allure allure.feature(用户模块) allure.story(登录) allure.severity(allure.severity_level.CRITICAL) allure.title(正确账号密码登录) def test_normal_login(client): ...第三请求和响应的详细信息建议在用例里通过allure.attach()保存到报告中这样在看报告的时候不需要翻日志就能定位问题。allure.step(发送登录请求) def send_login_request(client, payload): resp client.post(/api/v1/login, jsonpayload) allure.attach(str(payload), namerequest_payload, attachment_typeallure.attachment_type.JSON) allure.attach(resp.text, nameresponse_body, attachment_typeallure.attachment_type.JSON) return resp6.2 流水线集成让接口测试成为发布流程的一部分在 GitLab CI 里接接口测试的核心逻辑很简单不复杂。但有一个策略问题需要先想清楚接口测试在流水线里到底承担什么角色。我的建议是分成两层。第一层是 merge request 阶段的冒烟测试只跑核心模块的几条用例目标是在 5 分钟内给出反馈。第二层是 nightly 或者发布窗口前的全量回归跑全部用例目标是把漏测风险降到最低。interface-test: stage: test image: python:3.11-slim script: - pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple - export API_ENVstaging - pytest -n 4 --alluredirreports/allure_results - allure generate reports/allure_results -o reports/allure_report --clean artifacts: when: always paths: - reports/allure_report/ expire_in: 7 days rules: - if: $CI_PIPELINE_SOURCE merge_request_event - if: $CI_PIPELINE_SOURCE schedule $CI_COMMIT_BRANCH master这个流水线里比较关键的是when: always。如果不加这个一旦测试跑挂CI 就不会上传报告你连问题在哪都看不到。加了这个之后不管成功失败都有报告产物留下来。另外我强烈建议在 CI 里把 allure 的 history 目录保留并回传这样 Allure 报告能展示历史趋势图。做法是把上一次生成的reports/allure_report/history目录复制到本次的reports/allure_results/history位置然后重新生成报告。6.3 失败信息的可读性让开发愿意看你的报告报告做得再漂亮如果开发点进来看不到有效信息那这个框架的信任度就建立不起来。我在每个用例的断言失败信息里都会刻意包含三层内容失败的具体字段、期望值和实际值、响应体原文。这还不够如果能做到把失败原因归好类比如数据准备失败环境异常断言不一致报告的价值会更高。我现在会在用例里用 try/except 包住关键步骤把异常类型转化为更友好的提示def test_order_create(client, new_user): with allure.step(创建订单): try: resp client.post(/api/v1/orders, jsonorder_payload(new_user[id])) except requests.exceptions.ConnectTimeout: pytest.skip(接口超时疑似环境问题) assert_status_code(resp, 201)这样报告里一眼就能看到是环境问题还是功能问题开发不用再反复问你这个失败是不是环境挂了。7. 避坑指南我落地过程中踩过的五个典型坑最后这部分是拿真金白银换来的教训。每个坑我都踩过而且当时都花了很长时间才排查出来。整理成清单你们遇到类似问题可以直接对照。7.1 断言环境差异测试环境和预发环境的数据状态不一样最大的一个坑是同一套用例在不同环境跑结果完全不一样。比如测试环境的用户库里有一个叫 test001 的用户用例假设它存在到预发环境没有这个用户用例就挂了。解决思路有两个。第一所有用例的测试数据尽量在 fixture 里动态生成不要把某个环境某个账号一定存在写死在用例里。第二环境配置里把每个环境的初始数据情况维护成文件比如dev_init_data.yaml、staging_init_data.yaml在部署环境的时候同步初始化。7.2 requests 的 verify 参数在测试环境带来的 SSL 报错很多内部的测试环境用的是自签名证书如果不处理requests 每次请求都会报 SSL 错误。我之前是一直在 HttpClient 里写死verifyFalse但这会带来 urllib3 的 InsecureRequestWarning 警告刷屏还容易在审查时被安全问题打回来。更好的做法是在 config 里加一个ssl_verify开关测试环境关掉正式环境打开同时用 warnings 模块过滤掉 InsecureRequestWarning。这样既不影响开发效率也没有安全审查风险。7.3 pytest 用例发现规则没配置对线上漏跑了一堆用例默认情况下 pytest 会匹配test_*.py文件里的test_*函数。如果你把用例文件命名为api_*.py或者类名不带 Test 前缀pytest 根本不会收集它们。更隐蔽的是当目录下有个__init__.py时pytest 的 module 命名规则会发生变化可能导致用例收集失败。我建议在 pytest.ini 里显式声明用例发现规则不要依赖默认值[pytest] python_files test_*.py python_classes Test* python_functions test_* testpaths test_cases7.4 全局变量污染session 级 fixture 在并发模式下被改乱session 级 fixture 里如果放了一个可变对象比如一个字典缓存登录 token并发执行时多个 worker 同时读写轻则数据错乱重则直接把 session 搞挂。我之前遇到过一次四个 worker 并发跑有时两个 worker 之间互相覆盖了 token导致一半用例认证失败。解决办法是session 级 fixture 里的可变状态尽量设计成只读如果确实需要跨线程共享用线程锁保护。7.5 测试数据残留跑完不清理下次就出脏数据接口测试最大的隐性成本是数据管理。每次跑完用例如果都在数据库里残留一堆自动化测试用户、测试订单时间长了这些脏数据会影响其他测试或者触发接口本身的逻辑异常。我的习惯是每条用例创建的测试数据不管成功还是失败都在用例的 teardown 阶段删除。删除失败的场景要有日志方便定期清洗。8. 从第一天起就值得养成的三个习惯技术细节讲了这么多最后说点软性的东西。这些不是直接写进代码里的东西但决定了框架能走多远。第一个习惯是用例即文档。写接口用例的时候不要只写断言把前置条件、操作步骤、预期结果都描述清楚。这样后来接手的人包括三个月后的你自己能快速理解这条用例在测什么。第二个习惯是报告要有人看。自动化测试的价值不在于跑了多少条用例而在于发现了多少问题、拦截了多少回归缺陷。如果报告生成出来没有人看那这套框架再完美也是自嗨。所以我会在每次发版前把全量回归报告发到技术群里用数据说话让团队对自动化测试建立信任。第三个习惯是要持续重构。框架不是一天建成的也不是一成不变的。接口在变业务流程在变团队也在变。每过一两个迭代我都会回来看一遍框架里哪些地方不合理哪些用例冗余了哪些公共方法可以合并。测试代码和业务代码一样需要持续维护。接口自动化测试框架的落地是一个从简单的脚本开始逐步被需求推着演进的过程千万不要一开始就追求大而全。先把最核心的一条链路跑通让团队看到它带来的效率提升再一步步把数据驱动、报告、CI 这些能力加进去。这个过程会比想象中慢但只要坚持迭代回报一定会超过预期。
返回列表