HttpRunner 演进之路 HttpRunner 2.0 正式发布

debugtalk · 2019年01月03日 · 最后由 回复于 2020年08月25日 · 5552 次阅读

在 2017 年 6 月份的时候我写了一篇博客,《接口自动化测试的最佳工程实践(ApiTestEngine)》,并同时开始了 ApiTestEngine(HttpRunner 的前身)的开发工作。转眼间一年半过去了,回顾历程不禁感慨万千。HttpRunner 从最开始的个人业余练手项目,居然一路迭代至今,不仅在大疆内部成为了测试技术体系的基石,在测试业界也有了一定的知名度,形成了一定的开源生态并被众多公司广泛使用,这都是我始料未及的。

但随着 HttpRunner 的发展,我在收获成就感的同时,亦感到巨大的压力。HttpRunner 在被广泛使用的过程中暴露出了不少缺陷,而且有些缺陷是设计理念层面的,这主要都是源于我个人对自动化测试理解的偏差造成的。因此,在近期相当长的一段时间内,我仔细研究了当前主流自动化测试工具,更多的从产品的角度,学习它们的设计理念,并回归测试的本质,对 HttpRunner 的概念重新进行了梳理。

难以避免地,HttpRunner 面临着一些与之前版本兼容的问题。对此我也纠结了许久,到底要不要保持兼容性。如果不兼容,那么对于老用户来说可能会造成一定的升级成本;但如果保持兼容,那么就相当于继续保留之前错误的设计理念,对后续的推广和迭代也会造成沉重的负担。最终,我还是决定告别过去,给 HttpRunner 一个新的开始。

经过两个月的迭代开发,HttpRunner 2.0 版本的核心功能已开发完毕,并且在大疆内部数十个项目中都已投入使用(实践证明,升级也并没有多么痛苦)。趁着 2019 开年之际,HttpRunner 2.0 正式在 PyPI 上发布了。从版本号可以看出,这会是一个全新的版本,本文就围绕 HttpRunner 2.0 的功能实现和开源项目管理两方面进行下介绍。

功能实现

在 2.0 版本中,功能实现方面变化最大的有两部分,测试用例的组织描述方式,以及 HttpRunner 本身的模块化拆分。当时也是为了完成这两部分的改造,基本上对 HttpRunner 80% 以上的代码进行了重构。除了这两大部分的改造,2.0 版本对于测试报告展现、性能测试支持、参数传参机制等一系列功能特性都进行了较大的优化和提升。

本文就只针对测试用例组织调整和模块化拆分的变化进行下介绍,其它功能特性后续会在使用说明文档中进行详细描述。

测试用例组织调整

之所以要对测试用例的组织描述方式进行改造,是因为 HttpRunner 在一开始并没有清晰准确的定义。对于 HttpRunner 的老用户应该会有印象,在之前的博客文章中会提到 YAML/JSON 文件中的上下文作用域包含了 测试用例集(testset)测试用例(test) 两个层级;而在测试用例分层机制中,又存在 模块存储目录(suite)场景文件存储目录(testcases) 这样的概念,实在是令人困惑和费解。

事实上,之前的概念本身就是有问题的,而这些概念又是自动化测试工具(框架)中最核心的内容,必须尽快纠正。这也是推动 HttpRunner 升级到 2.0 版本最根本的原因。

在此我也不再针对之前错误的概念进行过多阐述了,我们不妨回归测试用例的本质,多思考下测试用例的定义及其关键要素。

那么,测试用例(testcase)的准确定义是什么呢?我们不妨看下 wiki 上的描述。

A test case is a specification of the inputs, execution conditions, testing procedure, and expected results that define a single test to be executed to achieve a particular software testing objective, such as to exercise a particular program path or to verify compliance with a specific requirement.

概括下来,一条测试用例(testcase)应该是为了测试某个特定的功能逻辑而精心设计的,并且至少包含如下几点:

  • 明确的测试目的(achieve a particular software testing objective)
  • 明确的输入(inputs)
  • 明确的运行环境(execution conditions)
  • 明确的测试步骤描述(testing procedure)
  • 明确的预期结果(expected results)

