Skip to content

02经验凝练,反复践行的13条自动化测试框架设计原则

不知你是否遇到过以下问题:你的小组新开了个项目,要做自动化测试,老板说:"你们不是有自动化框架吗?把那个拿过来用"。接到这个任务后,你便兴致勃勃地去做了,结果发现要改的东西太多,无法直接在新项目使用原来的框架。

或者你正在调试一个测试脚本,它出错了,你却无法一眼看出来在那个地方出错了?定位问题要打断点一步一步调试。甚至公司原先写框架的那个人离职了,慢慢地,这个框架就废弃了,没人用了。

如果你被这些问题困扰过,你就知道,这些都是因为没有遵循测试设计原则导致的。

测试设计原则很重要,但还是经常会有同学问我,为什么要制定这么多原则?为什么不直接动手搭建框架?这时,我通常会举下面的例子来说明"按照原则进行框架设计"的必要性,它能帮助你少走很多弯路。

这个过程你会发现,如果你总是碰见一个问题解决一个问题,那么当需求积累至一定高度时,比如十万人都要过河时,你才发现已经无法单一性地解决问题了,你亟须建造一座桥梁,来系统解决问题。

所以测试框架的设计原则,在我看来就是造桥经验的教训总结,让你能够跳过建造木筏、小船这些步骤,而直接去建造桥梁,直达最优的根本方法,并保障在面对大规模、复杂场景应用时,仍然能发挥稳健。

以下五大类,合计 13 条设计原则是我多年经验的实践总结,希望你能充分理解,并在实践中灵活运用,从而少走一些弯路。

  • 清晰明了,学习成本低;

  • 通用性强、可维护、可扩展;

  • 对错误的处理能力强;

  • 运行效率高且功能强大;

  • 支持持续集成和版本控制。

接下来,我会展开一 一讲述。

清晰明了,学习成本低

自动化测试框架是个系统性工程,需要多成员一起运作,为了降低使用人员的学习成本,提升运行效率,自动化测试框架的代码、模块、报告应清晰明了。

1.代码规范

测试框架随着业务推进,必然会涉及代码的二次开发,所以代码编写应符合通用规范,代码命名符合业界标准,并且代码层次清晰。

特别在大型项目、多人协作型项目中,如果代码没有良好的规范,那么整个框架的代码会风格混杂、晦涩难懂,后续维护会很困难,最终成为没人敢动的"祖传代码"。

比如,下面的代码你要极力避免:

python
ll = [{"person": {"name": "kevin", "country": "cn", "traveled": "no"}, "test": 0}, 
    {"person": {"name": "Alex", "country": "us", "traveled": "yes"}, "test": 0}]
def hesuan_test(l):
    l1 = []
    for i in l:
        for j in i.keys():
            if (j =="test"):
                if(i["test"] == 0):
                        l1.append(i["person"]["name"] )
    return l1
if __name__ == "__main__":
    print(hesuan_test(ll))

上面的代码,定义了一个函数来判断列表的人是否需要做核酸检测。可以看到,代码有如下缺点:

  • 函数名称中英文混杂;

  • 函数体内变量命名随意,根本看不出业务含义;

  • 函数不简洁,这么简单的函数有 2 个 for 循环,2 个 if 判断分支语句,很烦琐;

总之,就是一点也不 "pythonic",不是简洁优美的代码,甚至很难让人看出它的功能。

我们通常说这样的代码有坏味道(Bad Smell),会毁掉代码的易用性和可维护性,编程时一定要注意避免。

下面我将这个代码优化下,示范一个好代码才会有的"代码即文档"的效果:

python
to_test_person_list = [{"person": {"name": "kevin", "country": "cn",   "traveled": "no"}, "test": 0}, 
    {"person": {"name": "Alex", "country": "us", "traveled": "yes"}, "test": 0}]
def covid19_test(person_list):
        return [person['person']['name'] for person in person_list if person['test'] == 0 ]
if __name__ == "__main__":
    print(covid19_test(to_test_person_list))

好的编程规范,可以帮我们快速理清业务逻辑,避免后续业务出现"搞不清逻辑不敢改" "不知道坑在哪里,不敢重构"的情况。

那么,业界标准的代码规范有哪些呢?不同语言的代码规范不尽相同。以 Python 为例,我们一般以 PEP 8 为准,具体请参考 Python Software Foundation 官网

2.模块清晰明确

模块化是将测试框架从逻辑上分为几个不同的模块,如下列的模块化分层的测试框架所示,使用者可以根据实际情况自行裁剪。

模块化分层的测试框架图

模块化的好处是可重用 ,并且便于替换修改

以上图为例,假设测试报告模块以前用的是 Allure,现在想替换成更加贴切自身业务的自研测试报告,我们仅需将报告模块替换掉就可以了。

