资讯详情

Python接口自动化测试实战:从requests到pytest框架搭建

📅 2026/10/10 10:17:26 | 华诺云谱 👁 阅读
Python接口自动化测试实战:从requests到pytest框架搭建
做接口自动化测试这件事其实没有想象中那么玄乎。我见过不少从功能测试转岗的朋友一听到“Python接口自动化测试”就先打退堂鼓觉得是不是得啃什么高大上的框架才能上手。实际做下来你会发现核心就是把请求发出去、把响应拿回来、按预期做断言这三步反复跑就能解决大部分回归问题。Python在这块生态特别完善requests库加pytest就能撑起一套可用的接口自动化体系再配合数据驱动和报告输出日常项目回归完全够用。这篇文章适合谁刚接触接口测试的测试工程师、想把手里的手工脚本整理成框架的同学还有准备软件测试面试想补一块硬技能的。我会从环境准备一路讲到框架搭建每个步骤都尽量写清楚“为什么要这么做”而不是扔一段复制粘贴就完事的代码。1. 接口自动化测试到底在解决什么问题1.1 先说说为什么是接口很多刚入行的测试同学有个误区觉得自动化就必须在界面上点点点。UI自动化看起来直观但维护成本高得吓人页面稍微调整一下脚本就全崩了。而接口层相对稳定业务逻辑的核心都在接口里。拿登录来说UI上的登录框怎么改后端验证用户名密码的接口基本不会大动。所以接口自动化测试特别适合做回归、做核心场景验证、做上线前的快速冒烟能帮你在短时间内把关键业务链路都过一遍。从成本角度看接口测试也比UI测试划算太多。借用我一直跟团队说的一句话接口是后端对外承诺的契约契约不变前端怎么折腾都不影响接口层的验证结果。这也是为什么很多公司把接口自动化测试作为CI流水线里的第一道关卡跑挂了就直接阻止合并从源头止损。1.2 接口自动化的三个核心动作不管用什么工具、什么框架接口自动化测试本质上就三个动作发送请求按照接口文档把方法、URL、参数、请求头拼好发给服务器。解析响应把服务器返回的JSON、XML或纯文本拿过来转成Python能处理的对象。做断言把响应里的关键字段跟预期值比较判断接口是否符合业务预期。三个动作听起来简单但实际做起来有很多细节。比如发送请求时要考虑鉴权怎么带、Token会不会过期解析响应时要注意字段是嵌套结构还是数组结构断言也不能只比对状态码还要关心业务码、关键字段、响应时间。这篇文章后面会逐个拆开讲。1.3 需要哪些基础储备如果你完全零基础建议先花一周时间把Python基础语法过一遍。不需要多深能看懂函数、字典、列表能写简单的for循环和if判断就足够了。毕竟接口自动化不太涉及复杂的算法更多是跟数据结构和网络请求打交道。需要掌握的几个点字典和列表的取值操作因为响应报文的解析主要靠它们。函数定义和参数传递因为封装请求方法时会大量用到。文件读写和JSON处理因为测试数据和配置通常放在外部文件里。异常处理因为网络请求总会遇到超时、断连这些意外情况。如果你已经会这些直接跳到下一节开始搭环境。2. 环境准备用最少的时间把环境跑起来2.1 Python安装的几个坑Python安装本身不复杂去官网下载安装包下一步下一步就完事。但有几个细节特别容易踩坑这里先提前说清楚第一安装时一定要勾选“Add Python to PATH”这个选项。很多人装完Python之后在命令行里敲python提示找不到命令基本都是因为没勾这个。如果已经装完了才发现没加进PATH也可以手动去系统环境变量里把Python安装目录和Scripts目录加上。第二建议装3.8以上的版本。虽然Python 2早就退休了但网上仍然能看到一些老教程用Python 2的语法写接口测试代码比如print后面不加括号这种。你现在学的话直接装3.10或更新的稳定版就行别回头学旧语法。第三装完记得验证一下。在命令行里分别执行python --version pip --version能正常输出版本号说明环境基本没问题。如果pip提示版本旧可以顺手升级一下。python -m pip install --upgrade pip2.2 安装必要的库接口自动化测试最核心的库就是requests它把HTTP请求的各种细节都封装好了相比Python自带的urllib代码简洁得多也直观得多。安装也简单pip install requests除了requests建议把pytest也一起装上后面组织测试用例和生成报告都会用到pip install pytest如果希望测试报告更好看可以再加一个allure-pytest插件。allure报告是目前测试行业用得比较多的报告方案界面好看统计信息也全。安装命令一样pip install allure-pytest不过第一次跑项目先装requests和pytest就够了。allure等框架搭起来之后再补不用一口气装太多东西不然容易把自己绕晕。2.3 虚拟环境每个项目都有自己的依赖这一点新手容易忽视但实际工作中很重要。虚拟环境的作用是让每个项目有独立的依赖包目录互不干扰。不然你今天装了这个库的1.0版本明天另一个项目需要2.0版本就会冲突。创建虚拟环境很简单python -m venv venvWindows下激活venv\Scripts\activateMac/Linux下激活source venv/bin/activate激活之后命令行前缀会出现(venv)字样这样你再pip install任何库都只会装到这个虚拟环境里不会污染全局。项目不需要了直接删掉venv目录就行。3. 核心操作用requests发送你的第一个接口请求3.1 GET请求实战先来一个最经典的GET请求。假设后端有一个查询用户信息的接口返回JSON格式的数据请求路径是/api/user/1001完整代码就这么几行import requests url https://api.example.com/api/user/1001 response requests.get(url) print(response.status_code) print(response.json())这段代码干了两件事把请求发出去然后把响应内容解析成JSON打印出来。response.status_code拿到HTTP状态码200表示成功404表示资源不存在500表示服务器出问题了。response.json()则是把响应的JSON字符串直接转成Python字典后面断言就方便了。3.2 POST请求实战POST请求通常用于创建资源或提交数据操作相比GET多了一个请求体。比如创建一个订单的接口import requests url https://api.example.com/api/order payload { user_id: 1001, goods_id: A1024, quantity: 2, remark: } response requests.post(url, jsonpayload) print(response.status_code) print(response.json())注意这里用了jsonpayload参数requests会自动把Python字典序列化成JSON格式的字符串同时把Content-Type请求头设置为application/json。这么写是最省事的不用手动做json.dumps操作。3.3 请求头、参数与超时处理实际工作中的接口不会这么干净。很多接口需要带Token来鉴权需要在请求头里额外传递字段比如import requests headers { Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., Accept: application/json } url https://api.example.com/api/user/1001 response requests.get(url, headersheaders, timeout5) print(response.json())这里有几个细节要说明一下。timeout5表示请求超过5秒就抛异常这个必须设置。不设超时如果服务器出问题一直不响应你的脚本就会一直等下去把整个测试卡死。建议根据业务情况设置3到10秒。再一个需要注意的是有些接口的查询参数不是拼在URL后面而是通过params参数传递import requests url https://api.example.com/api/user params { page: 1, size: 20, keyword: 张三 } response requests.get(url, paramsparams)requests会把字典自动拼成/api/user?page1size20keyword%E5%BC%A0%E4%B8%89这样的查询串。这么做的好处是参数有结构方便维护也能避免手动拼接时忘记转义特殊字符。4. 从脚本到框架把请求方法封装起来4.1 为什么要封装刚开始写接口自动化代码很自由想怎么发请求就怎么写。但一旦用例多起来你会发现同样一段发请求的代码反复出现在各个用例里只是URL和参数不同而已。这时候就需要封装把公共逻辑抽取出来统一维护。这个道理跟你写业务代码一样不要有三个地方出现相同逻辑却各自实现。封装之后如果需要在每个请求里加公共请求头比如统一加密参数只需要改一个函数就够了不用满项目找代码改。4.2 封装一个简单的RequestHandler下面给一个基础的封装示例。这个类比较简单但已经能覆盖大多数场景import requests class RequestHandler: def __init__(self, base_url, headersNone, timeout5): self.base_url base_url self.headers headers if headers else {} self.timeout timeout def get(self, path, paramsNone): url self.base_url path response requests.get(url, paramsparams, headersself.headers, timeoutself.timeout) return self._handle_response(response) def post(self, path, jsonNone, dataNone): url self.base_url path response requests.post(url, jsonjson, datadata, headersself.headers, timeoutself.timeout) return self._handle_response(response) def _handle_response(self, response): try: result response.json() except ValueError: result response.text return result if __name__ __main__: handler RequestHandler(https://api.example.com) data handler.get(/api/user/1001) print(data)这个封装好在哪你可以把base_url放在构造函数里统一配置每个测试用例只需要关心接口路径和参数不用重复写请求头、超时这些琐碎的东西。_handle_response方法统一处理响应格式的解析保证返回的数据结构是可控的。如果想支持PUT、DELETE这些方法照葫芦画瓢再写两个函数就行。4.3 配置文件与用例分离第二个该做的工程化改造是把环境相关的配置放到配置文件里。比如测试环境、预发布环境的域名不一样接口路径可能也不同。把这些硬编码在代码里每次切换环境都要去改代码太麻烦。通常的做法是建一个config.yaml或者config.json{ test: { base_url: https://test-api.example.com, timeout: 5 }, pre: { base_url: https://pre-api.example.com, timeout: 5 } }然后在代码里读取配置import json with open(config.json, r, encodingutf-8) as f: config json.load(f) env test base_url config[env][base_url] handler RequestHandler(base_url)这样做的好处是环境切换只需要改一个变量或者通过命令行参数传入测试用例代码完全不用变。这在后面接入CI流水线时尤其重要不同的Job可以跑不同的环境。4.4 从接口文档到测试用例封装好请求之后下一步就是把接口文档里的每个接口转成测试用例。以登录接口为例接口文档里会写明请求方法、URL、参数、返回结构。根据文档可以设计出这样一组测试用例正常登录返回业务码0和用户信息密码错误返回业务码1001和错误提示用户不存在返回业务码1002缺少必填参数返回参数校验错误这里的关键点是接口测试用例设计的核心不是校验HTTP状态码而是校验业务返回码。有时候用户密码错误HTTP状态码依然是200因为请求本身是通的只是业务上认证失败。判断接口是否正确必须看业务码。5. 断言设计与pytest实战5.1 断言怎么写才靠谱我把断言看作接口自动化测试的灵魂。请求发出去、响应拿回来最终总要有人来判定“这次测试是过了还是挂了”。Python里最常用的就是assert语句assert response.status_code 200 assert data[code] 0 assert data[data][user_name] 张三但这里想提醒一个常见误区很多人只断言状态码为200然后就不管了。200只能说明HTTP请求成功并不能说明业务正确。更合理的断言方式是分层HTTP状态码确认网络层没有4xx、5xx错误。业务返回码确认业务逻辑是否符合预期。关键业务字段确认响应的核心内容完整且正确。关键流程状态如果接口涉及状态流转比如下单、支付、退款要把流转后的状态也断言。我在测试团队里定过一个规矩纯状态码断言不算有效断言必须至少覆盖业务码和关键字段。这样做之后漏测率明显下降因为很多接口HTTP 200但业务码在报错如果只断言200问题就被漏过去了。5.2 用pytest组织测试用例pytest是目前Python生态里最主流的测试框架它跟unittest比代码更简洁fixture功能也更强大。用pytest管理接口测试用例目录结构通常长这样testcases/ ├── test_login.py ├── test_order.py └── test_user.py一个登录模块的测试用例用pytest写出来是这样的import pytest from handler import RequestHandler handler RequestHandler(https://test-api.example.com) def test_login_success(): response handler.post(/api/login, json{ username: zhangsan, password: 123456 }) assert response[code] 0 assert response[data][token] ! def test_login_wrong_password(): response handler.post(/api/login, json{ username: zhangsan, password: wrongpass }) assert response[code] 1001 assert 密码错误 in response[message]pytest会自动收集文件名以test_开头或将test开头的函数作为测试用例执行时在项目根目录运行pytest -v-v参数可以看到每个用例的通过情况。5.3 参数化实现数据驱动如果登录用例要把几十组账号密码都跑一遍总不能复制粘贴几十个函数。这时要用pytest的参数化功能import pytest from handler import RequestHandler handler RequestHandler(https://test-api.example.com) TEST_DATA [ (zhangsan, 123456, 0, 登录成功), (zhangsan, wrong, 1001, 密码错误), (lisi, 123456, 1002, 用户不存在), (wangwu, , 1003, 参数缺失), ] pytest.mark.parametrize(username,password,expected_code,expected_msg, TEST_DATA) def test_login(username, password, expected_code, expected_msg): response handler.post(/api/login, json{ username: username, password: password }) assert response[code] expected_code assert expected_msg in response[message]这样一个用例就能跑四组数据。后面要加数据只需要在TEST_DATA列表里加一条测试用例代码不用变。这才是“数据驱动”的核心思想测试逻辑和测试数据分离数据变而逻辑不变。更进一步的可以把测试数据存到Excel或JSON文件里用代码动态读进来。但很多小型项目做到参数化列表就够用不一定非要上Excel。我见过不少团队为了Excel化而Excel化数据格式处理的时间比写测试用例还长这就本末倒置了。5.4 生成可读的测试报告测试报告是给谁看的不只是给自己看更多是给项目组、给领导看的。pytest自带的输出信息比较朴素建议搭配allure生成更直观的报告。安装好allure-pytest之后执行pytest -v --alluredirreport然后本地预览报告allure serve reportallure报告里有几个信息特别有用用例总数、通过率、失败用例的完整请求日志、执行耗时。调试的时候直接从报告里点开失败用例能看到请求参数和响应内容定位问题非常方便。allure本身需要安装allure命令行工具这一步网上有很多教程不同系统安装方式不同。如果公司不方便装allure也可以先用pytest的html插件pip install pytest-html pytest -v --htmlreport.html生成的report.html单文件打开就能看胜在零配置适合快速交付。6. 常见问题与排查技巧实录6.1 请求头缺了什么导致接口报错做一个新增数据的接口时我遇到过这样一个问题用postman测接口都正常一旦换成Python脚本就报参数错误。排查了很久最后发现是请求头的问题。接口要求Content-Type为application/x-www-form-urlencoded但requests的json参数会自动设置成application/json后端不认直接拒绝了。解决办法是明确指定data参数和请求头import requests url https://api.example.com/api/add data user_namezhangsanage18 headers { Content-Type: application/x-www-form-urlencoded } response requests.post(url, datadata, headersheaders)这个案例想提醒的是接到接口文档后第一件事就是看清楚请求头的要求。别盲目用json参数也别盲目照搬postman的格式。postman能通只是一种参考最终要以代码发出的真实报文为准。遇到这种问题最直接的排查方法就是打印请求的实际内容用response.request.headers看看发出的请求头长什么样。6.2 SSL证书校验报错内网测试环境经常遇到自签名证书requests默认会验证SSL证书然后报错requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]测试环境的证书可能没配好导致每次跑脚本都要被这个错误拦一下。处理方式是在request请求中设置verifyFalseresponse requests.get(url, verifyFalse, timeout5)同时会有个警告提示可以用以下代码屏蔽import warnings import urllib3 warnings.filterwarnings(ignore) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)这里要提个醒verifyFalse只建议在测试环境使用线上环境还是要把证书校验打开否则容易埋下安全隐患。如果你在线上环境遇到这个问题正确做法是更新证书或让运维处理而不是粗暴地关闭校验。6.3 响应乱码问题接口返回JSON但打印出来是一堆乱码大多数情况是编码问题。requests会根据响应头里的Content-Type来猜测编码但有些接口不声明或声明错了requests猜不出来。处理方式是指定编码response.encoding utf-8 text response.text或者更省事的方式直接用response.json()解析requests解析JSON时一般能自动处理编码。如果.json()也报错那大概率是接口返回的JSON不合法可以先打印原始文本看看。6.4 Token失效导致偶发失败接口鉴权一般用TokenToken通常是限时间的。测试用例跑着跑着突然一大片失败一看日志全是401。这种情况大概率是Token过期了。解决方案有几个思路写一个自动获取Token的fixture在pytest里每个测试用例执行前都检查Token是否过期过期就重新登录拿新Token。用pytest的session级fixture在整个测试会话开始时统一获取Token会话结束后再失效。缺点是一跑就是一两个小时Token中途照样会过期。把Token的获取和过期时间封装在请求类中每次发请求前动态判断。我的经验是最靠谱的是第三种思路把Token管理收敛到层import time class TokenManager: def __init__(self, username, password): self.username username self.password password self.token None self.expire_time 0 def get_token(self): if time.time() self.expire_time: response login(self.username, self.password) self.token response[data][token] self.expire_time time.time() 3600 return self.token然后把TokenManager传给请求Handler每次发请求时先调用get_token()。这样Token就实现了自动续期不用跑两个小时后手动去改代码。这种问题在长周期回归测试中特别常见如果不提前处理半夜跑出来的失败报告会让你欲哭无泪。6.5 断言结果总是不稳定还有一种让人抓狂的场景同一个接口用例有时候通过有时候失败也没有规律。排查这类问题我一般先确认是不是响应时间不稳定。如果响应在3秒和8秒之间反复横跳那大概率不是测试用例的问题而是服务本身存在性能瓶颈。这种情况建议先在pytest里设置超时再配合日志判断是偶发还是持续。另一种可能是数据问题。比如你用固定的一组账号密码去跑测试但账号的状态可能在某个前置用例里被修改了——比如前面有一个用例把账号禁用了后面的登录用例自然就失败。这类问题一定要回头检查用例之间的依赖关系。接口自动化测试里用例之间尽量保持独立不要依赖执行顺序。如果依赖就在用例内部自己准备数据别把状态留着给后面的用例用。这个坑我踩过很多次。最开始写接口测试习惯把用户注册、登录、下单放在一串用例里执行注册成功就认为后面能用。结果某次注册接口失败后面全崩排查了半天才知道是数据污染问题。后来我给自己定了个规矩每个用例都自己造自己的数据用例结束时该清理的清理不做共享状态的假设。7. 进阶方向从能跑到能调优7.1 接口自动化的分层设计当用例数量超过几十个时代码的组织方式也需要升级。我常用的分层思路是基础层封装请求发送、日志记录、配置读取、数据库操作。业务层把一个个接口调用封装成业务方法比如login()、create_order()。用例层只负责描述测试场景调用业务方法并做断言。数据层管理测试数据比如Excel、JSON、YAML文件。这样分层之后用例层非常干净一眼就能看出这个用例在测什么。需求一变修改范围也基本限定在业务层和数据层用例层很少要大改。7.2 日志的重要性很多人写接口自动化不写日志出了问题全靠print。print在调试阶段够用但跑完一夜的回归测试你不会想在一堆printf输出里翻找某个用例的请求记录。建议在项目里引入logging模块把关键信息输出到文件import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(test_run.log), logging.StreamHandler() ] ) logger logging.getLogger(__name__) logger.info(请求开始POST /api/login) logger.info(请求参数%s, payload) logger.info(响应内容%s, response_text)有了日志出问题时你能清楚看到哪个环节出了问题是请求参数不对还是响应解析失败还是断言逻辑错误。日志和测试报告配合排查效率提升不止一倍。7.3 接入CI流水线接口自动化测试的最终归宿是持续集成。测试脚本在本地跑得再好如果每次都要手动执行价值就大打折扣。接入CI之后每次代码提交或者定时触发流水线自动拉取代码、安装依赖、执行用例、生成报告、发送通知一条龙完成。当前主流的CI工具比如Jenkins、GitLab CI都支持pytest接入本身不算难。接入时重点关注两个点一是环境的隔离谁触发用例用的是哪套环境的配置这个必须明确二是失败的处理用例失败后是阻断主线流水线还是只是发个告警需要根据团队场景决定。建议前期先做成告警模式观察一段时间稳定性再逐步加严。8. 一些个人体会接口自动化测试这份活入门容易做深难。入门只需要几行代码把请求发出去、拿到响应就觉得自己会了。但真正值钱的是后面这些细节如何让用例稳定可复现、如何让报告有说服力、如何让脚本适应环境变化、如何让项目从几十个用例扩展到几百个用例还能维护。这些没有标准答案只能靠一个个项目喂出来。我给新人的建议是先挑一个稳定的业务模块比如登录、注册、用户信息查询把这几个接口用本文的方式完整跑通。不要贪多先把链路跑顺再逐步加接口、加用例、加场景。过程中遇到问题优先学会看日志、打印请求响应、对比postman差异。这三板斧解决绝大多数接口自动化的问题。最后分享一下我现在的工作习惯每接到一个接口自动化项目第一周先不做脚本把接口文档反反复复读透把业务场景梳理清楚把团队最关心的高频回归用例列出来。脚本是实现这些用例的工具不是目的。理清业务脚本怎么写都不会太歪。希望这篇文章能让你少走几步弯路。写到这儿我想到的最有用的就是一句话——动手去写遇到问题再解决比看二十篇教程都有用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