对应地,我们就可以对 HttpRunner 的测试用例描述方式进行如下设计:

  • 测试用例应该是完整且独立的,每条测试用例应该是都可以独立运行的;在 HttpRunner 中,每个 YAML/JSON 文件对应一条测试用例。
  • 测试用例包含测试脚本测试数据两部分:
    • 测试用例 = 测试脚本 + 测试数据
    • 测试脚本重点是描述测试的业务功能逻辑,包括预置条件、测试步骤、预期结果等,并且可以结合辅助函数(debugtalk.py)实现复杂的运算逻辑;可以将测试脚本理解为编程语言中的类(class)
    • 测试数据重点是对应测试的业务数据逻辑,可以理解为类的实例化数据;测试数据测试脚本分离后,就可以比较方便地实现数据驱动测试,通过对测试脚本传入一组数据,实现同一业务功能在不同数据逻辑下的测试验证。
  • 测试用例是测试步骤的有序集合,而对于接口测试来说,每一个测试步骤应该就对应一个 API 的请求描述。
  • 测试场景和测试用例集应该是同一概念,它们都是测试用例的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系怎么办,例如登录功能和下单功能;正确的做法应该是,在下单测试用例的预置条件中执行登录操作。

理清这些概念后,那么 接口(API)测试用例(testcase)辅助函数(debugtalk.py)YAML/JSONhooksvalidate环境变量数据驱动测试场景测试用例集 这些概念及其相互之间的关系也就清晰了。关于更具体的内容本文不再展开,后续会单独写文档并结合示例进行详细的讲解。

模块化拆分(Pipline)

随着 HttpRunner 功能的逐步增长,如何避免代码出现臃肿,如何提升功能特性迭代开发效率,如何提高代码单元测试覆盖率,如何保证框架本身的灵活性,这些都是 HttpRunner 本身的架构设计需要重点考虑的。

具体怎么去做呢?我采用的方式是遵循 Unix 哲学,重点围绕如下两点原则:

  • Write programs that do one thing and do it well.
  • Write programs to work together.

简而言之,就是在 HttpRunner 内部将功能进行模块化拆分,每一个模块只单独负责一个具体的功能,并且对该功能定义好输入和输出,各个功能模块也是可以独立运行的;从总体层面,将这个功能模块组装起来,就形成了 HttpRunner 的核心功能,包括自动化测试和性能测试等。

具体地,HttpRunner 被主要拆分为 6 个模块。

  • load_tests: 加载测试项目文件,包括测试脚本(YAML/JSON)、辅助函数(debugtalk.py)、环境变量(.env)、数据文件(csv)等;该阶段主要负责文件加载,不会涉及解析和动态运算的操作。
  • parse_tests: 对加载后的项目文件内容进行解析,包括 变量(variables)、base_url 的优先级替换和运算,辅助函数运算,引用 API 和 testcase 的查找和替换,参数化生成测试用例集等。
  • add tests to test suite: 将解析后的测试用例添加到 unittest,组装成 unittest.TestSuite
  • run test suite: 使用 unittest 运行组装好的 unittest.TestSuite
  • aggregate results: 对测试过程的结果数据进行汇总,得到汇总结果数据。
  • generate html report: 基于 Jinja2 测试报告模板,使用汇总结果数据生成 html 测试报告。

为了更好地展现自动化测试的运行过程,提升出现问题时排查的效率,HttpRunner 在运行时还可以通过增加 --save-tests 参数,将各个阶段的数据保存为 JSON 文件。

  • XXX.loaded.json: load_tests 运行后加载生成的数据结构
  • XXX.parsed.json: parse_tests 运行后解析生成的数据结构
  • XXX.summary.json: 最终汇总得到的测试结果数据结构

可以看出,这 6 个模块组装在一起,就像一条流水线(Pipline)一样,各模块分工协作各司其职,最终完成了整个测试流程。

基于这样的模块化拆分,HttpRunner 极大地避免了代码臃肿的问题,每个模块都专注于解决具体的问题,不仅可测试性得到了保障,遇到问题时排查起来也方便了很多。同时,因为每个模块都可以独立运行,在基于 HttpRunner 做二次开发时也十分方便,减少了很多重复开发工作量。

开源项目管理

除了功能实现方面的调整,为了 HttpRunner 能有更长远的发展,我也开始思考如何借助社区的力量,吸引更多的人加入进来。特别地,近期在学习 ASF(Apache Software Foundation)如何运作开源项目时,也对 Community Over Code 理念颇为赞同。

当然,HttpRunner 现在仍然是一个很小的项目,不管是产品设计还是代码实现都还很稚嫩。但我也不希望它只是一个个人自嗨的项目,因此从 2.0 版本开始,我希望能尽可能地将项目管理规范化,并寻找更多志同道合的人加入进来共同完善它。

