你好,我是茹炳晟。
今天的“答疑解惑”文章,我将针对API自动化测试和代码级测试这两个系列6篇文章中的问题,和你展开分享。
我还是会先简单概括下每篇文章的内容,并给出文章链接,帮助你复习相应的内容。同时,如果你再次阅读时还有哪些疑问的话,也欢迎你在文章下面继续留言。我会一直关注着你的学习情况,希望可以扫清软件测试精进道路上的障碍。
现在,我们就开始今天的主题吧。
在专栏的第22篇文章《从0到1:API测试怎么做?常用API测试工具简介》中,我以基于主流Spring Boot框架开发的简单Restful API为例,分别介绍如何使用cURL和Postman对其进行最基本的功能测试,希望可以让你先对API测试有个感性认识。另外,在这篇文章中,我还和你分享了目前一些常见的典型复杂场景,以及相应的测试思路和方法。
而在文章最后,我希望你思考的是实际项目中往往会存在按时序的API调用以及异步API调用,这类API测试要如何开展?现在,我来说说我的经验吧。
我们先一起看看按时序调用的API序列的测试场景。
对于此类测试,我一般建议通过GUI操作来录制API的调用。比如,在启用Fiddler的情况下,通过GUI来完成业务操作,随后去分析Fiddler抓取到的后端API请求顺序,然后以此来开发API测试用例。
开发测试用例的过程中还需要特别关注前后两个API调用之间的数据传递,比如需要将前一个API调用返回的response中的某个值作为参数传递给下一个API调用。
其次是异步API的测试。对于异步API测试的场景,我们往往先会采取“只验证其是否发起了正确的调用,而不直接验证操作结果”的方式。比如,你的被测API是一个异步操作的API,那么我只会去验证这个API是否按照预期发起了正确的异步调用请求,而不会直接去验证异步操作的结果。如果这类测试全部通过后,我们才会考虑真正验证异步操作结果的测试用例。
举个实际的例子,假设你的被测API A完成的是下订单的操作。这个API A完成下订单操作要通过调用另外一个API B将订单信息写入到消息队列中去。而真正下订单成功指的是消息队列中的消息被后续服务正确处理并且成功了。此时,这里的后续消息处理就是异步的操作了。
那么,当我们要测试这个API A的时候,我们只需要验证它是否正确地发起了对API B的调用即可,而不用关心API B的具体行为结果。
也就是说,我们只关注API A是否以正确的参数调用了API B即可,而无需关注API B是否正确地执行了将订单信息写入消息队列的操作,更不用关注,消息队列中的消息被异步处理的结果。
注意这里的测试重点,应该更多地放在前面的部分,而真正验证异步操作结果的测试在资源有限的情况下只需覆盖最典型的场景即可。
在专栏的第23篇文章《知其然知其所以然:聊聊API自动化测试框架的前世今生》中,我和你分享了API自动化测试框架的发展历程,帮助你理解API测试是如何一步一步地发展成今天的样子,希望可以以这种“知其所以然”的方式加深你对API自动化测试的理解。
而在这篇文章最后,我提到了基于配置文件的API测试框架,比如典型的HttpRunner。在此类API测试框架的支持下,测试用例本身往往就是纯粹的配置文件了。如果你用过这个框架的话,我希望你可以谈谈你的看法。
对于基于配置文件的API测试框架的确是个不错的方向,尤其是国内的开源框架HttpRunner更是推动了这种测试框架的普及。
基于配置文件的API测试框架的优势,可以归纳为以下三方面:
降低了测试用例开发的门槛,使得完全没有代码基础的同学也可以很容易地完成API测试;
可以很方便地将API功能测试用例直接转换成API性能测试的用例(HttpRunner可以使用lucust直接实现这样的转换);
同时,HttpRunner这类工具还支持直接从网络转发工具得到的HAR中提取API调用的测试用例,进一步降低了API测试用例的开发成本。
所以,基于配置文件的API测试框架很受初学者的欢迎。
但是,为了完成一些复杂场景的测试用例设计以及复杂的结果判断,你还是需要具备基本的代码能力,以完成这些复杂场景的测试实现。比如,HttpRunner就会涉及到使用debugtalk.py来实现hook函数的功能扩展。
也就是说,完全不写代码的API测试只能覆盖大部分的简单测试场景,如果你要搞定复杂场景的API测试的话,你是要必须掌握一些基本的开发技能,这里没有任何捷径可走。
另外,很多读者的留言也很精彩,我这里特地选取了两条供大家参考。从Cynthia的留言中,我看得出她已经完全习得了这篇文章中描述的方法的精髓,这也正是我想要传达给你的最核心的内容。
- Martin在留言的后半部分中提到,通过HttpRunner来实现轻量级的API测试的确是个好方法,也最大程度地发挥了HttpRunner的价值。但是,留言前半部分的“Postman转Python或者Java”的观点我并不是很认同。其实,Postman是有直接代码转换功能的,而且支持各种语言的各种框架,基本可以实现一键操作,所以,其实很多没有采用HttpRunner的企业都还在普遍使用这个方法。
在专栏的第24篇文章《紧跟时代步伐:微服务模式下API测试要怎么做?》中,我和你分享了微服务模式下的API测试,旨在帮助你认清庞大的测试用例数量、微服务之间的相互耦合这两个问题的本质,以更好地开展测试。所以,我今天分享这个主题的目的就是,帮你理解这两个问题的本质,以及如何基于消费者契约的方法来解决这两个难题。
而在今天这篇文章最后,我希望你思考的是:基于消费者契约的API测试中,对于那些新开发的API,或者加了新功能的API,由于之前都没有实际的消费者,所以你无法通过API Gateway方法得到契约。对于这种情况,你会采用什么方法来解决呢?
从我的经验来看,因为缺乏契约,所以还是会采用传统的API测试方法,也就是根据API设计文档来设计测试用例。
这时,我们采取的API测试策略是:
在专栏的第25篇文章《不破不立:掌握代码级测试的基本理念与方法》中,我根据实际工程项目中的实践,总结了五种常见的代码错误,以及对应的四大类代码级测试方法。这里我还想在多啰嗦一句,代码级测试的测试方法一定是一套测试方法的集合,而不是一个测试方法。
而在这篇文章最后,我希望你分享一下你所在公司,在进行代码级测试时采用过哪些方法,又是如何具体开展的。这里我来分享下我在eBay的经验吧。
在eBay,代码质量保障已经完全纳入了CI/CD流水线。
首先,我们基于Sonar启用了静态代码。除了在上传Git的时候触发静态扫描外,开发人员在本地IDE中也会进行实时的静态扫描,并可以实时看到分析结果,这样就可以在代码被递交到代码仓库前就已经完成了预检测。
其实,我们并没有直接采用标准的代码静态扫描的规则库,而是删除了其中很多我们认为过于严格的规则,同时加入了一些我们认为比较重要的检测项,使得这个规则库更符合我们的业务场景。一般情况下,这些规则的修订是由测试架构师牵头,与开发主管和资深的开发人员一起协商决定的。
这里需要注意的是,我们并不要求静态扫描上报的所有错误都被修复后才能发布,只要求解决最关键的问题即可。而对于那些所谓的Code Smell问题,基于研发成本的考虑,我们并不会要求完全修复。
接着CI/CD流水线会触发代码动态测试,即单元测试。这里,我们不仅要求单元测试能够100%通过,并且会要求达到一定的代码覆盖率。在eBay,我们对不同模块的代码覆盖率要求也不一样,并没有一个硬性指标。其实,这也是出于研发成本的考虑。
通常来讲,对底层模块以及提供公共服务的中间件的代码覆盖率指标的要求,一般都会比较高。而我们对前端模块的覆盖率要求,就会低很多。
在专栏的第26篇文章《深入浅出之静态测试方法》中,我和你详细分享了人工静态测试方法和自动静态测试方法,来帮你理解研发流程上是如何保证代码质量的。另外,我以Sonar为例,和你分享了如何搭建自己的自动静态代码扫描方案,并且应用到项目的日常开发工作中去。
而在这篇文章最后,我希望你分享的是除了Sonar你还用过哪些静态代码扫描工具,使用过程中遇到过哪些问题。
其实优秀的代码静态扫描工具远远不止Sonar,比如Fortify SCA和Checkmarx CxSuite等都是很优秀的静态扫描工具。至于使用过程中需要的问题,我觉得主要有这么三个:
第一是误报率。过高的误报率会降低开发人员对测试工具的信任度,而且还会引入很多人为标注的工作量。
第二是规则库的完备性和实用性。很多时候你会发现,标准代码规则库中的一些规则设计不够合理,有点教条主义。比如,有些规则库会强行规定一个函数的代码行数不能超过200行,从代码的模块化和易维护性角度来讲,过长的函数实现体的确不利于代码健康。但是,也不能完全一刀切,毕竟有些函数就是实现起来比较复杂。所以,很多时候我们需要对标准规则库进行深层次地裁剪,以更好地适应企业的实际情况。
第三是自定义规则的难易程度。虽然很多静态代码工具都提供了规则编辑器,来方便你实现自己的规则,但是这些规则编辑器的使用方法和语法的学习成本比较高,对初学者不够友好。
在专栏的第27篇文章《深入浅出之动态测试方法》中,我和你分享了人工动态测试方法和自动动态测试方法。因为自动动态方法并不能理解代码逻辑,所以仅仅被用于发现异常、崩溃和超时这类“有特征”的错误,而对于代码逻辑功能的测试,主要还是要依靠人工动态方法。
在这篇文章最后,我希望你分享的是,除了我在文中提到的几个单元测试的难点问题,你还遇到过哪些问题,又是如何解决的。
这里,我想再和你分享我曾在单元测试中遇到过的问题。
单元测试的难点中较为典型的就是对内部输入的控制。对于内部输入的控制有数十种不同的场景,这里我就举一个例子,意在抛砖引玉。
首先为了达到较高的测试代码覆盖率,如果代码中包括了if-else分支,那么我们的测试就需要分别执行到这两个分支。假设,现在有一个if-else分支是根据malloc这个内存分配函数的结果进行不同的处理,如果内存分配成功了,就执行A逻辑,如果执行失败了就执行B逻辑。
那么,在做单元测试的时候,通常情况下很容易覆盖内存分配成功的场景,但是想要实现“可控”的内存分配失败就比较困难了。因为malloc是个底层系统函数,根本无法对其控制。
为了解决这个问题,我们就可以采用桩函数的思想,引入一个malloc的桩函数,在这个桩函数的内部再去调用真正的系统malloc函数,如果需要模拟真正的malloc函数的失败,就在桩函数里面直接返回malloc函数失败的返回值,来达到模拟真正malloc函数失败场景的目的。这样就能在被测函数中通过可控的方式,来模拟系统底层函数的返回值了。
最后,感谢你能认真阅读第22~27这6篇文章的内容,并写下了你的想法和问题。期待你能继续关注我的专栏,继续留下你对文章内容的思考,我也在一直关注着你的留言、你的学习情况。
感谢你的支持,我们下一期答疑文章再见!