Skip to content

第19讲:搭建敏捷自动化测试框架及其案例分析


在前几讲已经介绍了虚拟化技术、CI/CD 环境、DevOps 下的基础设施及自动部署、BVT 等,而上一讲介绍了静态测试技术和工具,这一讲将侧重介绍动态测试工具,从而形成一个完整的测试基础设施的体系。


如果只是讨论工具,感觉不够恰当,所以会提升到自动化测试框架这个层次上。因为工具很多,变化也很快,而且换工具是比较容易的,今天喜欢这个工具,就用这个,明天有更好的工具,就可能想换了。经常望着这山比那山高、频繁换工具,也是不合适的,因为团队已经熟练使用工具并积累了良好的经验,与工具联系在一起的脚本值得继承,这些无形资产都值得保护。


自动化测试框架是测试基础设施的核心部分,不仅提供了各种测试服务,比如测试脚本的开发、执行、调试和管理,测试过程的管理、测试资源的管理,以及支持不同类型的测试(比如性能测试、安全性测试、易用性测试等)执行与分析,而且也希望基于这个框架,让测试与开发平台、CI/CD 环境更加融合,构建更高效的研发平台。

自动化测试框架的构成

可以设想一下自动化测试开发与执行的场景:首先,研发人员根据测试任务的要求,开发和调试自动化测试脚本,并能基于脚本和测试环境组合成测试任务,在下班前预先安排好测试任务,比如在某个 Web 页面上提交测试任务,而这些任务能够在当晚自动执行,第二天我们一上班就可以查看测试结果或浏览测试报告。如果晚上执行不顺利,系统则会发消息或邮件给相关人员,让我们检查并处理存在的问题,使得测试能够继续跑下去。当然,如果测试都在半夜执行,不适合人工干预,那就增加一些异常处理机制、重试机制来自动处理这类问题。


这种测试任务能够按某种机制(比如定时机制、版本构建成功后消息触发机制等)自动启动执行,而且需要自动发现可用的测试资源来执行测试任务,这依赖于资源监控和调度工具 或 平台来完成,并借助代理获得机器状态、运行测试工具和将测试日志发送到特定服务器上以供分析。


为此,我们需要构建一个自己的自动化测试框架,能够集成测试脚本开发环境、测试执行引擎、测试资源管理、测试报告生产器、函数库、测试数据源和其他可复用模块等为一体,而且还可以灵活地集成其他各种测试工具,包括单元测试工具、API 测试工具和 UI 测试工具等。不同于工具,框架只是实现一个架构,用户可以根据自己的需求进行填充,比如进行二次开发增加具体、特定的功能,还可以集成其他不同的测试工具。图1就展示了自动化测试框架的逻辑结构,由多个组件构成。


图1 自动化测试框架的基本构成


  1. Harness/IDE :TA 框架的核心,相当于"夹具",框架的其它组成部分都能与之集成,而且具有脚本的创建、编辑、调试和管理等功能。

  2. TA 脚本的管理 ,包括公共脚本库、项目归类的脚本库,这部分可以与 GitHub 这类(代码库)配置管理工具集成。

  3. 测试资源管理 :增加、删除和配置相应的测试设备(软硬件资源),并根据它们的使用状态来分配测试资源,这部分可以和容器管理工具集成。

  4. 测试数据管理 :测试数据的自动生成、存储、备份和恢复等,也可以演化成一个数据平台,甚至是数据中台。

  5. 开放的接口 :提供给其他 CI 环境或其他测试环境的集成接口,这种接口以 API 形式提供,类似之前提到的"基础设施即代码"的概念。

  6. 代理(Agents) :负责 Harness 与工具的通信,控制测试工具的运行。

  7. 任务安排(Scheduler) :安排和提交定时任务、事件触发任务等,以便实现无人值守的自动化测试执行。

  8. 数据统计分析 :针对测试结果(含测试工具运行产生的日志),生成可读性良好的测试报告(如 HTML 格式的测试结果),如上一讲提到的 SonarQube、Allure2 等。


自动化测试框架能够与 CI 环境、配置管理系统和缺陷管理系统等集成起来,持续构建后直接触发 BVT、后续的深度自动化测试。这种集成,不仅发生在单元测试、接口层次上,而且还可以在系统层面、业务层面的测试。下面我们就介绍不同层次的自动化测试框架。

自动化测试框架的分类