但如果测试框架没有做模块化划分,测试报告是耦合在框架代码里的,那么就会导致无法切换测试报告,或者切换代价过大的问题,改动起来就会比较痛苦。

通用性强、可维护、可扩展

接下来我会向你讲解"通用性强、可维护、可扩展"这一设计原则,这点是从框架推广和维护角度出发来考虑的。

1.通用性强

  • 通用于不同的操作系统,比如,测试框架不仅适用在 Windows 操作系统上,还要适用在 MacOS、Linux 系统上,越通用,测试框架的受众就会越多。

  • 能解决同一类通用问题,比如,测试框架有个底层方法是用来操作弹出框的,那么无论是 Alert 框、确认框,还是一个允许用户输入的交互框,测试框架应该都能识别并操作。

2.可维护、可扩展

(1)可维护性

测试框架要做到容易维护,就一定要代码规范,模块清晰,除此之外整个测试框架代码风格还应该统一、易读、易懂。总之,要做到框架出问题时能容易定位并修改;更要做到,即使多人合作这个框架,这个框架代码要看起来是出自同一人之手。

可维护性无法用具体的指标衡量,也没有标准的实现方式。

但不可维护性是可以感知的,因为不可维护性常常以代码逻辑混乱,不遵循编码规则等特征出现。所以一般通过消除不可维护性来证明测试框架是可维护的。不可维护的典型例子便是代码逻辑,比如一部分判断逻辑嵌套了非常多层的 if....else,就像上面的反例代码一样,这样的代码不易理解,改起来容易出错,这是你必须要避免的。

(2)可扩展性

可扩展性指当需求变化时框架容易扩展。如果测试框架不能扩展,就无法解决业务发展带来的新问题,也就意味着测试框架的寿命会很短。

下面我举例说明下什么是可扩展性,假设测试框架运行测试的流程是:查找测试文件夹下的所有用例 → 判断该用例是否要运行 → 加入用例到待运行用例集 → 顺序运行测试用例 → 输出测试报告。

比如现在随着业务发展,我有了新需求: 需要按照一定的规则将"顺序运行"改为"并发运行",即将带有特定标签的测试用例改为"并发运行",而将没带有特定标签的测试用例继续保持"顺序运行"。

如果我们的测试框架可扩展,那么我仅需简单更改"顺序运行测试用例"这个模块的相关代码即可;反之,我则需要将测试流程重新设置甚至改造,所以我说可扩展性是测试框架的一个重点。

对错误的处理能力强

该原则是从测试运行的角度看的,当我们测试开始时,往往会运行很多测试用例,当测试出错时,测试框架如何处理才能让运行更有效率呢?

1.错误处理机制,高效解决

在测试运行中,难免由于种种原因运行错误,这时测试框架就必须具备处理错误的能力。错误处理机制一般分为停止运行和错误恢复两种。

  • 停止运行是指发现错误后直接停止本次测试,在实践中一般在测试框架本身出现错误的时候才会使用。

  • 针对具体的测试用例执行,错误恢复 这种方式比较常见。其步骤通常是标记当前用例为"失败",清理失败数据,恢复测试环境,然后再运行下一条测试。

    其中,根据错误恢复的时机又可以分为事先恢复 (当前用例运行前,将环境和数据恢复为初始状态)和事后恢复 (当前用例执行完成后,将环境和数据恢复为初始状态)两种。

    事先恢复现在是比较常用的,因为事后恢复可能会因为用例执行失败而永远执行不到。

2.系统日志清晰,方便调试

除了错误处理机制外,系统的操作日志也能帮你快速排查问题根源,所以平时的日志一定要清晰详细,最好具备上下文,这样才能根据日志进行有效调试,快速定位错误发生的原因。

对于测试框架来说,系统日志除了要按等级 DEBUG、INFO、WARN、ERROR 划分外,最好包括以下内容:

  • 记录测试用例的开始和结束时间;

  • 记录测试人员的关键操作(如写文件、连接 DB、更改 DB 等);

  • 关键方法的异常信息(如 run 模块出错部分的上下文信息等)。

运行效率高且功能强大

在当前的互联网大环境下,每时每刻都可能有构建(Build)发生,有了构建就需要不断地测试,那么运行效率的高低直接决定了构建和发布的次数多少。

1.支持测试环境切换

一个产品从开发到上线,会经历几个测试环境的测试,比如 dev 环境, 集成测试环境,预生产环境,生成环境等。所以测试框架要能做到,一套脚本多环境运行,支持环境切换,并且能根据环境进行自动化的配置(包括系统配置、测试数据配置等)。

2.支持外部数据驱动

根据外部输入数据,动态生成测试用例,并在测试报告中单独展示。测试框架会把这些只有数据不同,步骤和操作都相同的测试用例,在运行中解析成一个个不同的独立测试用例,并在测试运行结束后,全部逐一展示到测试报告里。

