ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从Postman到Python:接口自动化脚本入门与稳定执行指南

从Postman到Python:接口自动化脚本入门与稳定执行指南 如果你已经在用 Postman 或者 Jmeter 手动点接口那自动化脚本对你的门槛其实只有一层窗户纸。我经常在团队里看到这样的场景测试同学打开 Postman把登录接口的 URL、Header、Body 一个个填好点 Send拿到 token再复制到下一个接口。这套动作一天重复几十遍点得多了手比脑子还诚实。有人问过我自动化脚本是不是很难是不是得专门学代码才能写 我的回答从来都是自动化脚本没有你想象的那么玄它就是把你平时在 Postman/Jmeter 里手工点的接口请求用代码写成可重复执行的脚本。这篇内容适合两类人一类是已经在用 Postman/Jmeter 做接口测试、但总觉得自动化高不可攀的测试同学另一类是后端开发想把手动验证接口的过程沉淀成可回归的脚本。我会从最基础的认知讲起一步步拆解请求结构给出能直接跑的代码再讲清楚怎么让脚本从能跑变成稳定跑一万次最后把 Jmeter 里的场景也脚本化。全程不绕弯子都是可以复制的实操。1. 别把自动化脚本想玄了你点的每个请求背后都是一条HTTP消息1.1 手工点按钮的本质是什么在 Postman 里点 Send程序背后做的事情和你用代码发一个 HTTP 请求没有任何区别。你填写的 URL 是这趟请求的地址选择的 GET/POST 是请求方式Header 里放的是给服务端看的附加说明Body 里写的是要传给服务端的业务数据。服务端收到后返回一段响应文本Postman 帮你渲染成好看的 JSON 格式展示出来。这个链条用代码来表达就是requests.post(url, jsondata, headersheaders)这样一行。你之所以觉得手工点击很轻松是因为 Postman 替你承担了构造 HTTP 消息这个底层工作。但同样因为它是图形界面每一次点击都是全新的操作无法被记录、无法被回放、无法一键跑一百次。Jmeter 也是同一个逻辑。你在线程组里添加一个 HTTP 请求采样器配置域名、路径、参数然后点绿色三角运行。很多人用 Jmeter 做过压测却从没想过那个 HTTP 请求采样器里的每一项配置本质上翻译过来都是代码里的一个个参数。如果你理解了这层映射再看自动化脚本就不会觉得是在学一门新技能而是在做一次已知技能的代码化表达。1.2 Postman 里的每个手势对应代码里的什么我见过很多同学卡在同样的地方不知道自动化脚本该从哪里下手。我建议他们做一件事——把自己在 Postman 里的常规操作列出来一行一行翻译成代码。你会发现这个对照关系惊人的简单Postman 里的操作对应代码里选择 GET 或 POST 方法requests.get()或requests.post()在地址栏填写 URL给函数传入 url 参数在 Header 里填键值对传入headers{key: value}在 Body 里写 JSON传入json{key: value}提交表单数据传入data{key: value}点击 Send调用requests.request()发起请求看响应结果response.text或response.json()肉眼判断对不对assert断言举个例子在 Postman 里测试登录接口的时候你填的是 POST、https://api.example.com/login、Header 里写Content-Type: application/json、Body 里写{username: admin, password: 123456}。这段操作翻译成代码就是import requests url https://api.example.com/login headers {Content-Type: application/json} body {username: admin, password: 123456} response requests.post(url, jsonbody, headersheaders) print(response.json())就这么简单。你点 Send 后看到的东西全在这个response对象里。想验证 HTTP 状态码是 200就写assert response.status_code 200想验证业务返回码是 0就写assert response.json()[code] 0。这就是 Postman 里看响应、看断言结果的那个环节。1.3 同样的事情为什么用代码写一遍就值了手工操作和代码脚本表面上做的是同一件事但本质完全不同。你要理解这个值在哪里才不会觉得自动化是为了写代码而写代码。手工操作是一锤子买卖。你今天点一遍通过了明天想再验证一次还得重新打开 Postman 重新点。中间万一漏配了一个 Header或者复制 token 的时候少复制了后半段结果就是误导你的判断。自动化脚本写出后同样的动作可以回放一千次且每一次的请求内容都不会因为手抖而变形。脚本还天然解决了留痕问题。你手工点接口的时候如果没有人站在旁边看事后很难证明你测了哪些接口、每个接口的响应是什么。但脚本每执行一次请求和响应的日志会完整保留下来。这个能力在项目交付、问题定位、回归验证的时候极其有用——尤其是线上出了问题你能快速回答这个接口在上个版本是好的这次改动后开始报错。更重要的是脚本可以脱离人独立运行。它可以被 CI 平台在每次代码提交后自动触发可以写进定时任务在每天早上跑一遍也可以接入你现有的测试平台做数据统计。这是任何手工点击都做不到的。理解到这一层你才算真正理解了自动化脚本存在的意义。2. 请求结构拆解把 Postman 里的一条记录翻译成代码2.1 最快的路径让 Postman 先帮你生成代码如果你的手边已经有一个调通的 Postman 请求又不想从零开始写 Python 代码最快的办法是让 Postman 自己把代码生成出来。在 Postman 的请求编辑界面右上角有一个/图标点开后可以选择目标语言找到 Python Requests 这一项。它会自动把当前请求翻译成一段可执行的 Python 代码。我第一次用这个功能的时候坦白讲有点震惊因为它连 Header、Body 里的每个字段都给你填好了。对于拷贝整个请求结构这件事它比人肉手工抄写可靠得多。但要注意生成的代码有一个显著的问题它会把所有的值全部硬编码在代码里。URL 是写死的Header 的 token 是写死的Body 里的账号密码也是写死的。如果你直接拿去跑确实能跑通但换个环境、换个用户、换一台机器可能就废了。所以我的习惯是把 Postman 生成的代码当作翻译草稿在此基础上做三件事——把 URL 抽成变量把有生命周期限制的值token、时间戳、随机码改成动态获取把关键响应加上断言。这样既有 Postman 生成的准确性又有脚本该有的灵活性。2.2 请求的五个要素每个都要弄清楚再动手无论你用 Postman、Jmeter 还是直接写库HTTP 请求的核心都由五个要素组成。我在带人的时候要求他们必须能默写出这五样东西因为任何一个环节理解不到位脚本都会在某个时刻突然翻车。请求方法与 URL决定你要对哪个资源做什么操作。GET 通常用来查询POST 用来提交创建PUT 用来全量更新PATCH 用来局部更新DELETE 用来删除。Query 参数URL 问号后面的键值对比如?page1size20。在 requests 里用params{page: 1, size: 20}传入程序会自动帮你拼接并处理中文编码。如果你手动拼 URL 字符串碰到包含中文或特殊字符的参数还要自己处理 URL 编码非常容易出错。Headers这里面最核心的就是Content-Type它告诉服务端 Body 里的数据是什么格式。用requests的json参数发送 JSON 时库会自动帮你设置Content-Type: application/json很多新手不知道这一点自己又在 Header 里写了一遍结果一样但显得多余。Body不同接口对 Body 的格式要求不一样。最常见的是 JSON对应json{}另一种是表单格式对应data{}还有上传文件用的multipart/form-data对应files{}。如果你接口用的是 JSON却用data参数传了一个字典服务端可能解析不出来报 400 错误。认证与状态登录之后绝大多数接口需要携带 token 或 cookie证明你是已经认证过的用户。这里有一个新手经常踩的坑——以为每次请求都要手动把 token 塞进 Header。其实用requests.Session()之后登录接口返回的 cookie 会被 session 自动保存后续用同一个 session 发请求时cookie 会自动带上。2.3 一个真实案例从浏览器抓包到 Python 脚本我经常拿电商系统的登录后查询订单这个场景来演示完整流程。你在浏览器 F12 的 Network 面板里可以看到登录请求长什么样请求 URL 是https://api.example.com/loginMethod 是 POSTPayload 是{phone: 13800138000, password: abc123}Response 返回了{code: 0, data: {token: eyJhbGciOi...}}。把这个流程脚本化需要做三件事。第一用账号密码请求登录接口拿到响应里返回的 token。第二调用查询订单接口https://api.example.com/orders?page1Header 里带上Authorization: Bearer token。第三断言订单接口返回的数据结构符合预期。写成代码如下import requests base_url https://api.example.com session requests.Session() # 第一步登录 login_resp session.post( f{base_url}/login, json{phone: 13800138000, password: abc123} ) login_data login_resp.json() assert login_data[code] 0, f登录失败: {login_data} token login_data[data][token] # 第二步携带 token 查询订单 order_resp session.get( f{base_url}/orders, params{page: 1}, headers{Authorization: fBearer {token}} ) order_data order_resp.json() # 第三步断言 assert order_resp.status_code 200 assert order_data[code] 0 assert isinstance(order_data[data][list], list)这段代码已经具备了一个可重复执行脚本的基本形态。你可能注意到了我特意用同一个session发送登录和查询请求即使不手动加 tokensession 也会把登录接口返回的 Cookie 保存下来。不过实际项目里很多接口用的是 Header 里的 Bearer Token 而不是 Cookie所以示例里还是显式地往 Header 里塞了 token。两种方式都掌握遇到什么接口都能处理。3. 手把手实现第一个可重复执行的接口脚本3.1 环境准备别在这一步卡太久动手写接口脚本Python 环境是最简洁的选择。我用的是 Python 3配合两个第三方库requests负责发送 HTTP 请求pytest负责组织测试用例和断言。整个准备过程在一个命令行窗口里五分钟搞定# 创建并激活虚拟环境 python3 -m venv api_test_env source api_test_env/bin/activate # 安装依赖 pip install requests pytest为什么要用虚拟环境因为它是你可重复执行的第一道保障。不同项目依赖的库版本可能不一样如果不做隔离今天升级一个库明天另一个项目的脚本就莫名其妙挂了。我见过太多团队因为全局 Python 环境里装了一堆互相冲突的包排查问题排查了半天最后发现是环境问题而不是脚本问题。从第一天就用虚拟环境这个成本极低收益却很大。3.2 从一个请求到一个用例引入 pytest有了上面 2.3 的代码你已经能跑通一次请求了。但这样的脚本还有个问题它是一次性的断言失败也只是往屏幕上吐一段红字。要做到可维护、可重复执行需要引入 pytest 来管理你的用例。pytest 的核心思想很简单一个以test_开头的函数就是一个测试用例函数里的assert就是断言。执行pytest命令后它会自动发现所有test_开头的函数并逐个执行。把登录流程整理成用例的写法是这样的import requests BASE_URL https://api.example.com session requests.Session() def test_login_success(): resp session.post( f{BASE_URL}/login, json{phone: 13800138000, password: abc123} ) data resp.json() assert resp.status_code 200 assert data[code] 0 assert data[data][token] ! 跑一下pytest -v你会看到它输出了一个清晰的执行结果。这个例子看起来和普通脚本差别不大但 pytest 给你带来了三个关键能力第一断言失败时它会详细展示哪个文件哪个函数哪一行失败了失败原因是什么第二一个文件里可以写很多test_函数批量执行第三它支持 fixture、参数化等高级功能为后面的进阶打下基础。3.3 接口关联把上一个接口的返回传给下一个接口真实业务接口几乎没有孤立的全靠接口之间传数据。最常见的模式就是登录拿 token调业务接口时把 token 放 Header。我在 3.2 的例子里还没有完整展示 token 的传递过程这里单独拿出来讲。实际开发中token 在响应里的位置多种多样有的在data.token有的在data.access_token还有的藏在返回头Set-Cookie里。稳妥的做法是先print(resp.text)看清返回结构再写提取逻辑login_data resp.json() token login_data[data][token] # 字典路径直接取有的接口返回嵌套层级很深比如{data: {user: {token_info: {access_token: xxx}}}}。这时候写一长串下标取值很容易踩 KeyError我建议写一个简单的递归查找函数按 key 名全局搜索def find_key(data, target): if isinstance(data, dict): for key, value in data.items(): if key target: return value result find_key(value, target) if result is not None: return result elif isinstance(data, list): for item in data: result find_key(item, target) if result is not None: return result return None token find_key(resp.json(), token)这个小工具函数我几乎每个项目都要用比层数一直变的时候一个个取下标省心得多。取到 token 之后就可以把它写进后续请求的 HeaderHEADERS {Authorization: fBearer {token}} resp session.get(f{BASE_URL}/orders, headersHEADERS)3.4 参数化让脚本从写死变成数据驱动第一次写自动化脚本的人很容易把所有值全部写进代码里。账号密码写死订单 ID 写死页码写死。这样的脚本虽然能跑但本质上和手工点没有太大区别——你改变一个测试数据就要改一遍代码。参数化是摆脱这种局面的关键一步。pytest 的pytest.mark.parametrize可以让你用一份数据跑多组用例。比如登录接口要测正确密码、错误密码、账号不存在、密码为空这四种情况你可以这么写import pytest import requests pytest.mark.parametrize(payload, expected_code, [ ({phone: 13800138000, password: abc123}, 0), ({phone: 13800138000, password: wrong}, 1001), ({phone: not_exist, password: abc123}, 1002), ({phone: 13800138000, password: }, 1003), ]) def test_login_with_params(payload, expected_code): resp requests.post(https://api.example.com/login, jsonpayload) assert resp.json()[code] expected_code为什么要从写死变成数据驱动因为接口测试很大程度上是数据组合的验证。你关心的不是一组数据跑通而是多组数据分别得到预期结果。参数化让测试数据从代码里抽离出来后续新增测试场景时不用改函数体只需要加一组数据即可。测试数据多了以后你还可以把它们放进 JSON 或 YAML 文件里由脚本读取这就是数据与代码分离的雏形。另外一个常见的需求是动态参数。比如创建订单接口要求订单号唯一一个固定的订单号跑第二次就会报订单已存在。解决方案是用时间戳或 UUIDimport time from uuid import uuid4 order_no fTEST{uuid4().hex[:12]} timestamp int(time.time()) payload {order_no: order_no, created_at: timestamp}这保证了每次执行脚本产生的数据都是唯一的脚本就有了重复执行而不互相干扰的能力。4. 可重复执行的关键不是能跑一次而是能跑一万次4.1 自动化脚本最大的敌人不稳定我见过太多团队自动化脚本写出来当天跑得很欢第二天就红了一片。为什么因为能跑通一次和能稳定跑很多次是两回事。自动化脚本真正的敌人不是接口变复杂了而是不稳定。不稳定来自很多方面。Token 有效期只有一小时脚本过了有效期就全部 401测试环境的数据被上一个用例污染了导致断言失败网络偶发超时接口本身没问题但脚本判失败配置写死了测试环境的域名一换环境全部跑不通断言写得不够严谨接口已经悄悄出错脚本却还显示通过。每一个问题我都踩过每一个问题也都有对应的解法。这节内容就是我在这些坑里爬出来的经验总结。4.2 环境配置化别把域名和密码写死在代码里我刚开始做接口自动化的时候习惯把所有环境的域名直接写死在代码里。后来被现实教育了开发环境、测试环境、预发布环境URL 不一样账号密码也不一样。每切换一次环境就要改一遍代码改完还可能漏掉某个接口。正确的做法是把环境信息放到配置文件和环境变量里。用 Python 的os.getenv读取环境变量再配一份.env文件注意别提交到代码仓库管理敏感数据import os BASE_URL os.getenv(API_BASE_URL, https://test.api.example.com) USERNAME os.getenv(API_USERNAME, test_user) PASSWORD os.getenv(API_PASSWORD, test_password)这样跑测试的时候只需要在命令行设置环境变量就能切换环境API_BASE_URLhttps://dev.api.example.com pytest -v另外提醒一句账号密码这类敏感信息不要直接写进代码更不要提交到 Git。我见过有人把自己的生产环境密码写死在测试脚本里然后整个仓库被拖下来密码直接泄露的事故。哪怕只是为了测试也要养成用环境变量的习惯。4.3 断言设计只看状态码 200等于没测新手最容易犯的错误是把assert response.status_code 200当成了全部断言。实际上HTTP 200 只能说明请求被服务端正常处理了完全没有验证业务逻辑的对错。举个真实例子一个查询接口在用户不存在时返回了 200 和一个空的 data 列表如果只断言 200错误根本不会暴露但用户实际看到的是查不到任何数据。接口测试的断言至少应该包含三层传输层断言HTTP 状态码是 200、201、204 等预期值响应时间不超过阈值业务层断言返回的 JSON 中业务状态码code是否符合预期message是否匹配数据层断言关键字段是否存在类型是否正确列表长度是否符合预期金额是不是计算正确。我常用的一组断言模板def assert_api_response(resp, expected_code0): assert resp.status_code 200, fHTTP状态码异常: {resp.status_code} data resp.json() assert data[code] expected_code, f业务码异常: {data} assert data in data, f响应缺少data字段: {data} return data[data]如果你需要更严格的数据结构校验可以引入jsonschema库直接用 JSON Schema 描述这个接口返回的 data 应该长什么样。字段类型错误、字段缺失、嵌套结构不对都能一次校验出来比手写一长串assert更高效、更清晰。4.4 失败后的现场保留让脚本告诉你错在哪儿脚本失败不可怕可怕的是失败后你完全不知道它到底哪里出了问题。没有日志的自动化脚本就像一个没有监控的服务器——它宕机了你也不知道为什么。我习惯在每个用例执行时把请求信息和响应信息记录下来。最轻量的做法是写一个日志函数import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s) logger logging.getLogger(api_test) # 在请求前后打印关键信息 logger.info(请求: POST %s, url) logger.info(请求头: %s, headers) logger.info(请求体: %s, payload) logger.info(响应: %s, resp.text)但凡脚本出问题第一件事永远是翻日志看请求发出去的是什么、服务端回的是什么。大多数接口问题通过这一对“请求-响应”就能定位。更进阶的做法是接入 Allure 报告在用例失败时自动附加当时的请求和响应内容这样整个团队在 CI 上看报告时都能直接看到失败现场不需要每个人重新跑一遍才能复现。4.5 幂等设计让脚本无论跑多少遍结果都一样可重复执行还有一个隐藏前提你的业务数据要支持重复执行。创建一个订单的接口如果每次都创建成功第一次跑没问题第二次跑就会因为订单号重复失败。解决这个问题的方法就是让脚本具备幂等性——无论跑多少遍结果都是一致的。一个常用的套路是先查重再创建import requests from uuid import uuid4 BASE_URL ... session requests.Session() token ... unique_order_no fTEST{uuid4().hex[:12]} # 先判断这个订单号是否已存在 check_resp session.get(f{BASE_URL}/orders/{unique_order_no}, headers{Authorization: fBearer {token}}) if check_resp.status_code 404: # 不存在才执行创建 create_resp session.post( f{BASE_URL}/orders, json{order_no: unique_order_no, amount: 99.9}, headers{Authorization: fBearer {token}} ) assert create_resp.status_code in (200, 201) else: # 已存在直接跳过创建并清理掉 session.delete(f{BASE_URL}/orders/{unique_order_no}, headers{Authorization: fBearer {token}})设计的关键思路是脚本不能假设测试环境永远是干净的。真实环境里上一次跑完留下的脏数据随时会影响到你。学会在脚本里做前置清理、后置清理、先查后建你的脚本才能真正做到重复执行。5. 把 Jmeter 里的场景也脚本化从图形界面到命令行5.1 Jmeter 测试计划的本质与代码是一一对应的很多用 Jmeter 的人一提到脚本第一反应是Jmeter 不是有图形界面吗还要脚本干嘛这个想法低估了 Jmeter 脚本化的价值。实际上Jmeter 测试计划的本质和代码脚本就是一回事线程组相当于代码里的一个 for 循环HTTP 请求采样器相当于循环体里的requests.get/post断言相当于assert监听器相当于日志输出和测试报告。Jmeter 的图形界面适合调试场景但当你要在 CI 里每次发版后自动跑一遍压测或者需要在一台没有图形界面的服务器上执行压测任务时你必须把它从图形界面里解放出来。命令行模式就是解放它的方式。5.2 用 JSR223 断言在 Jmeter 里写你自己的校验逻辑Jmeter 内置的断言组件响应断言、大小断言等在处理简单场景时够用但一旦涉及从响应里提取 token再校验另一个字段的长度这类逻辑你仍然需要写脚本。Jmeter 里支持 BeanShell 脚本但我更推荐用 JSR223 采样器配合 Groovy 语言来做断言因为 Groovy 和 Jmeter 的结合性能更好语法也更接近现代编程语言。下面是一个在 JSR223 断言里验证登录响应并提取 token 的示例def responseText prev.getResponseDataAsString() def json new groovy.json.JsonSlurper().parseText(responseText) assert json.code 0 : 业务码错误: ${json.code} assert json.data.token ! null : token为空 // 把 token 存入 Jmeter 变量供后续请求使用 vars.put(token, json.data.token)写完这段 Groovy 脚本后续的 HTTP 请求采样器里就可以直接用${token}引用这个变量。Jmeter 变量引用的用法和代码脚本里的参数化异曲同工本质上都是在运行时动态替换值。还有一个我在 Jmeter 录制 HTTPS 脚本时踩过的坑如果你用 Jmeter 的 HTTP(S) 脚本录制功能抓 HTTPS 包需要在 Jmeter 里安装它的根证书并在浏览器里信任这个证书否则手机或浏览器上的请求根本录不进去。很多人录制失败九成是因为没有处理好证书信任问题。这个操作不复杂但很容易被忽略。5.3 命令行跑 Jmeter从点运行到一条命令Jmeter 命令行模式的核心命令是这样jmeter -n -t test_plan.jmx -l result.jtl -e -o report_dir参数含义分别是-n表示非 GUI 模式-t指定测试计划文件-l指定原始结果文件的输出路径-e表示生成 HTML 报告-o指定报告输出目录。如果是一次压测任务你还需要动态控制线程数和循环次数。Jmeter 允许通过-J参数从命令行传入 JMeter 属性测试计划里用${__P(threads, 10)}这样的写法来引用jmeter -n -t test_plan.jmx -Jthreads50 -Jloops100 -l result.jtl -e -o report_dir这一步的意义是巨大的。从此之后压测不再需要人坐在电脑前点绿色的运行按钮而是变成可重复执行的命令。你可以把它写进 CI 流水线也可以安排成定时任务。我见过不少团队因为不会命令行模式每天定时压测都是靠同事手动操作一旦执行人请假压测就断档。学会命令行模式之后这个场景彻底消失了。5.4 Postman 代码脚本和 Jmeter到底该用哪个这是我在团队里被问得最多的问题。我的建议是分场景需求场景推荐工具联调阶段快速验证一个接口Postman功能接口自动化回归、CI 集成Python requests pytest高并发性能压测、压力测试Jmeter需要复杂断言和逻辑控制的功能接口自动化Python 代码脚本需要录制一个完整业务流程再压测Jmeter 代理录制 命令行运行作为个人经验我通常会在项目里同时保留两套东西Postman 用来做接口调试和给团队分享接口调用样例代码脚本用来做功能回归和 CIJmeter 则专门负责性能测试场景。三者并不冲突它们的共同底层逻辑都是 HTTP 请求的构造与响应验证。想明白这一点你就不会在学 Postman 还是学 Jmeter 还是学 Python之间纠结了。另外针对最近大家讨论比较多的根据测试用例自动生成自动化脚本这类 AI 辅助测试的玩法我的看法是工具越智能你越要把基础原理吃透。你连请求结构都不会拆AI 生成的脚本出了问题你根本不知道从哪修。反过来如果你已经能把一个接口手动翻译成代码那 AI 生成脚本对你来说只是提效工具而不是救命稻草。基础牢固了再往上走才踏实。我在实际项目里带过不少测试同学最终能独立维护接口自动化脚本的人都具备同一个习惯每遇到一个接口先把它在 Postman 里调通再亲手用代码写一遍哪怕慢也坚持手写。这个过程重复二十个接口之后你就不会再问自动化脚本怎么写了因为你眼里看到的接口已经自动拆成了 method、url、headers、body、断言这五个部分。最后给你一个可以立刻上手的建议今天就把你明天要在 Postman 里手工点的三个接口用 Python 代码写一遍。不要用代码生成功能就手写。写不出来的部分去查 requests 文档查完再写。三个接口跑通之后你会发现之前那道我不会写脚本的心理障碍已经彻底消失了。
返回列表