结合前面分层自动化测试策略------金字塔模型来划分自动化测试框架更合适一些,从单元测试、接口测试再到 UI 层、ATDD/BDD 的自动化测试框架。

  • 单元测试框架 ,由 JUnit 演化成单元测试框架家族 xUnit 最具代表性,形成了单元测试的基本规则,包含了面向各种编程语言的框架,比如 JUnit、CppUnit、NUnit、PyUnit、JsUnit、QUnit、DBUnit、HttpUnit 等。JavaScript 语言,也有一些其他的测试框架,比如 Jasmine、Mocha、Buster.js、DaleJS、PhantomJS、TestSwarm、JsTestDriver 等。

  • 接口测试框架 ,比如 HttpRunner、Karate、APIfortress、Swagger 等。从框架的角度看,JMeter、SoapUI、Postman、PyTest、APIAutoTest 等算接口测试工具,还不能算框架,而 REST Assured 通常也算 API 框架,它更是为了简化基于 REST 服务的测试而建立的 Java 领域特定语言(DSL),但将它和 JUnit 集成起来,如同 APIAutoTest +TestNG + HttpClient、Unittest + Request + HTMLRunner 等集成,也可形成接口测试框架。Robot Framework 和 Requests 库集成起来,也能执行 API 的测试。

  • UI 自动化测试框架 ,比如面向 Web 的 Selenium + WebDriver、TestCafe 和 Cypress,面向移动 App 的 Appium,面向 Windows 客户端软件的 AutoIT 等。移动 App 还有更多的自动化测试框架,比如基于 Android 的 TA 框架 Robotium、Selendroid、ATAF 等,基于 iOS 的 TA 框架 KIF、Kiwi 等,以及跨平台的 Ranorex Studio、Calabash 等。

  • ATDD/BDD 自动化测试框架 :Robot Framework、Ginkgo、Cucumber、JBehave/ NBehave / CBehave、SpecFlow、RSpec、JDave、Chakram(REST API)、Concordion、Fitnesse、Guage 等。


在敏捷测试中,更推荐单元测试和基于接口的自动化测试,如果再进一步,ATDD 和 BDD 也是敏捷测试中所推荐的,是更为彻底的自动化,即让需求可执行,将需求变成真正的活文档。而基于 UI 的自动化测试框架更适合传统的开发,或者说不是为敏捷测试而生,所以我们重点会关注单元测试和基于接口的测试、支持 ATDD/BDD 的验收测试等三类自动化测试框架。下面将从这三类框架中各拿出一个工具,做进一步的案例分析。

单元测试框架 JUnit 5

先说单元测试框架。谈起单元测试框架,不得不介绍 JUnit,它是最为经典的自动化测试框架,也成为了事实上的单元测试框架的业界标准。JUnit 最新版本是 JUnit 5,它不再是一个单一的 jar 包,而是由 JUnit platform(平台)、Jupiter(木星)、Vintage 等三部分组成,如图 2 所示,其显著的新特性有扩展模型、嵌套测试、条件测试、参数化测试等。


图2 JUnit 5 架构示意图

  • JUnit platform ,其主要作用是在 JVM 上启动测试框架,包含一个内部的 JUnit 公共库以及用于测试引擎、配置和启动测试计划、配置测试套件的注释等公共 API,同时还支持通过控制台(Console Launcher)命令、IDE 或构建工具 Gradle、Maven(即借助 surefire-provider、gradle-plugin)等来启动测试。

  • JUnit Jupiter ,包含了 JUnit5 最新的编程模型(注释、类、方法)和扩展机制的组合(Jupiter API)和一个测试引擎(Test Engine),用于编写和执行 JUnit 5 的新测试,其中 junit-jupiter-params 为参数化测试提供支持。

  • JUnit Vintage ,一个测试引擎,允许在平台上运行老的 JUnit 3 和 JUnit 4 测试用例,从而确保必要的向后兼容性。



通过上面这张注释列表,能感受到 JUnit 5 更强大的功能。例如,扩展机制通过 @ExtendWith 定义,简单明了。



可以通过 @ParameterizedTest 来定义参数化测试方法,而且还可以和其他注释组合使用,指定多个来源,包括 @ValueSource、@MethodSource、@CsvSource、@ArgumentSource 等。


API 层的 TA 测试框架 Karate

API 层的自动化测试框架,如上所列,也有很多,要选择适合自己的框架,也不是容易的事情,可以选择自己熟悉的工具,比如 HttpRunner、JMeter、Postman 等。这里介绍一个由 Intuit 公司开发并开源的 API 测试框架 Karate,它不仅提供了源代码,而且还提供了比较完整的文档和演示实例,值得关注。这个框架,官方列出了 30 多个优点(特性),这里从中选出十大优点,供参考。

(1)纯文本脚本,可以调用其他脚本,能调用 JDK 类、Java 库,并具有嵌入式 JavaScript 引擎,可构建适合特定环境的、可重复使用的功能库,具有良好的可扩展性。

(2)标准的 Java / Maven 项目结构,以及与 CI / CD 管道的无缝集成,并支持 JUnit 5。

(3)优雅的 DSL 语法原生地支持 JSON 和 XML,包括 JsonPath 和 XPath 表达式,覆盖数据的输入和结果的输出。

(4)基于流行的 Cucumber / Gherkin 标准,支持 BDD(Cucumber 场景 Scenario Outline 表),并内置与 Cucumber 兼容的测试报告。

(5)内置对数据驱动测试的支持,原生支持读取 YAML 甚至 CSV 文件,并能够标记或分组测试,其场景数据支持友好的 JSON、XML 或其独有的 payload 生成器方法。

