从零搭建自动化测试框架:Python+pytest+requests实战指南
发布时间:2026/9/8 5:15:06 锦皓数字建站

说个我自己的经历。之前在一个项目里做回归版本迭代一次涉及十几个接口的改动我手工点了一遍又一遍眼睛都花了结果上线前还有一个老接口悄悄变了返回值第二天又得全部重跑一遍。就是从那次开始我下定决心把自动化测试这块真正捡起来也才有了这篇文章——软件测试专栏的第5篇主题是自动化测试入门从零开始构建你的第一个测试框架。这篇文章会带着你从零搭一个最小可用的自动化测试框架技术栈选用目前测试圈最常见的 Python pytest requests 组合。不管你刚入行还是做了几年手工测试想转自动化只要照着下面几节一步步操作都能在本机跑通第一个属于自己的测试项目。等这套框架跑通之后再往里面加接口测试、UI测试、持续集成心里就有底了。1. 先别急着写脚本想清楚自动化测试框架要解决什么问题很多初学者一上来就抱着 Selenium 疯狂写页面点击脚本结果写了两周发现页面一改脚本全挂于是得出“自动化测试没啥用”的结论。其实问题不在自动化本身而在场景选错了。自动化测试不是万能的它适合做那些“重复、稳定、量大”的回归验证不适合用来替代探索性测试和一次性的临时验证。1.1 不是所有场景都适合自动化先分清“适合”与“不适合”先列一下我自己的判断标准这也是我在多个项目里踩过坑之后总结出来的。适合自动化的场景通常有几个特征接口数量多且相对稳定、版本回归频繁、测试数据可以构造、执行结果可以被自动校验。反过来不适合自动化的场景也很明显界面还在频繁改版、业务流程本身不稳定、纯探索性测试、一次性的冒烟验证这类情况手工执行反而更快更灵活。举个例子如果你所在的项目两个星期一次迭代每次上线前要回归 50 个接口手工点一轮至少大半天这时候自动化测试的收益就非常明显。而如果项目还在原型阶段页面交互一天一个样你花两周写的 UI 脚本下周就要改一半投入产出比就非常低。另外还要注意一个常见误区不要试图把自动化覆盖率做到 100%。我见过有的团队为了追求覆盖率连一个弹窗文案都要写成断言结果线上每次改个错别字都要跟着改脚本维护成本远大于收益。自动化追求的是把核心业务路径和高频回归场景稳住不是替代所有手工测试。1.2 一个最小可用框架的核心构成用例、执行、断言、报告再往深一点讲一个能真正用起来的测试框架无论多简单都绕不开四个核心模块。第一是用例管理用目录、文件、标记来组织测试用例让上千条用例不混乱想跑哪一批就跑哪一批。第二是执行驱动负责决定哪些用例运行、以什么顺序运行、失败后是继续执行还是停下来这个能力决定了框架在项目中能不能规模化使用。第三是断言校验对接口返回值、页面结果进行自动比对判断测试是通过还是失败。第四是结果输出生成测试报告记录执行时间、失败原因、错误日志让结果可追溯。用一个生活化的类比用例管理就像菜谱执行驱动就是厨师断言是尝味道的人结果输出是给客人的上菜记录。四者配合才能把一道菜稳定地做出来。很多初学者搭建所谓的“框架”时只写了用例和执行断言和报告随便糊弄结果跑完一看全绿实际业务早就挂了这就是框架不完整带来的假象。所以这篇文章在实操环节设计目录结构时会把这四个模块全部安排进去保证你从第一天开始就建立一个完整的框架认知而不是东拼西凑地写脚本。1.3 为什么我建议你从接口自动化切入而不是 UI 自动化刚接触自动化测试的新人很多人的第一反应是“自动化 模拟用户点击页面 UI 自动化”。说实话我并不反对 UI 自动化但我不建议把它作为第一个框架。接口自动化和 UI 自动化的差异非常明显我从稳定性、执行速度、维护成本、入门门槛、适合场景几个维度做了个对比。对比项接口自动化UI 自动化稳定性高接口很少大改动低前端样式一变就挂执行速度秒级分钟级起步维护成本低接口变化频率相对低高元素定位常出差错入门门槛低会 HTTP 协议基本概念就行中还要会元素定位、等待策略适合场景CI 回归、全量回归、冒烟测试核心流程验证、端到端测试接口自动化还有一个容易被忽略的优势就是它跑通之后会给你积累一套可复用的 HTTP 请求封装、数据管理和报告体系。后面再做 UI 自动化时直接继承这套能力没必要从零再来一遍。所以我认为接口自动化是性价比最高的入门切入点这也是本篇文章后半部分实操全部围绕接口测试来展开的原因。2. 技术选型为什么是 Python pytest requests 这套组合选型这件事我从来不追新原则只有一条社区活跃、资料多、团队里有人会用。基于这个原则接口自动化框架我推荐 Python pytest requests。这套组合在测试圈几乎是事实标准各大公司的测试岗位 JD 里出现频次也最高跟着它入门后续查资料、找人请教都容易。2.1 pytest 为什么成了测试圈的事实标准pytest 之所以能取代 unittest 成为主流测试框架主要有三点。第一是 fixture 机制它可以在测试用例执行前后自动做准备工作比如申请测试数据、初始化数据库连接、生成登录 token用完后自动清理。这个设计比 unittest 里那个 setUp/tearDown 灵活得多fixture 的作用域还能控制到模块、类、整个会话级别写起来非常直观。第二是参数化。一个测试用例可以传入多组数据循环执行这在接口测试里几乎天天用。比如一个查询接口要覆盖正常参数、边界参数、非法参数不需要把每种情况都拆成一个函数一行装饰器就能搞定。第三是插件生态pytest-html、allure-pytest、pytest-rerunfailures、pytest-xdist 这些插件能直接解决报告、重试、并发执行这些高频需求。入门阶段你可能感受不到插件带来的好处等框架运行一段时间后稳定性、报告展示、并发加速这些问题都会遇到到时候你就能体会到为什么 pytest 能活得这么好。2.2 requests封装一次受用很久requests 库是 Python 生态里最通用的 HTTP 客户端接口自动化框架基本都拿它做底层请求。几个关键点我强调一下。第一用 requests.Session() 可以保留下一次请求的 cookie很多业务系统的登录态都靠 cookie 维持所以一个 session 对象应该在测试会话里被复用。第二务必设置 timeout 参数避免请求一直挂起导致整个测试卡死。我在实际项目里见过不少测试脚本卡在某个接口上几十分钟不动排查到最后就是忘记设超时。第三响应统一用 resp.json() 解析不要直接用文本字符串去断言。直接拿文本比对非常脆弱一旦返回结果里字段顺序变化、编码出现偏差明明接口功能正常断言却莫名其妙地失败。正确做法是用 JSON 格式解析后按字段取值后再判断。另外构建请求时优先使用 params 和 json 这两个参数来传数据而不是手动拼接 URL 或者写一大段 data 字符串这样代码可读性好也更容易维护。2.3 测试报告从能跑到看得懂这一步不能省测试报告是自动化框架里最容易被忽略的一环。我见过不少团队用例写了不少跑完只有黑底白字的终端输出领导看不懂测试结果也留不下记录出了问题连是哪条用例挂的都不知道。入门阶段我建议用 pytest-html 就够了在 pytest.ini 里配置好每次执行自动生成一份 HTML 报告里面包含用例数、通过失败数、失败堆栈浏览器打开就能看清爽直观。如果团队对报告展示有更高要求再上 Allure。Allure 支持步骤标注、附件、历史趋势适合做平台看板展示。接入方式也不复杂装一个 allure-pytest执行时加上 --alluredir 参数最后用 allure generate 生成页面。不过入门期没必要一上来就追求 Allure先能把 pytest-html 用明白把“每次跑完能说清楚通过率多少、失败在哪个环节”这件事做到就比很多项目强了。3. 手把手搭建从零构建你的第一个测试框架这一节我们直接动手。我用一个公开的 HTTP 模拟服务httpbin.org做演示环境它会把请求参数原样返回非常适合第一次跑通框架。你可以照着下面的步骤敲一遍感受一个最小框架从项目创建、编写代码到跑出报告的完整链路。3.1 项目目录结构一进来就知道东西该放哪很多初学者写自动化脚本时所有文件堆在一个目录里test_login.py、utils.py、config.py 全混在一起项目一复杂就乱了。我的建议是项目初始化时就按职责分好目录结构清晰了后面扩展才不痛苦。auto_test/ ├── common/ # 公共封装层 │ ├── __init__.py │ ├── http_client.py # HTTP 请求封装 │ └── logger.py # 日志模块 ├── testcases/ # 测试用例层 │ ├── __init__.py │ ├── conftest.py # pytest fixture 定义 │ └── test_demo.py # 示例用例文件 ├── reports/ # 测试报告输出目录 ├── requirements.txt # 项目依赖清单 ├── pytest.ini # pytest 配置文件 └── .env # 环境变量配置这个目录结构参考了行业里比较常见的数据与用例分离、公共层与用例层分离的思路。common 放公共封装testcases 放测试用例.env 管理环境配置reports 放生成结果。等以后用例多了你还可以在 testcases 下按业务模块再拆分子目录比如 user、order、payment每个模块放自己的 test_ 文件。3.2 安装环境与依赖先把地基打好在开始写代码之前先确认本机装了 Python 3.8 及以上版本。然后创建一个虚拟环境把依赖都装好。虚拟环境的作用是隔离项目的第三方库避免不同项目之间的包版本互相干扰这一步建议所有项目都养成习惯。cd auto_test python -m venv venv source venv/bin/activate # Windows 上用 venv\Scripts\activate pip install -r requirements.txtrequirements.txt 里的内容可以先写这几项版本号不用完全照抄挑你当前环境兼容的版本就行pytest7.4.0 requests2.31.0 pytest-html4.0.0 python-dotenv1.0.0装完之后可以用 pip list 检查一下是否安装成功。后面如果要用 Allure再补一个 allure-pytest入门阶段有前面这几个就足够跑起来了。3.3 写公共模块HTTP 封装与日志让用例保持干净公共层我建议先写两个模块HTTP 客户端封装和日志模块。HTTP 封装的作用是把 requests 的常用逻辑收拢到一个类里以后所有用例都走这个通道统一加超时、统一加日志不用每个用例都重复写 session 和管理超时。# common/http_client.py import requests class HttpClient: def __init__(self, base_url): self.base_url base_url self.session requests.Session() def get(self, path, **kwargs): url self.base_url path resp self.session.get(url, timeout10, **kwargs) return resp def post(self, path, jsonNone, **kwargs): url self.base_url path resp self.session.post(url, jsonjson, timeout10, **kwargs) return resp日志模块也很有必要。很多入门项目不写日志出了问题只能靠 print 猜测非常低效。我习惯在关键节点打日志比如“请求开始”“响应状态码”“断言结果”这样用例一多排查问题的时候看日志比看代码快得多。# common/logger.py import logging def get_logger(nameautotest): logger logging.getLogger(name) logger.setLevel(logging.INFO) if not logger.handlers: handler logging.StreamHandler() formatter logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s) handler.setFormatter(formatter) logger.addHandler(handler) return logger3.4 用 fixture 处理环境地址与客户端初始化环境地址的问题很多初学者会直接在代码里把 URL 写死比如 base_url https://httpbin.org。这个做法在换环境时会非常痛苦开发联调环境、测试环境、预发布环境地址都不一样改一处就要改整个项目。正确做法是把地址放到配置文件用 python-dotenv 加载。先说 conftest.py这个文件是 pytest 的 fixture 集中地。它在 testcases 目录下创建后该目录下所有测试用例都能自动读取。# testcases/conftest.py import os import pytest from dotenv import load_dotenv from common.http_client import HttpClient load_dotenv() pytest.fixture(scopesession) def base_url(): return os.getenv(BASE_URL, https://httpbin.org) pytest.fixture(scopesession) def client(base_url): return HttpClient(base_url)同时创建一个 .env 文件把环境地址写进去BASE_URLhttps://httpbin.org这里有个小技巧base_url 这个 fixture 设了默认值即使 .env 里配错了代码也能兜底。scopesession 表示整个测试会话只创建一次客户端Session 连接可以复用执行效率更高。以后换环境只需要改 .env 里的 BASE_URL其他代码一行都不用动。3.5 编写并运行你的第一条测试用例前面的准备都做好了现在写第一条测试用例。我们直接写两个用例一个 GET一个 POST覆盖参数传递和 JSON 响应断言两个基本能力。# testcases/test_demo.py import pytest from common.logger import get_logger logger get_logger() def test_get_request(client): resp client.get(/get, params{key: value}) assert resp.status_code 200 assert resp.json()[args][key] value logger.info(GET 请求验证通过) pytest.mark.parametrize(name, [tester01, tester02]) def test_post_request(client, name): resp client.post(/post, json{name: name}) assert resp.status_code 200 assert resp.json()[json][name] name这里用到了 pytest 的 assert 直接断言这也是我推荐 pytest 的原因判断逻辑写起来非常干净。第二条用例加了一个参数化装饰器同一段逻辑跑两遍覆盖不同数据组合这就是自动化框架里的“数据驱动”雏形。运行测试前先建好 pytest.ini。[pytest] testpaths testcases addopts -v --htmlreports/report.html --self-contained-html然后回到项目根目录执行pytest正常情况下你会看到两条用例三条结果因为参数化了一条变两条全部通过。如果这一步能跑通恭喜你的第一个测试框架已经立起来了。3.6 接入报告告别黑底白字跑通用例之后打开 reports 目录下生成的 report.html就能看到一份带排版、带统计的测试报告。pytest-html 在 pytest.ini 里通过 addopts 配置后每次执行 pytest 会自动生成报告不需要额外敲命令这一点非常省事。如果你已经跑熟了 pytest-html想上 Allure 做更专业的展示可以再补一个 allure-pytest 依赖执行时加上 --alluredirreports/allure_result最后用 allure generate reports/allure_result -o reports/allure_html 生成静态报告页面。Allure 的好处是支持在用例上打 allure.story、allure.feature 这样的标签报告里能按业务模块分组展示更适合做团队平台化看板。不过入门阶段先把 pytest-html 用好就足够了。4. 常见问题与排查技巧实录搭建框架只是第一步真正让它稳定跑起来的过程中你一定会遇到各种奇怪问题。这一节我把实际项目里经常出现的坑整理成一份速查表每一条都是真金白银踩出来的经验。4.1 断言失败但接口明明没问题先从返回结果查起接口在浏览器里或 Postman 里测得好好的脚本一跑却断言失败这种情况我几乎每周都会遇到。导致它的大部分原因有三个第一返回的 JSON 结构和小时候对接口的理解不一致你以为在根节点能拿到某个字段实际它嵌套在 data 里。第二编码问题接口返回了中文但脚本终端或断言用的字符串编码不一致。第三接口有动态字段比如时间戳每次请求都不一样你拿固定的值去比自然就挂了。排查思路很简单断言失败时先把实际响应内容打到日志或报告里不要只看一个 Red 的断言结果。我习惯在每次请求返回后至少打印一次状态码和响应摘要这样定位问题会快很多。断言尽量围绕着稳定的字段去做比如业务状态码、明确的数据项尽量不要把动态值放进断言条件里。4.2 测试数据互相污染两条用例单跑都过一起跑就挂这是自动化测试里一个比较典型的坑。比如你写了一个注册接口的用例用固定的手机号第二次执行时这个手机号已经被注册了用例就过不了。又或者 A 用例创建了一条订单数据B 用例在查询时假设这条数据一定存在执行顺序一改变B 用例就挂。解决思路是让每条用例尽量独立。测试数据要做到“唯一化”手机号、用户名这些可以用时间戳或者 uuid 拼出来保证每条数据在时间维度上不重复。再就是在 fixture 里用 yield 写 teardown用例执行完自动清理数据像创建订单这类用例跑完后把订单删掉或改成废弃状态避免污染下一轮执行。数据独立性这件事代码层面不需要多高级的架构关键是意识要到位。4.3 环境切换搞死人配置管理的正确姿势很多项目到了一个阶段就会发生这样的事测试环境跑得好好的用例切到预发布环境就挂一片。最后发现代码里有三五个地方把测试环境地址写死了换个环境得全局搜索替换既费劲又容易漏。这个问题的根治办法就是所有环境相关的配置一律放配置文件代码里不出现任何硬编码的环境域名。我在前面实操部分放了一个 .env 的例子就是解决这个问题的。把 BASE_URL、数据库地址、账号密码这类环境相关的内容全部抽离用 os.getenv 加载。这样不仅换环境方便团队协作时也不会因为某个人本地配置不同导致莫名其妙的结果不一致。还有一个细节不同环境的账号密码大概率不一样建议配置项里都分开管理并且敏感信息不要提交到公共代码仓库.env 文件要加进 .gitignore。4.4 偶发超时和网络抖动稳定性优化框架跑一段时间后你会发现偶尔会有用例因为网络波动失败比如 requests 抛 connect timeout但重跑一遍又通过了。这类不稳定用例如果不处理会严重消磨团队对自动化的信任感本来一小时能跑完的回归总有人要反复重跑确认。我一般从两个方向去优化。第一是请求层统一设置合理的 timeout上面封装 HttpClient 时我已经写了 10 秒这个值不是越大越好要根据接口实际响应时间去定通用场景 10 秒左右偏保守。第二是接入 pytest-rerunfailures对偶发的接口超时做重试。用法简单先 pip install pytest-rerunfailures然后在 pytest.ini 里加 --reruns 2 --reruns-delay 1意思是失败后等一下再重跑两次。要注意重试不能滥用只对网络请求这类偶发因素做重试业务断言失败不要重试否则真实问题会被掩盖。为了让你更直观地排查问题我还整理了一张速查表症状常见原因解决思路接口正常但断言失败JSON 结构动态变化、编码不一致解析 JSON 后按字段断言打印实际响应两条用例一起跑就挂测试数据互相污染数据唯一化fixture 增加 teardown换环境后大量失败base_url 写死在代码里用 .env 统一管理环境配置偶发 connect timeout网络抖动设置 timeout接入失败重试机制用例执行顺序变了结果就变用例之间存在硬依赖前置逻辑收敛到 fixture用例保持独立最后再聊点个人的体会。框架这个东西没必要一开始就追求大而全我最早搭框架的时候也就几十行代码目录混乱日志没有后来是一个用例一个用例地跑、一个坑一个坑地填慢慢才变成现在能支撑多个项目日常回归的样子。刚接触自动化的朋友记住一句话先能跑再跑稳最后才是跑好看。跑通之后可以研究怎么接入持续集成怎么用数据驱动覆盖更多场景也可以在接口框架之上再叠一层 UI 自动化。如果你卡在搭建过程中某个环节最有效的办法就是打开终端把报错信息完整读一遍大部分问题其实在报错里已经给了你答案。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。