引言
在软件开发这一充满创造性的领域中,开发人员不仅要构建功能强大的软件,还要确保这些软件的稳定性和可靠性。然而,开发过程中并非所有任务都能激发创造力,有些甚至是重复且乏味的。其中,编写单元测试无疑是最令人头疼的任务之一,它虽然对于验证软件组件是否按预期工作至关重要,但其编写过程却常常让开发人员感到枯燥和疲惫。
理想的情况是,开发人员在编写代码的同时编写单元测试。但是编写单元测试是软件开发中的繁琐工作,会占用开发人员大量时间。并且开发人员在手动编写复杂代码库的单元测试时可能会犯错。因此,很多软件缺乏足够的单元测试,这使得代码难以维护。如果没有单元测试,出现问题后定位问题就如同大海捞针,这不仅增加了维护成本,也降低了软件的可靠性。
在这样的背景下,AI代码助手应运而生,它通过大模型辅助生成代码来减轻开发人员在单元测试编写上的负担,提高开发效率,同时确保软件质量。它将如何帮助开发人员克服单元测试的挑战,是我们接下来要探讨的主题。
一、 单元测试概述
单元测试专注于程序中的最小可测试单元,这通常包括函数、类的方法或模块等。其核心目的是确保这些基本单元能够正确执行特定的功能,并妥善处理各种边界条件和异常情况。单元测试的目的是确保这些基本组成单元按照预期工作,能够正确地执行特定的功能,并且能够妥善处理各种边界条件和异常情况。
单元测试一般情况下需要符合如下特点:
自动化:单元测试由开发人员编写,并使用自动化测试框架运行,能够快速反馈测试结果,提高测试效率。
隔离性:每个单元测试独立运行,将被测试单元与其他代码隔离,确保测试的准确性和可靠性。
针对性:每个测试用例都针对代码中一个具体的、明确的行为,确保测试的精确性和有效性。
重复性:单元测试可以频繁重复运行,尤其是在代码修改后,以确保新修改没有引入错误。
及时反馈:单元测试能够快速发现错误并定位问题,提高开发效率和软件质量。
单元测试一般会包含以下类型:
函数测试:验证函数或方法在给定输入下是否返回预期的输出。这包括测试各种正常的输入值,以确保函数按照预期工作
边界条件测试:测试函数或方法在输入值达到边界或极限情况下的行为。例如,对于一个接受整数输入的函数,边界条件测试应该包括测试最小值、最大值、以及接近最小值和最大值的数值。这可以帮助发现函数在处理极端情况时的错误
异常处理测试:测试函数或方法在遇到异常情况(例如无效输入、错误参数或资源不足)时的行为。这包括测试函数是否能够正确地处理异常,并返回适当的错误信息或采取相应的补救措施
Mock 测试:使用模拟对象来代替真实的依赖项(例如数据库连接或外部API),从而隔离被测单元,并确保测试结果的可靠性。这对于测试依赖于外部资源的函数或方法非常有用,因为它可以避免测试结果受到外部因素的影响
数据驱动测试:使用一个数据表来驱动测试用例的执行。测试用例的输入数据和预期输出数据都存储在数据表中,测试程序会读取数据表中的数据,并自动执行测试用例。这可以减少测试代码的冗余,并提高测试效率,尤其是在需要测试大量不同输入值的情况下
在实际的开发场景中,需要结合业务的真实需求,选择合适的测试类型,来确保代码质量和功能正确性。
二、 AI 在单元测试中的应用
以下是 AI 代码助手在单元测试中的应用实践:
代码补全与单元测试生成: AI 代码助手基于上下文理解,能够自动推荐最可能的代码片段,包括方法调用、变量声明、循环结构等。此外,它还能根据现有代码结构,自动生成对应的单元测试案例,确保代码变更时功能的稳定性。
可以通过对话框中的 /test 生成单元测试,或者使用 IDE 编码区域中使用每个功能方法的快捷键“生成测试”来生成该方法的单元测试。
多轮对话优化测试用例: 开发者可以通过多轮对话,告诉 AI 更多的信息,让生成的单元测试内容更符合业务预期。例如,可以指定业务边界条件、特殊的异常处理逻辑、数据处理方式等。
对于生成的内容,如果有额外的测试场景需求,可以通过对论对话追加提问。
AI 代码助手的对话模型会识别用户意图结合上下文对话内容优化单元测试结果。
一键应用单元测试内容: 通过对话框结果中的快捷按钮,如“应用”、“插入到 IDE”等,开发者可以快速判断与接受生成的单元测试代码。
可以选择“应用”按钮,将对话生成的结果,作用到对应的代码文件中。
可以在 IDE 编码区域看到“应用”这部分代码对于当前代码文件的修改情况,让研发人员快速识别到改动,快速判断与接受。
通过这些应用功能,我们可以看到 AI 代码助手在单元测试中的强大能力,它不仅提高了开发效率,还帮助确保了软件的质量和稳定性。
三、实际编码案例解析
针对业务代码准确生成单元测试
在真实的开发场景中,软件的功能都是异常复杂的,一个完整的软件功能有多个类、方法共同组成。以 Java 开发为例,一个功能的开发,可能由 类的定义、Controller 控制层的方法、Server 接口定义、ServiceImpl 接口实现类方法、Repository 和 DTO 等等部分组成。在生成单元测试的时候,需要考虑到真实业务中的各个组件关联性。
以上图这个 Java 工程为例,我们需要 AI 帮我们准确生成接口实现类 EstateServiceImpl.java 中的查询和模糊查询方法对应的单元测试代码。
我们选中 getEstates 和 getEstatesContainingText 方法之后,使用 /test 为其生成单元测试代码。在结果中可以看到,AI 代码助手在生成单元测试的时候,会分析代码的工程结构,并在生成的时候智能 import 相关依赖;在生成测试代码的时候,会结合其他文件中的类和方法定义生成测试计划,并准确构造测试数据。
将生成的单元测试内容,插入到指定单元测试文件中,可以直接执行 mvn test 命令进行单元测试验证,可以看到这里生成的代码可以编译通过,并完成单元测试功能
结合业务逻辑逐步完善测试结果
在真实的业务开发过程中,有很多测试场景都是隐晦的,它的逻辑可能并没有直接定义在代码内容中,而是需要结合实际需求而定。这种场景下,需要研发人员使用对话功能告诉 AI 代码助手这里的业务测试逻辑,继而完善测试代码。
如上图所示,我们可以通过多轮对话能力,继续输入真实业务场景,丰富测试的边界条件,让 AI 持续优化单元测试结果。
我们将生成的扩展内容应用到之前的单元测试代码中,并执行下这里生成的最终结果,可以看到执行成功。
四、AI 单元测试的优势
从上文的实际单元测试生成场景中可以看到, AI 代码助手可以准确的生成单元测试代码,在技术上,AI 代码助手采用了两个核心优势技术,让生成的单元测试结果更准确。
应用上采用 AST 语法树解析技术,让 AI 更理解项目工程,生成结果更符合业务逻辑
在单元测试过程中,最小可测试的工作单元一般为 函数、方法、类、接口等,工作单元的结果包含:返回值、系统状态更改、调用第三方服务。一般情况下,工作单元的结果为以上三种的一种,或者三种的组合。
在实际的项目中,我们的测试单元很可能存在对外部的依赖,而单元测试的独立性原则要求测试用例应该要独立运行的,即我们只需要保证被测试的单元内部逻辑正确即可,不需要真正依赖外部资源,因此我们最重要的事情是识别依赖。
AI 代码助手,通过 AST 语法树解析技术,在生成单元测试的时候,不单单只将单元测试的功能代码给到模型,还会结合 AST 获取功能代码背后的依赖代码,比如文件I/O操作、HTTP接口调用、RPC调用、对数据库或者消息队列等资源的请求代码。将这些依赖代码给到大模型,可以有效理解软件工程的业务逻辑,也可以结合这些依赖代码,在生成单元测试的时候,自动生成准确有效的Mock、stub、fake 等数据。
模型上针对各类主流的单元测试框架进行了增量训练,可自动识别各类主流单测框架
在单元测试场景下,各主流的编码语言都有对应的单元测试框架,不通框架的单元测试代码逻辑有一定区别。 AI 代码助手,支持根据现有单元测试代码自动识别对应的单元测试框架,在生成的时候可以遵循当前项目已有框架生成。同时, AI 代码助手也针对业界主流的单元测试框架进行了增量训练,优化单元测试的生成效果。
五、未来展望
虽然 AI 代码助手目前可以做到针对单个方法/单个文件的单元测试生成,但还是依赖研发人员的对于此功能的主动使用与探索,并且当前 AI 生成代码还存在一些局限性:
只能做到片段式生成,多个代码文件之间需要联动测试的场景,覆盖度一般
生成结果比较比较随机,多次运行的结果可能差异较大
AI 代码助手即将推出批量单元测试执行能力,旨在帮助开发者以最小成本有效提升单测覆盖率。
项目级别的批量单元测试生成,需要强大的基础模型能力,包括:支持丰富的上下文、模型对于代码工程的理解和推理能力、模型的反思能力等;同时对于业务代码本身需要非常标准规范,注释清晰、逻辑明确。代码解耦较好,代码可测性较强。单元测试最终仍需要技术人员进行确认,研发需要理解模型生成的单测结果,并配合模型不断提升单测效率。
六、总结
总结来说,AI代码助手结合腾讯混元大模型的功能,已经在单元测试领域展现出了显著的价值,它不仅提高了开发效率,还帮助确保了软件的质量和稳定性。随着技术的不断进步,AI 在单元测试中的应用将更加广泛和深入,为软件开发带来更多的便利和创新。