ARTICLE DETAIL

资讯详情

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

HttpRunner接口自动化测试实战:从YAML配置到CI集成

HttpRunner接口自动化测试实战:从YAML配置到CI集成 1. 为什么我最终选择了HttpRunner而不是自己写一堆Python脚本先聊聊我个人的真实经历。前几年我做接口自动化最开始的方案特别原始用requests写一个工具类封装get、post然后每个接口一个测试函数pytest组织用例。做了一段时间问题开始冒出来——写得越多冗余越多。每个接口的代码结构都差不多但参数、断言、前置条件纠缠在一起新同事接手要读懂这些代码得花不少时间。最关键的是一旦接口字段调整你得在代码里找到对应位置改完还要担心会不会破坏其他用例。后来我在一次技术分享里看到HttpRunner第一感觉是这东西把接口测试从编程任务变成了写配置文件。它用YAML或JSON描述接口请求、参数、校验点而不是用代码硬写。这意味着什么意味着你不必是个Python高手也能编写和维护一套接口自动化用例领导或者产品想看看你测了哪些接口直接把YAML打开就能看懂。HttpRunner的核心价值说白了有三点声明式写用例接口的请求方法、URL、请求头、请求体、校验点全都用结构化数据描述比代码直观得多。自动化能力内置变量提取、参数化、数据驱动、用例依赖、失败重跑、测试报告生成这些常规接口框架需要自己造轮子的能力它都内置好了。生态兼容底层基于pytest所以你想加一些Python逻辑也可以写hooks钩子自定义同时支持har2case抓包录制能把浏览器或抓包工具导出的har文件直接转成用例。这篇文章不是给你讲概念而是直接带你从零跑起来。你可以理解为这是一份我用了很长时间、踩了不少坑之后整理出来的快速上手手册跟着做基本当天就能出活。2. 环境准备Python版本、安装方式和第一个容易踩的坑2.1 安装前的环境要求HttpRunner是基于Python开发的所以第一步先确认你机器上有Python环境。我建议使用Python 3.8到3.11之间的版本。3.7以下太老部分依赖可能装不上3.12以上有些场景下第三方依赖编译会有兼容性问题。如果你机器上同时装了多个Python版本建议用venv或者conda建一个独立环境别把项目依赖和系统环境搅在一起。创建独立环境的操作很简单以我用venv为例python3 -m venv httprunner_env source httprunner_env/bin/activateWindows下激活命令是httprunner_env\Scripts\activate。为什么要创建虚拟环境因为我被坑过——之前直接在全局环境装和另一个项目里的Flask依赖互相冲突结果两边都跑不起来。把HttpRunner隔离在虚拟环境里至少以后出问题不会牵连到别的项目。2.2 安装HttpRunner激活环境后安装命令就一行pip install httprunner装完后验证一下hrun -V如果能输出版本号比如3.x.x说明安装成功了。注意这里用的命令是hrun不是httprunner这是HttpRunner 3.x及以后版本的命令行入口。如果你搜到老的教程让你用httprunner命令那是2.x的用法3.x已经改了。2.3 安装过程中的常见坑我在帮几个同事装环境的时候发现两个频率最高的报错坑一pip安装时超时或下载慢。这个解决方案简单换用国内镜像源pip install httprunner -i https://pypi.tuna.tsinghua.edu.cn/simple坑二安装时报错提示缺少wheel或者某些依赖无法安装。这种情况大多是Python版本不匹配。比如HttpRunner依赖的pydantic在Python 3.7和3.8下版本要求是不同的。遇到这种问题别硬扛直接切换到3.9或3.10重试。实测下来Python 3.9配合HttpRunner 3.x是目前最稳妥的组合。安装好环境之后我们开始写第一个真正的用例。3. 第一个用例从YAML结构理解HttpRunner的设计思路3.1 项目目录结构HttpRunner本身不强制你用什么目录结构但官方推荐的规范我建议照着用。比如我们建一个项目目录长这样httprunner_demo/ ├── testcases/ │ └── test_get_demo.yml ├── debugtalk.py └── reports/testcases/存放用例文件YAML或JSON格式。debugtalk.pyPython辅助函数文件用于定义测试中需要的钩子函数、生成随机数、签名等等。reports/生成的测试报告默认输出目录。实际上hrun命令在运行的时候会自动找当前目录下的testcases子目录你可以直接在里面写YAML用例。3.2 YAML用例的基本结构一个最简单的GET请求用例长这样config: name: 第一个用例访问百度首页 base_url: https://www.baidu.com verify: false teststeps: - name: 步骤1GET请求首页 request: method: GET url: / validate: - eq: [status_code, 200]我来拆解一下这个YAML里面的知识点config用例级别的配置。name是这个用例的名字base_url是统一的基础地址步骤里的url写相对路径即可运行时会自动拼接verify: false表示不校验SSL证书。这个verify很有用很多测试环境的HTTPS证书是自签名的不关掉校验会直接报SSL错误。teststeps测试步骤列表。每个步骤对应一次接口请求。这里是一个数组所以每个步骤前都有-短横线。request和requests库的参数风格几乎一致。method指定请求方式url是路径。你可能会想为什么不直接把完整URL写进去因为接口测试的项目往往有成百上千个用例如果base_url集中在config里管理环境切换测试环境/预发环境/生产环境时只需要改一处而不是逐个用例改。validate校验点定义。eq表示等于后面的列表第一个参数是实际值第二个参数是期望值。[status_code, 200]意思就是响应的状态码等于200。3.3 运行用例和查看报告在项目根目录执行hrun testcases/test_get_demo.yml运行结束后命令行会输出每个步骤的通过情况同时reports/目录下会生成一个基于HTML的测试报告。用浏览器打开报告里面对应步骤的执行详情、响应时间、请求和响应的完整数据都有记录。这里我要多说一句HttpRunner生成的HTML报告质量很高。之前我用pytest写断言想给领导展示测试结果还得自己拼接日志。用HttpRunner之后报告直接发给对方打开就能看到哪个接口通过、哪个失败、失败时响应体是什么省了太多沟通成本。4. 变量、参数化和数据驱动日常自动化最核心的三板斧4.1 用variables定义局部变量接口测试中很多请求参数是不固定的。比如用户ID、token、时间戳。HttpRunner支持在config和teststeps里定义变量语法是$变量名。看一个例子config: name: 带变量的用例 base_url: https://api.example.com variables: user_id: 10086 token: abc123 teststeps: - name: 查询用户详情 request: method: GET url: /users/$user_id headers: Authorization: Token $token validate: - eq: [status_code, 200]运行的时候$user_id会被替换成10086$token会被替换成abc123。这种写法比直接把参数写死的好处是你可以在一个config里统一定义公共变量然后多个步骤引用后续要改只动定义处所有引用自动更新。4.2 parameters参数化让一份用例跑多组数据在真实项目里一个查询接口你不可能只测一组数据。比如分页参数page要测1、2、3每页条数size要测10、20、50。如果每个组合写一个用例那YAML文件会膨胀得没法看。HttpRunner的parameters就是解决这个问题的。它支持在config里声明参数列表运行时自动生成多组数据组合。config: name: 分页查询接口参数化 base_url: https://api.example.com parameters: page: [1, 2, 3] size: [10, 20, 50] teststeps: - name: 分页查询 request: method: GET url: /items?page$pagesize$size validate: - eq: [status_code, 200]这样运行HttpRunner会生成9个用例——3个page值乘以3个size值。报告里你会看到每个组合都独立执行、独立记录。而且parameters不仅支持列表还能引用外部CSV文件。我之前的项目里测试数据量比较大团队成员习惯把数据放在Excel里维护我就会转成CSV放到项目目录然后在parameters里这样引用config: name: 读取CSV参数化 parameters: - page-size: data/pages.csvCSV文件内容page,size 1,10 2,20 3,504.3 variables和parameters的优先级这个地方是新手最容易糊涂的。我当年也踩过这个坑我在config里定义了variables又在parameters里定义了同名变量结果运行时用的到底是哪个HttpRunner的变量优先级从高到低是这样的优先级作用域说明最高teststeps步骤内提取的变量通过extract提取当前步骤后面的请求可用高teststeps步骤内variables只在该步骤内生效中config里的parameters优先级高于config里同名的variables低config里的variables全局可用但可以被上面的覆盖这个顺序背后逻辑也合理parameters是数据驱动的数据它本身就希望覆盖掉默认配置的值而步骤内的变量是过程值是前后依赖的数据必须更优先。理解了这套优先级你在设计用例结构时就不会出现我明明定义了变量为什么没生效的困惑。4.4 一个完整的参数化登录用例我举个我实际项目里的例子。登录接口需要用户名、密码以及一个由用户名密码计算的签名sign。我们参数化了3组账号期望它们返回不同的状态码config: name: 登录接口参数化用例 base_url: https://api.example.com variables: # 默认账号被parameters覆盖 username: default_user password: default_password sign: default_sign parameters: - username-password-sign: - [test_user1, 123456, abc123] - [test_user2, 654321, def456] - [error_user, wrong_pass, expired] teststeps: - name: 登录并校验响应 request: method: POST url: /login json: username: $username password: $password sign: $sign validate: - eq: [status_code, 200]5. 断言、变量提取和请求依赖写复杂业务用例的核心操作5.1 断言validate的常用写法接口测试的断言不像UI测试那样需要等元素出现、判文字它更直接——校验响应状态码、响应体里的字段值、响应耗时等等。HttpRunner的validate支持的操作符不止eq常用的还有操作符含义示例eq等于- eq: [status_code, 200]lt小于- lt: [body.data.total, 100]lte小于等于- lte: [response_time, 1000]gt大于- gt: [body.data.count, 0]gte大于等于- gte: [status_code, 200]contains包含- contains: [body.message, success]startswith以某字符串开头- startswith: [body.url, https]这里有个关键点status_code和response_time是HttpRunner内置的响应属性直接可用。而响应体里的字段要用body.xxx.yyy这种路径表达式。比如响应体是{ code: 0, data: { user_id: 12345, nickname: test_user }, message: success }那么断言用户ID等于12345写法就是validate: - eq: [body.code, 0] - eq: [body.data.user_id, 12345] - eq: [body.message, success]如果响应体是一个数组比如data是个列表要断言第一个元素的某个字段可以用body.data.0.id这种索引方式。这一点在分页接口里尤其有用。5.2 用extract提取变量并传递给后续请求接口测试有一个极其常见的场景先用登录接口拿到token然后拿着token去操作其他需要鉴权的接口。这种请求A的响应结果要作为请求B的入参的需求就是extract的用武之地。看这个例子config: name: 登录后获取用户信息 base_url: https://api.example.com teststeps: - name: 步骤1登录获取token request: method: POST url: /login json: username: test_user password: 123456 extract: token: body.data.token user_id: body.data.user_id validate: - eq: [status_code, 200] - eq: [body.code, 0] - name: 步骤2使用token获取用户信息 request: method: GET url: /users/$user_id headers: Authorization: Bearer $token validate: - eq: [status_code, 200] - eq: [body.data.nickname, test_user]这里的关键是extract。它的写法是变量名: 提取表达式提取表达式和validate里引用实际值的语法一样用body.xxx。第一个步骤执行完token和user_id这两个变量就存储下来了第二个步骤直接通过$token、$user_id引用。这种机制的设计思路是HttpRunner把每个测试步骤看作一个可独立执行的最小单元步骤之间通过变量传递数据而不是在同一个函数里互相操作。这样做的好处是你可以随意调换步骤顺序、复用步骤不会出现传统代码里类变量被到处修改的问题。5.3 遇到中文或特殊字符注意编码和JSON格式在用HttpRunner处理POST请求时我遇到过一个问题请求体里带中文结果服务端返回乱码。排查下来发现是YAML文件编码问题。HttpRunner 3.x默认按UTF-8读取YAML文件但Windows环境下有些编辑器默认存成GBK就会出问题。解决方式很简单所有YAML文件统一存成UTF-8编码。在VS Code里右下角可以看到当前文件的编码格式点一下就能切换。同时在YAML里写长文本推荐用单引号或双引号包起来避免特殊字符被解析器误判。6. 进阶玩法hooks钩子、debugtalk.py和失败重试机制6.1 前置条件和后置逻辑setup_hooks与teardown_hooks有些接口在请求前需要生成加密参数或者请求后需要清理测试数据。这些逻辑如果用纯YAML描述写会很别扭。HttpRunner提供了hooks机制让你在某个步骤前后插入Python函数。YAML里这样声明teststeps: - name: 带前置处理的请求 setup_hooks: - ${setup_prepare_data()} request: method: POST url: /create_order json: order_id: $order_id teardown_hooks: - ${teardown_cleanup(order_id)}对应的debugtalk.py里写函数import random def setup_prepare_data(): # 生成一个随机的订单号赋值给全局变量简单示例 order_id ORD str(random.randint(100000, 999999)) return order_id def teardown_cleanup(order_id): # 删除测试数据 print(f删除订单: {order_id})注意debugtalk.py里的函数通过${函数名()}语法在YAML中调用。这个文件可以放在项目根目录HttpRunner运行时会自动加载。这里对一些新手朋友多说一句hooks虽然灵活但不要滥用。如果一个接口的前置逻辑特别复杂你可能要反思这个接口本身是不是设计得太重了。正常情况下90%的接口测试用变量和参数化就能覆盖hooks是给那剩下10%的特殊场景准备的。6.2 使用debugtalk.py实现复杂的参数生成我举一个实际工作中的例子。有一个下单接口它的请求头里需要带一个签名sign签名算法是把时间戳、token、请求体里的部分参数拼接后做MD5。这种逻辑用YAML根本写不了必须用函数。在debugtalk.py里import hashlib import time def generate_sign(token, body_str): # 模拟一个签名算法MD5(token body_str timestamp) timestamp str(int(time.time())) raw f{token}{body_str}{timestamp} sign hashlib.md5(raw.encode(utf-8)).hexdigest() return sign然后在YAML的request里使用- name: 下单接口 request: method: POST url: /order/create headers: Sign: ${generate_sign($token, order_data)这种函数化的能力让HttpRunner不至于在复杂的真实项目中变得无力。换句话说你既可以用声明式写法解决80%的简单场景又可以用Python函数兜底剩下的20%复杂场景——这就是它区别于很多纯配置型接口测试工具的优势。6.3 失败重试机制接口测试跑在CI流水线里最怕遇到偶发性失败。比如网络抖动、服务端临时超时接口本身功能没问题但测试就是红了。HttpRunner提供了失败重试的配置config: name: 带重试机制的用例 base_url: https://api.example.com retry_times: 3 retry_interval: 1retry_times失败后最多重试几次。retry_interval两次重试之间间隔的秒数单位是秒。这里我给你的建议是重试机制设置不要太激进。我们曾经把重试次数设为5间隔0.5秒结果大量用例因为服务端逻辑问题一直在重试整个测试套件跑完比平时慢了将近三倍。合理的方式是重试1到2次间隔1秒以上并且只在特定的弱网络环境场景中开启正常的接口回归测试不要开。7. Har文件转用例把Chrome里的请求直接变成自动化用例接手一个老项目最痛苦的事情是什么是没有接口文档。后端接口上千个文档写着详见某内部wiki但那个wiki早就没人维护了。这种情况下用HttpRunner的har转用例能力就能帮你快速打底。7.1 获取har文件操作很简单打开Chrome开发者工具切到Network标签页勾选Preserve log保留日志在页面上手动操作一遍业务流程比如登录、下单、查询。操作完之后在Network面板空白处点击右键选择Save all as HAR with content就会得到一个.har文件。7.2 将har转换为YAML用例HttpRunner 3.x中可以通过har2case命令或者直接在hrun命令中使用转换功能hrun --har-to-yml demo.har执行后目录下会生成一个与har文件同名的YAML文件。打开看里面的teststeps已经自动生成了请求方法、请求URL、请求头、请求体。有些步骤里可能带着extract和validate如果原har文件记录了响应数据转换工具会尝试自动加上基础的状态码断言。7.3 录制用例后必须做的事清理和改造这是我特别想提醒你的一点har转出来的用例可以直接跑但千万不能直接用于回归测试。原因有三个请求头里可能有动态token。har文件记录的token是录制那一刻的过期后用例就废了。你需要把token改成$token变量用登录步骤提前提取。请求体里有时间戳等随机值。比如timestamp字段录制时的值和运行时肯定对不上。需要改成${generate_timestamp()}这类动态函数。断言太弱。自动生成的验证点通常是status_code等于200如果接口返回200但业务逻辑错误测试照样通过。所以关键的商业字段必须手动补充断言。我之前接手过一个老系统的接口测试用har转了一批用例然后花了一个下午把里面的token和时间戳全部改成变量再补充了每个核心接口的字段断言。之后这套用例稳定跑了大半年确实省了不少力。8. 把用例跑在流水线里Jenkins集成与高频使用心得8.1 集成Jenkins的思路接口自动化跑本地机器只能算自娱自乐。真正有价值的是把用例集成到持续集成流水线里每次代码提交后自动跑一遍接口测试有问题及时通知到人。在Jenkins里配置HttpRunner项目核心步骤就几个新建一个自由风格的项目或流水线项目。源码管理里选择Git填上存放HttpRunner用例的仓库地址。构建环境里选择Add timestamps to the Console Output可选。构建步骤里选择Execute shell输入# 进入虚拟环境 source /opt/httprunner_env/bin/activate # 安装依赖 pip install -r requirements.txt # 运行全部用例 hrun testcases/这里要注意hrun testcases/会运行testcases目录下的所有测试用例文件。如果只想跑某个文件就指定文件路径。8.2 测试报告的留存与归档跑完用例后reports/目录会生成新的HTML报告。如果Jenkins构建机器上的工作空间每次都被清理报告也找不到了。我建议在构建后操作里加一步Archive the artifacts把reports/*.html归档起来。这样每次构建的测试报告都会保留在Jenkins的历史记录里随时可以翻查。8.3 几个实际使用中的高频问题我把自己经常被同事问的几个问题整理一下这些问题在官方文档里讲得不多但实战中特别容易遇到。问题一Python环境在CI机器上装不上怎么办优先检查Python版本。CI机器上可能预装的是Python 3.6HttpRunner 3.x要求最低3.7。建议直接用Docker集成镜像里装好Python 3.9和HttpRunner然后Jenkins的构建步骤就是一行docker run --rm -v ${WORKSPACE}:/workspace httprunner_env hrun testcases/问题二测试报告中文乱码报告中文乱码通常是因为系统默认编码不是UTF-8。Linux服务器上可以检查localeecho $LANG如果不是UTF-8结尾在Jenkins的构建环境里设置环境变量LANGzh_CN.UTF-8或LC_ALLen_US.UTF-8用英文环境也可以至少不乱码。问题三用例跑得非常慢如果用例数量多且每个用例都引用同一个登录步骤那重复登录的消耗会非常大。这种情况我建议把登录步骤放到setup_hooks里只执行一次或者用parameters去复用同一个已登录的会话避免每个用例都拉起一次登录接口。9. 我的实际落地建议什么时候该选HttpRunner什么时候不建议用过一段时间后我越来越清楚HttpRunner的适用边界。聊点掏心窝的经验。适合用HttpRunner的团队和场景团队成员测试基础较好但Python编程能力一般不想写大量代码。用YAML写用例学习和维护成本都低。项目接口数量中等几十到几百个接口间依赖关系不复杂主要需要快速覆盖回归。希望测试报告直观、可分享能直接往团队群或者文档里丢链接。不太适合用HttpRunner的场景接口间的逻辑依赖特别复杂需要大量自定义Python代码支持这时你可能会发现自己在YAML里写了很多${func()}调用反而比直接用pytestrequests更绕。纯性能测试场景。HttpRunner虽然可以结合Locust做性能测试但它更适合功能接口的自动化专业性能测试有更顺手的工具。接口本身只是辅助你真正要测的是复杂的业务流程状态机这时候最好还是用代码驱动式框架而不是声明式配置。我最终给团队定的方案是HttpRunner负责所有稳定的、可复用的业务接口回归Python代码负责复杂的、探索性的接口逻辑验证。两个工具各管一摊都不越界效果反而比之前把资源全压在一个方案上好。最后再说一个给新人的小提醒接口测试框架选择这件事没有银弹。网上吹得再厉害的框架落到你的项目里都有适配成本。我今天分享HttpRunner的用法不是说它好到万能而是它在快速上手、维护简单、报告清晰这三个维度上做得确实很均衡。你要是手头正好有接口自动化的需求拿这篇文章当起点踩一圈坑之后自然就知道它是你的菜还是不你的菜了。
返回列表