开源项目管理是一个很大的话题,当前我也还处于初学者的状态,因此本文就不再进行展开,只介绍下 HttpRunner 在 2.0 版本中将改进的几个方面。

作为一个产品,不仅要有个好名字,也要有个好的 logo。这个 “好” 的评价标准可能因人而异,但它应该是唯一的,能与产品本身定位相吻合的。

之前 HttpRunner 也有个 logo,但说来惭愧,那个 logo 是在网上找的,可能存在侵权的问题是一方面,logo 展示的含义与产品本身也没有太多的关联。

因此,借着 2.0 版本发布之际,我自己用 Keynote 画了一个。

个人的美工水平实在有限,让大家见笑了。

对于 logo 设计的解释,主要有如下三点:

  • 中间是个拼图(puzzle pieces),形似 H 字母,恰好是 HttpRunner 的首字母
  • 拼图的寓意,对应的也是 HttpRunner 的设计理念;HttpRunner 本身作为一个基础框架,可以组装形成各种类型的测试平台,而在 HttpRunner 内部,也是充分解耦的各个模块组装在一起形成的
  • 最后从实际的展示效果来看,个人感觉看着还是比较舒服的,在 HttpRunner 天使用户群 里给大家看了下,普遍反馈也都不错

版本号机制

作为一个开源的基础框架,版本号是至关重要的。但在之前,HttpRunner 缺乏版本规划,也没有规范的版本号机制,版本号管理的确存在较大的问题。

因此,从 2.0 版本开始,HttpRunner 在版本号机制方面需要规范起来。经过一轮调研,最终确定使用 Semantic Versioning 的机制。该机制由 GitHub 联合创始人 Tom Preston-Werner 编写,当前被广泛采用,遵循该机制也可以更好地与开源生态统一,避免出现 “dependency hell” 的情况。

具体地,HttpRunner 将采用 MAJOR.MINOR.PATCH 的版本号机制。

  • MAJOR: 重大版本升级并出现前后版本不兼容时加 1
  • MINOR: 大版本内新增功能并且保持版本内兼容性时加 1
  • PATCH: 功能迭代过程中进行问题修复(bugfix)时加 1

当然,在实际迭代开发过程中,肯定也不会每次提交(commit)都对 PATCH 加 1;在遵循如上主体原则的前提下,也会根据需要,在版本号后面添加先行版本号(-alpha/beta/rc)或版本编译元数据(+20190101)作为延伸。

HREPs

在今年的一些大会上,我分享 HttpRunner 的开发设计思路时提到了博客驱动开发,主要思路就是在开发重要的功能特性之前,不是直接开始写代码,而是先写一篇博客详细介绍该功能的需求背景、目标达成的效果、以及设计思路。通过这种方式,一方面可以帮助自己真正地想清楚要做的事情,同时也可以通过开源社区的反馈来从更全面的角度审视自己的想法,继而纠正可能存在的偏差,或弥补思考的不足。

直到我后来更深入地了解到了 PEPs(Python Enhancement Proposals),以及类似的 IPEPs(IPython Enhancement Proposals),我才知道原来我曾经使用过的博客驱动开发并不是一个新方法,而是已经被广泛使用且行之有效的开发方式。

因此,从 2.0 版本开始,在 HttpRunner 的开发方面我想继续沿用这种方式,并且将其固化为一种机制。形式方面,会借鉴 PEPs 的方式,新增 HREPs(HttpRunner Enhancement Proposals);关于 HREPs 的分类和运作机制,后面我再具体进行梳理。

License

最后再说下 License 方面。

HttpRunner 最开始选择的是 MIT 开源协议,从 2.0 版本开始,将切换为 Apache-2.0 协议。

如果熟悉这两个 License 的具体含义,应该清楚这两个协议对于用户来说都是十分友好的,不管是个人或商业使用,还是基于 HttpRunner 的二次开发,开源或闭源,都是没有任何限制的,因此协议切换对于大家来说没有任何影响。

总结

以上,便是 HttpRunner 2.0 发布将带来的主要变化。

截止当前,HttpRunner 在 GitHub 上已经收获了近一千个 star,在 TesterHome 的开源项目列表中也排到了第二名的位置,在此十分感谢大家的支持和认可。

希望 HttpRunner 2.0 会是一个新的开始,朝着更高的目标迈进。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 31 条回复 时间 点赞

