有赞目前,结合insight接口自动化平台、horizons用例管理平台、引流回放平台、页面比对工具、数据工厂等,在研发全流程中,已经沉淀了对应的质量保障的实践经验,并在逐渐的进化中。
二、调研过程
模糊测试(Fuzzing)的核心思想是通过系统自动生成随机数据作为输入,来验证被测程序的可靠性。在测试领域中,Fuzzing经常作为一种补充接口测试手段,来覆盖/探索接口中潜在的异常/临界值场景。简单来说,系统通过给定种子用例,随机生成大批量用例,调用被测接口,尝试发现问题(挖掘bug)。
模糊测试的难点在于如何基于种子用例生成随机有效的用例数据,从业界的经验来看,测试人员通过对生成内容进行建模、设计相应算法来匹配被测对象,才能取得比较好的生成效果。随着ChatGPT的发布,其AIGC的能力令人惊艳,如果借助ChatGPT的优势,能否降低生成随机数据的成本呢?于是,笔者围绕ChatGPT生成用例的可行性进行了尝试。
为了验证ChatGPT生成数据的能力,笔者随机找了一份公司的PRD,摘取了一部分需求(已脱敏)来测试ChatGPT生成用例的质量,以下是调研过程中的部分结果展示。
摘取需求:商品设置了会员价-会员等级-打折,则群团团下单享受会员折扣。前置条件为当前店铺A,消费者P是店铺的会员,店铺A笔记内的商品M售价100元,运费0元,店铺设置了该商品M会员打8折。
部分问答内容:
经过上述的调研,模糊测试(Fuzzing)的思路是基于种子用例生成随机用例->执行用例->发现问题(bug挖掘),但其难点在于如何生成高质量的随机用例,而ChatGPT的内容生成能力,似乎可以解决这一问题。笔者将两者尝试结合,模糊测试作为核心思想,ChatGPT作为用例生成服务,目标是通过大量ChatGPT生成的用例,来挖掘被测对象潜在的问题。
在自动化测试中,如果仅依赖模糊测试和ChatGPT生成的用例还不够,因为我们无法判断ChatGPT生成的用例是否有效,笔者尝试引入了自动化测试覆盖率的概念,将整体流程给串联起来:以模糊测试为基石,让ChatGPT来充当规则变异器,自动生成接口测试用例,覆盖率作为检验生成用例的有效性,目的是发现问题和提高自动化测试的效率。
下面,笔者将以ChatGPT用例生成、bug挖掘、代码覆盖率作为主线,来进行AI自动化测试实践。
三、设计与实现
前言提到,有赞已经有几个成熟的平台可以使用,为了降低实现的成本,笔者将尽可能基于现有平台的能力,来做设计与实现。
有赞目前已有成熟的接口测试平台insight、流量回放平台zan-hunter,来承接日常接口测试、线上巡检、引流回放等测试活动。基于调研结果,笔者经过整理,核心思路可概括为拾取用例->生成用例->执行结果判别->覆盖率条件循环。具象的说,就是(1)insight/zan-hunter获取用例生成模版数据->(2)根据模版数据生成ChatGPT的输入(prompt)->(3)调用ChatGPT,根据要求生成用例(JSON输出)->(4)执行用例,调用java应用的接口进行测试,输出测试结果(bug挖掘)->(5)获取接口对应的行覆盖率,并根据判断是否要继续执行->(6)循环往复,直到覆盖率摸高到天花板(可能是70%~80%)->(9)End。
在现有资源限制、功能实现复杂度较高的背景下,笔者将其拆解为2个阶段来完成,同时本期将优先实现第一阶段的功能:
笔者根据实际情况,将实现内容做了拆解。
目前,第一阶段的开发和验证已经结束,包括基于ChatGPT的推荐用例生成服务、用例执行、断言回写、结果输出等。实践结果证明基于ChatGPT创建的推荐用例,已经能够正确执行并感知到代码异常错误。第二阶段做了初步调研,暂未实现功能。
基于ChatGPT的推荐用例生成服务:前端页面输入提供生成规则,系统推荐参数模版;后端基于GPT-3.5模型,设计prompt来生成准确可靠的随机内容入参,结合insight已有能力,创建测试用例与执行集。
用例创建执行:基于ChatGPT生成的随机内容入参构建测试用例和创建执行集,insight执行并获得测试报告。(左:执行集详情;右:执行集结果)
断言回写:由于执行结果的不确定性,我们将每一条用例第一次执行的结果作为用例基准断言。
结果输出:insight平台还没有合适的聚合结果展示能力,我们将每一条用例第一次执行的结果、后续执行的失败结果,系统进行规则过滤后,将潜在风险问题均输出到飞书文档,方便测试人员可视化观察。(左:平台单条错误展示;右:飞书表格聚合展示)
bug挖掘:由于输出的结果经过系统的第一次过滤,产生报告量数据适中,可以通过人为检查结果的方式来判断在当次执行的结果中,是否存在有效问题。
用例生成质量可以通过能否发现有效问题,或者接口能够正常拦截异常输入的角度来判定生成质量好坏。
笔者在测试过程中,主要对分销员业务接口(包含少量美业、群团团接口)进行bug挖掘验证,筛选验证接口共25(21+1+3)个接口,通过ChatGPT共生成了3400余条推荐用例,共发现4个有效问题,剩余21个接口能正常拦截异常错误。其中,发现的有效问题主要是SQL注入类异常报错(涉及代码安全问题)、业务代码NPE处理不健全2类;正常拦截错误的接口,说明其接口相对健壮,能够合法处理异常输入且不影响业务使用。
从测试的结果来看,用例生成质量符合预期(能发现问题)。
结合目前第一阶段计划中已经实现的功能和使用效果,笔者对其有了更加深刻的认知,主要从工具定位、工具承担的使命、工具和现有测试手段的关系做了简短的总结。当然,现有的结论下,并不会影响到先前第二阶段的初步设计,在解决了第一阶段发现的问题后,继续推进。
新接口的异常测试入参校验在没有经过测试验证前,校验项是容易存在遗漏缺失,通过随机生成的入参内容,可以用来验证校验逻辑的完备性
生成的推荐用例可以用来验证同一接口在不同代码版本下响应,理论上针对推荐用例的响应,预期是一致的
四、未来的计划
使用推荐用例生成服务,需要提供关键的参数格式、生成数量和规则给到ChatGPT。如果不对生成规则在生成的时候,总是会生成错误的、不符合预期的数据,这样的用例在实际执行的过程中,大概率是不能走到对应的业务逻辑代码中去,导致无效用例生成数量在执行集中的占比偏高,执行效果不理想。
如果需要解决该问题,有2种比较合适的方案:
受限于ChatGPT使用资源、私有模型数据集整理收集的高昂成本问题,笔者退而求其次尝试了第二种方法,尽可能的将生成结果靠近预期内的数据。
这里尝试借鉴使用了MVEL模版语言的规则,经过验证,ChatGPT是能够自主理解MVEL模版语言和如何使用该语言的,即使结果不太符合预期,也可以通过提示使ChatGPT更加理解我们的需求,并生成准确数据。
当然,标准的MVEL语言有自己的编写规范,我们要做的是设定自己的编写方式,目的是降低使用者在撰写推荐入参时的理解成本。为了让ChatGPT理解什么时候要通过MVEL模版语言进行生成数据,我们通过MVEL()来标记字段,并结合prompt来传达生成规则。
我们现在可以通过自定义MVEL表达式编写方式,来解决具有业务语义参数的指向性生成,比如在接口参数中需要指定"kdtId"字段的取值范围在某几个特定的店铺范围内,可以在入参中写为{"kdtId":"MVEL(1||55||160)"},ChatGPT在处理字段数据生成时,就会按照自定义规则在[1,55,160]数组中选取任意一个数字作为"kdtId"的值。但是,在方案二的实现中,我们仍然不能要求ChatGPT自主生成期望的“随机”业务语义数据,这也是其带来的一个弊端。
刚开始接触ChatGPT时,如何准确的向ChatGPT传达我的需求,成为了最大的问题。最开始都是将它作为搜索工具来使用的,类似于百度、谷歌,用来解决代码问题时特别好用。但当我需要解决一个复杂问题的时候,如果直接通过人类语言描述的方式,ChatGPT理解起来有一定难度,经常答非所问。并且,ChatGPT说到底还是LLM,本质还是在根据提供的上下文,预测下一个最优解。所以如何构建ChatGPT能够理解的上下文,成为了破局的关键。在这次实践中,我需要拆解我的需求,以及构建尽可能简短精炼的prompt(应对token上限)。
在prompt设计中,我首先选择中文来编写我的prompt,毕竟是母语,能够直白快速的撰写我所理解的需求。实际使用体验下来,基本上能够满足我的要求。但也发现了几个问题:
即使是在prompt中使用了Chinglish(中式英语),英文版本的执行效果还是略好于中文版本。笔者在使用英文prompt和ChatGPT进行交互时,发现其回答的响应速度有明显的提升,不过其回答的准确度似乎有所降低,可能原因在于我去掉了中文prompt中的详细规则描述。
回答准确度降低的表现形式,包括但不限于1.输出格式不为json格式;2.生成内容重复率高;3.不同结构的入参格式在生成过程中的解析能力差导致生成错误等。得益于"gpt-3.5-turbo-16k-0613"新版本,prompt的编写可以不再考虑token的限制了(目前需求达不到16Ktokens上限),同时还能使用到functioncalling的新特性,笔者尝试在英文prompt中增加了问答案例(让ChatGPT学习生成规则、方式)、函数调用的能力(结合问答案例,方便输入固定格式内容进行问答,便于工程化管理),优化了输出格式的规则要求等其他措施,基本解决了生成内容不稳定的问题。
当然,现有版本还有很大的优化空间,包括如何保障json格式的稳定输入输出(引入DSL)、paramTemplate的解析稳定性、ChatGPT生成数据的准确性、通过案例数据集微调私有模型等,在未来的规划中,会尝试逐一去解决这些问题。
在以上问题解决后,用例生成服务在调用ChatGPT生成内容时,还是会出现一些奇怪的回答,目前只能发现一例解决一例。笔者认为,在基于ChatGPT生成数据功能的架构设计时,要多考虑工程化上的保障措施,特别是强依赖ChatGPT返回内容的场景。
如何选择合适的断言作为第一次执行结果的判断,也困扰了笔者很久,毕竟对于未知的入参,其响应也是未知的,到底如何判定其运行结果的有效性呢?
在第一次设计断言回写时,笔者拍脑袋的认为状态码等于200就可以(其实是偷懒了~)。在实际测试和使用的过程中,证明了这个断言是多么愚蠢!根本原因是这类断言没有解决判断运行结果的有效性,大部分用例的执行结果状态码都是200,压根发现不了问题。
笔者和小伙伴进行了头脑风暴讨论,重新审视了使用断言的方式,发现和最初设想的不一样。最初的想法是基于模糊测试来发现问题,但并不需要对预期结果有要求,现在受限于现成平台(insight)的限制,断言成了强依赖,导致无法绕过断言设计的问题。
于是,我们转变了思路。既然我们的目标是发现问题,那么此断言可不作为预期断言。假设代码执行结果作为正确结果,通过在不同代码版本上去执行相同的用例,均以该结果为准,如果断言发生了报错,则能帮助我们发现预期外的问题。所以,笔者最终决定将其自生成的用例的第一次执行结果作为用例本身的断言。举个例子:在master代码版本V1中,创建了推荐用例集,执行后断言回写到用例集,当下一次master代码版本V2发布时,执行用例集,如果发现断言失败的情况,说明有场景不符合上一次返回的结果,可以介入排查问题。但该做法也有弊端,就是不能及时发现问题。
为了更加及时的发现问题,笔者尝试将2种方式结合,通过状态码等于200来初筛一遍执行结果,输出初筛报告方便人工检查;同时回写第一次执行的结果作为断言,以便与下一次回归测试时使用。总的来说,效果还算可以,但仍不是最终的解决办法。在下一个迭代计划中,笔者也会继续尝试解决该问题。
参考文档:TCP-Fuzz:DetectingMemoryandSemanticBugsinTCPStackswithFuzzing