根据外部输入数据,动态切换运行用例。测试目的不同,其需要采用的测试用例也会不同,所以自动化测试框架会给各个测试用例打上标签,再根据需要,自动选择具备特定标签的测试用例进行运行。

3.支顺序、并发、远程运行

当你的测试用例有上千条,甚至上万条时,顺序测试会花费大量的时间。为了快速得到测试结果,测试框架应该支持顺序、并发、远程执行,这样能够缩短测试用例的整体执行时间。

4.报告完备详尽

测试报告是 QA 工作中的重要一环,通常在一个项目结束或者一个 sprint 结束时发出。

虽然,在实际工作中,我们经常听到大家抱怨说测试报告太烦琐了,又不产生什么直接价值,但完备详尽的测试报告,不仅可以述说 QA 到底做了哪些工作,还可以看出整个项目的生命周期运行得平稳与否,软件的质量如何。

测试报告应该包含哪些部分?这些部分如何罗列?分别能看出来什么问题?你可以参考我的公众号 iTesting 发布过的一篇文章《你真的能看懂测试报告吗?》,来详细了解一份好的测试报告,究竟有哪些威力。

5.解决当前没有解决的问题

"不要重复造轮子"是工具创造的首要原则。从功能角度看,框架得到认可,要么是解决了当前无法解决的问题 ,要么是解决方案比当下的更好

例如,Selenium/WebDriver 最开始为人所知是因为它开源、可跨平台;后来 Selenium/WebDriver 的替代者 Cypress 为人所知,是因为它还具备运行在浏览器之内,且自备 Mock 的能力。

所以,你的框架能不能被认可,就在于它是否具有独特的功能特性,这是与其他框架区别开来的标签,也是弥补市场空白的撒手锏。

支持版本控制和持续集成

版本控制可以让使用者更好地理解框架的演变历史;框架支持持续集成可以让框架迅速融入公司的技术体系中,使框架被越来越多的团队接纳。

1. 版本控制,回溯复盘

什么是版本控制?其实就是将代码纳入版本控制系统(如 Git)的管理之下。那么为什么测试框架要做版本控制呢?请思考如下问题:

  • 你开发了功能 A,老板说这个功能不要,你就把 A 代码删除了。等一个月后业务发生了变化,功能 A 又变得需要了,如果没有版本控制,你怎么把 A 代码恢复回来?

  • 我们知道,当前的测试开发中,一个人单打独斗的情形很少见了,常见的是团队协作开发。那么假设你和 B 在开发不同的功能,但是都改动到了同一个底层共享模块。那么如果没有版本控制,你们的代码提交后还能正常工作吗?

假设有了版本控制,那么这些问题发生后,复盘时就非常容易找到根本原因,代码回溯也很方便,所以测试框架应该支持版本控制。

此外,还有一个用处就是对测试代码进行版本控制。假设你同时需要支持同一个微服务的两个不同版本的业务测试。有了版本控制,你的不同版本的测试代码就能以不同分支的形式出现,否则,你只能一次保持一个版本的代码,非常不方便。

有了版本控制,不仅协作开发、版本切换变得非常容易,使用者也可以通过查看版本之间的变化来理解框架的发展脉络。

2. 持续集成,全局出发

我要讲解的最后一条原则是"支持持续集成",前面的原则是从测试本身角度出发的,而"持续集成"是从整个公司业务出发,需要你与整个开发团队合作完成,同时这是你晋级"资深"的体现。

测试框架应该能方便地集成至公司的持续集成系统 ,并且通过持续集成系统触发测试。我在**"开篇词 | 功能测试转测试开发的正确姿势"里提过,持续集成是资深测试开发**需要重点关注的部分。

一般来讲,公司的持续集成和持续发布系统通常由 DevOps 和开发架构师打造,测试要做的就是将测试框架融入公司的持续集成和持续发布技术栈

那么测试框架就应支持通过持续集成系统,触发测试用例运行。具体来说就是:当某个代码提交的 hook 被触发时,持续集成会打包并部署最新代码到测试机上,此时测试框架及其对应的测试用例应能被唤醒并执行。

支持持续集成的程度决定了框架在团队和公司的接纳度。支持持续集成的成本越小,框架就越容易被推广和深度使用。

小结

最后我们来总结下今天的内容。

今天我讲了测试框架的设计原则,共五大类,合计 13 条,希望你在编写测试框架时能参考这些原则,少走一些弯路。这里的每一条原则,其实都是失败经验的总结,也就是实践的结果。

我想强调的是,好的测试框架,都不是设计出来的,除了参考原则,还需要你多多实践。只有不断实践,你的测试框架才能逐步演化成你希望的模样。所以希望你在后续的测试框架编写中,能多实践多总结。

好了,今天的分享就到这里,为了方便你理解,我把今天的内容制作成了一个脑图,供你查阅。


"测试开发工程师名企直推营" 入口,免费领取 50G 资料包