(6)全面的断言功能,容易定位故障,清楚地报告哪个数据元素(和路径)与预期不符。

(7)多线程并行执行,内置分布式测试功能,可用于 API 测试而无需任何复杂的"网格"基础架构,从而显著节省测试时间,简化测试环境准备工作。

(8)API mocks or test-doubles 甚至可以在多个调用之间维持 CRUD 的"状态",从而支持微服务和消费者驱动的契约测试。

(9)模拟 HTTP Servlet,可以测试任何控制器 Servlet,例如,Spring Boot / MVC 或 Jersey / JAX-RS- 无需启动应用程序服务器,可以使用未更改的 HTTP 集成测试。

(10)全面支持不同类型的 HTTP 调用:

  • SOAP / XML 请求

  • HTTPS / SSL,不需要证书、密钥库等

  • HTTP 代理服务器

  • URL 编码的 HTML 表单数据

  • Multi-part 文件上传、Cookie 处理的支持

  • HTTP head、路径和查询参数的完全控制

  • WebSocket 支持


这里展示了一个简单的 WebSocket 测试示例,用到了 Given-When-Then 这种 BDD 的场景描述方式。


验收测试框架 Ginkgo

最后来分析一个验收测试的自动化测试框架,比较著名的有前面提到的 Cucumber 和 Robot Framework,今天介绍一个用 Go 语言开发的框架 Ginkgo(银杏),它对 BDD 有很好地支持,拥有自己的 DSL,包括嵌套的 Describe、Context 和 When 容器模块,BeforeEach / AfterEach、BeforeSuite / AfterSuite、It / Specify 等也一应俱全,这样就能帮助我们组织和编排测试用例了。


先上一个例子,让你感受一下,测试用例的业务场景是多么清晰、脚本的可读性多么良好,这会大大降低脚本后期的维护成本。




Go 语言擅长并行处理,Ginkgo 并行执行能力也就是原生的能力,实现了进程级并行执行测试的能力,既节省时间,稳定性也大大提高,也特别适合现在流行的容器环境,一个容器跑一个进程,可以直接在每个容器上运行命令 ginkgo -p 来执行测试。而且,ginkgo CLI 工具在并行执行测试时,会起一个监听随机端口的服务来实现不同进程之间的消息同步、日志和报告的聚合工作,从而输出整齐漂亮的日志和测试报告。


下面给出 ginkgo 几个命令,可以看出:非常方便地实现并行执行、代码覆盖率度量和 XUnit 测试包的转换。


ginkgo -nodes = N 在N个并行进程中运行测试,并实时打印出一致的输出
ginkgo -cover 使用Go的代码覆盖率工具运行测试
ginkgo -coverprofile=FILENAME 指定覆盖率文件名称
ginkgo -outputdir=DIRECTORY 指定覆盖率文件存放目录
ginkgo convert将XUnit样式的测试包转换为Ginkgo样式的包

通过 ginkgo build、ginkgo -notify 等命令,还能进行测试服务分发、执行工作流时实现消息通知,这样很容易和 CI/CD(如 Jinkins)集成起来,实现全流程的自动化测试。通过 ginkgo bootstrap、ginkgo generate 可以创建测试集、测试用例模板,从而更好地实现测试复用。


ginkgo build PACKAGE_PATH编译测试集成.test文件,可部署到其他地方执行
ginkgo -notify 执行完成后触发通知,需要按照对应插件
ginkgo -r  递归执行文件夹内的所有测试用例
ginkgo bootstrap 创建测试集模板文件,会生成xxx_suite_test.go文件
ginkgo generate xxx 创建测试用例模板文件

Ginkgo 也支持第三方测试库:Gomock 和 Testify,还能和 Google Go 的 Agouti(基于浏览器的验收测试测试库)集成。


Ginkgo 借助 Gomega(匹配器 / 断言库,是 Ginkgo BDD 测试框架的最佳搭档)的 Eventually 和 Consistently 两大功能提供了原生的异步支持,能大大降低死锁或者未设置超时而异常卡住等问题的风险,提升执行的稳定性,而且能够减少没必要的等待时间。


Eventually(func() []int {
    return thing.SliceImMonitoring
}, TIMEOUT, POLLING_INTERVAL).Should(HaveLen(2))

Consistently(func() []int {
    return thing.MemoryUsage()
}, DURATION, POLLING_INTERVAL).Should(BeNumerically("<", 10))

针对分布式系统进行集成测试时,这个功能也很有用。


externalProcess.DoSomethingAmazing()
Eventually(func() bool {
    return somethingAmazingHappened()
}).Should(BeTrue())

至此,完成了本讲要讨论的内容,主要讨论了自动化测试框架的构成与分类,并从中选了三个具有代表性的框架进行了分析与展示,它们分别是单元测试框架 JUnit 5、API 层的 TA 测试框架Karate 和验收测试框架 Ginkgo。


最后留一个思考题,如果让你在 Ginkgo 和 Robot Framework 中选择一个框架,你会选择哪一个?为什么?欢迎留言参与讨论。