问一下,使用自定义函数的时候,需要传递给函数的参数带"+","-"这些特殊字符的时候直接整个 ${fun()}就都变成字符串处理,这些特殊的字符如何当做字符串参数传递?

debugtalk 回复

有 HttpRunner 结合 json schema 的例子吗

xzyxyz 回复

HttpRunner 从 2.5.0 开始加入了 json schema 格式校验。

从报错提示来看,是因为你用例中的 params 字段为 None,而预期格式应该是一个 dict。

我想问下一个问题,我遇到了图片中的问题,不知道该怎么解呀,有哪位大神可以帮忙看一下嘛?

感谢楼主分享和开源。@debugtalk
想请教楼主一个问题,用例全都用 yaml 或者 json 文件维护,对于接口变动的情况会不会后期维护难度比较大?
谢谢!

@debugtalk ,我想实现用 httprunner 实现 doubble 接口自动化,可以实现吗?要怎么进行

最近我一直在看你写的源码,工具的思路确实是不错,有一个问题想请教个,就是每个测试套件对应的配置文件(也就是测试环境)可能会不一样,但有时我们部署在同一台机器上面, 如果启动 A 套件用 A 环境,启动 B 套件时,那 A 套件的用例就会受影响,因为 host 变量了,这种不知道 httprunner 本身是否能处理? 我目前自己工具用的是自定义 DNS 解析,这样就不需要配置本机的 host 了。

@debugtalk 像这种写法,validate 里的并没有在 report 里打印出来,如果写在 testcase 里的 validate 就可以

26楼 已删除

从 1.5.9 升级到 2.0 后,发现 case 里调用 suite 失败了,现在怎么实现测试用例分层?我看官方文档里 这块 也是空白的

@debugtalk 大神你好,我刚刚升级了 2.0 版本,最近修改脚本遇到了些问题。对 testsuite 和 testcase 的概念有点模糊不清了
1,之前 1.1.15 版本我用 testsuite 是一些公共 testcase 的集合,可用于被其他 testcase 调用 ,然后场景可以在一个 testcase 里完成。
2,那升级了 2.0 版本后,我个人感觉 testsuite 像是 testcase 的场景组合? tescase 还可以调用 testsuite 吗?
3,应该运行 testsuites 还是 testcases 来完成我的接口测试呢?
刚刚在学习 httprunner 的我,求助~~ ,因为看了官方 2.0 的文档还是有疑问,感觉和 1.x 的差不多?

debugtalk 回复

@debugtalk 之前给的例子有问题,没有暴漏出 bug,第二次 commit 新增了单元测试,可以暴漏问题,2.0 也试过了一样有问题的。

支持楼主,已经升级了,脚本也正在修改中,希望越来越强大

@rihkddd 我看到 PR 里面没有任何描述,当时就没有去细看了。

刚才我也看了下你提的 issue #450,也没有复现该问题。

借楼问一下,给 issue #450 提的 PR 为啥一直不给过呀~

xiaotian 回复

感谢支持

HttpRunner 真的是非常优秀的一套框架!非常感谢分享。

16楼 已删除
wolfgao 回复

这个是其他同学基于 HttpRunner 做的接口测试平台。当前基于 HttpRunner 的开源平台已经有好多个了,HttpRunnerManager 是我知道的第一个

牛,我最近也在研究这个框架,为什么又推出一个基于 webUI 的子项目:HttpRunnerManager?这个是咋考虑的?

膜拜。mark 学习

12楼 已删除
debugtalk 回复

可能是因为水印的问题。

支持支持!静候连载😜

9楼 已删除
debugtalk 回复

是这样的,我自己本人也在用 httprunner,随着业务发展,框架本身就不太满足我们的需求了,所以近期打算做二次开发,但是我对运行测试用例时读取 debugtalk.py 这块的实现不是很明白,所以想请作者大概说下实现思路,这个可以吗?

今年过年不寂寞了,研究大佬的项目。😀

@Lihuazhang @chenhengjie123 报个问题,我这 logo 图片为啥贴到文章中颜色变深了好多?实际颜色应该是专栏头像里面那样的。

pan 回复

没明白你的问题哈

有个问题想问下作者,你文章中提到了辅助函数 debugtalk.py,都是在下一步的 parse_test 上面就行解析吗?

支持,果断尝鲜,吸取大佬思路

666,一直停留在 158,之前也是对 suits 用例集有些晕头转向,看来是时候尝鲜升级了

一个字,牛批

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册