软件测试之我见
背景
本人并非专业测试岗位人员,我始终认为测试是软件工程的一部分,就像运维一样,都仅仅是分工而已,真正的软件工程师除了开发能力外,这些能力也是不可少的。
本人对于测试相关的经验主要来源于2012年在阿里做KV存储系统测试开发的5个月及2022年在datafuselabs从事databend SQL测试的两个月,时间是比较短暂的,本文仅代表个人观点。
测试分类
- 比较典型的分类是根据规模,分为小、中、大的测试。其中小测试围绕函数,比较典型的有单元测试(独立进程);中测试则包括集成测试(独立主机);大测试包括系统功能测试、e2e测试(集群)
从方法论上分类,则包括几个系统方法,比较常见的包括功能测试、模糊测试、混沌工程等
其他一些包括性能测试(压力测试)、稳定性测试(似乎和混沌工程有重叠的部分)、基准测试(涵盖了功能和性能)
我的观点
核心问题
- 自动化测试流程
软件是不断迭代的,测试不会只执行一次而是不断的反复执行,为了保障质量将测试集成到CICD流水线是很重要的;但测试的自动化也存在遗漏,因为能自动化的测试往往是比较明确的,对于发现新问题只能不断的完善用例,这时候需要其他工具来发现新问题,如混沌测试、模糊测试等。
- 选择和开发测试工具
好的工具对效率的提升是很大的,如果没有适合的工具那就自己动手吧,在构建测试工具的时候应该追求快速和好用,因此大量的测试工具是用python、perl、shell等脚本语言构建的,因为开发效率高。对于工具的选型,特别是相对成熟的软件领域,比如RDMS其实存在大量的前人经验可以借鉴,大量的公开的测试集可以使用。感谢这些无私的开源项目,分享自己的测试集。
- 从性价比最高的地方开始,文化的改变需要慢慢来
边际效应在测试里是很明显的,因此选择产出高的地方开始投入,而放弃对一些硬性指标的要求,在开发效率和捉虫效率上取得平衡。开发人员普遍有个不好的习惯就是爱开发不爱测试,其实测试用例的编写(尤其是单元测试和集成测试)在开发工作量中的占比是很高的,需要一个质量团队去设计规范和把握整体的用例组织,让开发人员写测试像填表格一样,减少心智负担,形成一个良好的测试驱动开发文化。
- 核心功能必须要测试覆盖,其他则要讲究平衡
重要的产品特性、外部API必须要有测试覆盖,因为这些是明确的且直接面向客户,换句话说,对外文档或者特性说明里包含的功能必须测试。
单元测试
单元测试最重要的是一些编写规范和覆盖率指标,编写规范各个语言有所区别,我就特别喜欢go基于表格驱动的单元测试,不同的语言可能存在不同的编写习惯或者规范,总之达成一致是团队协作的前提。
对于覆盖率包括行覆盖率和分支覆盖率,通常我们会看到质量团队对研发设置一个覆盖率指标如90%… 固然这个覆盖率越高代码测试的越充分,但并不代表着bug发现的越多。很多时候为了提升覆盖率指标,开发人员陷入一种交差的心态,而不是认真的设计用例的输入输出,导致覆盖率的提升陷入边际效应的尴尬。
先写测试还是先开发也是一个争议地带,测试驱动开发看起来十分的美好,但提前编写的单元测试经常会因为设计及实现的调整,需要重写,如果一开始就要求很高的覆盖率指标,那简直是一场灾难。测试驱动是好的,但前提是对功能设计的明确,每个函数、类、组件、API 要有明确的输入输出预期,只有当这些明确下来后,可以先编写测试,在去完善功能代码。
Mock工具的使用也是单元测试里的一个问题,因为不同的语言对Mock工具的实现情况不一样,有些语言很方便,有些则比较麻烦,遇到支持不好的情况,可能需要自己在设计API或者类的时候,额外的写一个Mock类专门用来测试使用。
集成测试
集成测试也有不同的维度,如一个分布式系统,可能一个组件(服务)就可以做集成测试,也可能是子系统这个维度集成。通常选择一个维度就可以了,冗余的测试依然是成本问题。
集成测试一般关系的是暴露给其他组件的功能和接口是否符合预期,比较经常的是一些API级别的测试,比如REST API、GRPC 等。
端到端(e2e)测试
端到端的测试是最大的测试,涉及到整个系统,如果系统存在不同的部署架构、可以运行在不同的系统或者CPU下,那么每个可能性都要被测试到。不能忽略任何一个可能提供给客户的情况。
我对e2e的认识主要是在CI里面构建e2e可能性模拟环境,比如使用k3d搭建测试的k8s集群,然后将服务部署上去,基于预置的功能测试脚本(如果是网站则使用网站自动化工具)进行测试。
对于非k8s部署的环境,底层系统架构的差异需要测试,包括部署架构师单体还是集群,部署在linux、windows、macos下,CPU是ARM还是x86,这些都要在CI流水线中体现。
功能测试
功能测试是一种质量保证过程,是一种黑盒测试,其测试用例基于被测软件组件的规范。
SQL的逻辑测试属于功能测试,验证数据库执行SQL返回的结果是否正确;网站的功能流程也属于功能测试。
功能测试通常不会关系实现细节,但对于输入输出的预期需要有明确的共识,需要了解软件的需求规格,并对需求规格中的条目设计测试用例。
用例的管理是功能测试重要的环境,通常是功能模块去划分,包含正常分支与异常分支。用例的设计有一些经典的方法论,如:
- 等价类划分方法
- 边界值分析方法
- 错误推测方法
- 因果图方法
- 判定表驱动分析方法
- 正交实验设计方法
- 功能图分析方法
比较典型的问题是需求不明确,测试过程中对输出的预期有分歧,很多功能开发没有计划和时间表,涉及到这些的时候其实已经不是测试问题了,尽量跳出测试层面从产品或者项目的角度去讨论。
性能测试
性能测试的相关工具还不少,但我对这块并不是很熟悉。
我认为性能测试是选型时候必须要考虑的,可以通过性能测试摸清楚整个关键特性的并发量、吞吐量、TPS、QPS、P99等关键指标,建立对整个系统容量的认识,通常性能测试完会输出一个性能报告。
业界做的比较好的,是clickhouse的性能测试,把各家产品拉到一个维度去打一遍,很好的体现他们的性能优势,参考 ClickBench — a Benchmark For Analytical DBMS
性能很重要,但不能迷信性能,好的架构设计可维护性也很重要,还有就是成本。
基准测试
基准测试像是功能测试与性能测试的结合,通过运行一套比较标准的测试数据集,其运行结果具有同行比对的权威性。比较典型的数据库基准测试TPC等(sysbench)
多数的业务系统并不一定关注基准测试。
模糊测试
模糊测试是一种自动化的测试技术,它会根据一定的规则自动或半自动地生成随机数据,然后将这些产生的随机数据输入到动态运行的被测程序入口,同时监控被测程序是否有异常情况出现,如系统崩溃、断言失败等来发现软件的缺陷。
模糊测试技术可发现那些使程序运行异常的缺陷,同时它也特别适合发现未知漏洞。从表面上看,这像是随机测试,其实测试数据的产生是基于一定规则的,规则可以设置,对于测试结果也需要一套规则来判断测试是否符合预期。
- 单元测试里的fuzz
典型如 go fuzzer
- 功能测试里的fuzz
以数据库的SQL fuzzer为例,典型的工具如 SQLancer、SQLmith,这些工具可以按照SQL的语法规则去生成测试语句,甚至能够校验结果是否正确,有3篇论文介绍,分别是:
- Finding Bugs in Database Systems via Query Partitioning
- Testing Database Engines via Pivoted Query Synthesis
- Detecting Optimization Bugs in Database Engines via Non-Optimizing Reference Engine Construction
- 其他fuzz
漏洞扫描工具会基于一定的规则生成测试报文去验证协议或者设备的安全性
因为各数据库的SQL语法有所区别,这些工具都是针对MYSQL构建的,要用的话必须对其进行扩展支持特定的SQL语法,存在一定的工作量。Fuzz测试针对的是不明确的bug,因此在自动化流程里,不能将其作为失败即阻塞的步骤,应该在流水线中设置失败即抓取失败的语句和异常输出,并自动生成bug报告到github issue或者其他bug系统中。在我看来fuzz是属于重要但不紧急的部分,功能测试的紧迫性大大于fuzz,因此当功能测试依然存在大量bug的时候,上fuzz的意义不是很大,这一点和混沌测试是一样的。
混沌测试(稳定性测试?)
混沌测试源于奈飞的chaosmonkey,2015年我在网龙的时候就跟人一起使用chaosmonkey对调研的服务发现系统做过混沌测试。现在回想起来多数人都把混沌工程理解成了故障注入,其实是有局限的。真实的混沌工程有一套系统的方法论,具体可以读读混沌工程:Netflix系统稳定性之道。
混沌工程的方法论
- 分布式系统上进行试验的学科,旨在提升系统的容错性,建立系统抵御生产环境中发生不可预知问题的信心核心理念
- 建立一个围绕稳定状态行为的假说 (业务运行状态的度量)
- 多样化真实世界的事件
- 在生产环境中进行试验 (试验环境越真实,价值越高)
- 持续自动的运行试验
- 最小化爆炸半径 (设计考虑、在工作时间进行测试;控制影响面)
原则
- 主动出击,反脆弱
- 坚持原则
- 明确的驱动目标而非简单的故障制造
- 常态化演练
实施前提
- 根据自身的架构及运行工作负载的方式去选择适合的工具
- 完善系统的可观测性,包括监控告警、日志、trace等
实施步骤
- 指制定试验计划
- 定义稳态指标(需要可观测性)
- 做出系统容错假设
- 执行实验
- 检查稳态指标
- 记录
- 恢复 实验
- 修复发现的问题
- 然后做持续验证
安全测试
主要包括使用静态审查工具对代码层面的安全进行检查;使用安全扫描工具对API、页面、服务端口等对外暴露的部分进行安全检查;对开发人员进行安全相关的培训宣导。
有一个相关的领域专门定义这部分工作就是DevSecOps。
总结
- 测试的自动化非常重要
- 一些新潮的方法论将在未来扮演越发重要的角色,如混沌工程、模糊测试
- 测试人员应该专注于工具和规范,研发人员应该深度参与到测试中
测试工具
- 自动化工具 ansible
- 自动化工具 robotframework
- 网站自动化 selenium or playwright
- 各自CI工具如Github Action、Jenkins ..