目录

俗话说,“40% 的时间写代码,60% 的时间 Debug。”经验表明,调试才是开发过程中真正的时间黑洞。在 AI 辅助编程日益普及的背景下,这一问题更加突出。AI 能加速代码生成,但处理复杂任务时难免出错,而这些错误更隐蔽且难调试。因此,我们比以往更需要依靠单测这套“原子级正确性保障”体系,确保代码质量和功能可靠性。

单元测试的首要作用是提升开发体验——**减少开发过程中的打断,缩短调试时间,**从而自然地提高项目的整体质量。

那么,单元测试为什么能够减少打断并缩短开发时间呢?关键在于它提供了更短的开发 - 验证循环。

1、提高开发体验的核心:更短的反馈链路

单测能够提升开发体验的核心在于它提供了更短的开发 - 验证的反馈循环。

在传统开发流程中,从修改代码到验证结果通常需要经过以下步骤:

zbuhou

尤其是在 UI 开发中,随着热更新技术的广泛应用,步骤 2 和 3 的耗时大大缩短,也让开发者更倾向于依赖上述验证流程。

然而,即便如此,这个过程仍可能耗费几分钟甚至更长时间,因为我们需要依赖整个应用的最终表现来验证某个函数的正确性。

而通过单元测试,这一反馈循环可以缩短至几秒钟:

图片

这种即时反馈不仅大幅节省时间,更关键的是,它有效地保持了我们的思维连贯性(减少打断):无需在冗长的验证流程中分散注意力,而是能将精力集中在问题本身。

然而,即便即时反馈提升了开发体验,开发流程中更大的挑战往往出现在问题被发现之后——也就是 Debug 阶段。

2、缩短开发时间的核心:“原子正确性”

单测的一个重要作用是让我们确信:**这个函数肯定没问题!**我将这种能力称为“原子正确性”。

在 Debug 时,我们希望问题的定位范围越小越好。

如果没有单测,最糟糕的情况是整个应用都可能成为 Debug 范围;

而在有单测的情况下,Debug 的重点就只需放在**函数之间的组合逻辑****,**因为单测为我们提供了“原子正确性”的保障。

图片

图片

单测通过提供“原子正确性”,显著缩小了调试范围:由语句级提升到函数级。

每个通过单测验证的函数(绿色节点)都成为可靠的构建块,我们只需关注这些构建块之间的组合是否正确。

3、AI 下单测的意义:分治

如果你研究过 prompt,就会发现一个高频出现的 magic word:step by step。

AI 在处理长任务时更容易出错,因此我们需要将长任务拆解为小任务(或者让 AI 自动拆分),并按照以下流程执行:

小任务 -> 检查 -> 小任务 -> 检查。

在各种 copilot 的辅助下,开发者往往不需要亲自写出函数的每一行代码,而是通过注释或指令完成大部分工作。

然而,正因为代码不是完全由我们编写,一旦出错,Debug 会变得更加困难。

在这种情况下,快速失败(fast fail)尤为重要。明智的做法是让 AI 自我检测:自动生成覆盖各种边界情况的单测。

通过一连串由“原子正确性”保障的小任务,才能显著提升长任务的整体正确性。

图片

图片

图片

基于 AI 构建工具应用,面临的核心难题是:**如何在非确定性模型上构建表现稳定的工具。**其解决策略与此相同——将复杂任务拆解为易控的小任务,并为每一步提供可靠的验证机制。

4、为什么依然不写单测:单测的冷启动

很多情况下,开发者并非不愿意写单测,而是因为想到需要先搭建一系列环境和配置,就直接放弃了。

我将这种现象称为单测的“冷启动”问题。

在实际项目中,**单测更常见于服务端开发中,**主要原因有以下两点:

  • **运行整个应用验证的成本更高:**服务端通常依赖外部资源,如 RPC 调用、数据库、缓存等。尤其在微服务架构下,完整、可重复地运行整个应用进行验证往往并不现实。

  • **语言内置测试工具:**大多数服务端语言自带轻量级测试框架,开发者几乎无需额外配置,便能快速上手,避免了冷启动问题。

图片

只有彻底消除冷启动,才能让编写单测变得轻而易举、自然而然。

下面,我将分享一些具体的实践经验,帮助消除前端开发中的“冷启动”问题,让单测成为随手可用的开发利器。

5、实践分享:拿来即用的干货

5.1 怎么写单测可以获得最短反馈路径

  1. 紧接着被测试函数写测试;

  2. 开箱即用的单文件(ts)运行测试;

紧邻被测试函数编写测试

将单测直接写在靠近函数实现的位置,不仅可以节省上下文切换时间,还能让单测发挥另一层作用:函数的“注释”。

例如,在 Python 中,可以直接在函数注释中编写测试。而在 TypeScript 中,虽然没有完全类似的机制,但可以通过以下方式在当前文件中快速验证:

if (require.main === module) {
  (() => {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const pbStr = fs.readFileSync('test/proto/tolstoyUserProto.proto', 'utf-8')
    try {
      const result = pb2mockData({
        pbStr,
        serviceName: 'Config',
        methodName: 'FormatLabels',
      })
      console.log('result', result)
    } catch (error) {
      console.log('e', error)
    }
  })()
}

这种方式能够让你在完成函数实现后,随手写一个测试,使用命令 npx ts-node src/… 即可快速验证。

只需关注测试本身,无需关注运行环境和配置

许多语言提供内置测试框架,例如 Go:


calculator.go 
// 只需要遵循约定创建
calculator_test.go

在这种约定下,calculator_test.go 自动成为 JS 中 if (require.main === module) 的对应实现:

  • 测试文件与源码文件共享上下文,测试代码与实现逻辑紧密结合;

  • 无需任何额外配置,直接运行 go test 即可验证代码。

5.2 TypeScript 中推荐的测试框架:Vitest

Vitest 是一个适合 TypeScript 项目的测试框架,提供了即开即用的体验,尤其是在“单测冷启动”问题上优势明显:

1、支持直接在源码文件写测试:In-Source Testing(https://vitest.dev/guide/in-source.html)

// src/index.ts

// the implementation
export function add(...args: number[]) {
  return args.reduce((a, b) => a + b, 0)
}

// in-source test suites
if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest
  it('add', () => {
    expect(add()).toBe(0)
    expect(add(1)).toBe(1)
    expect(add(1, 2, 3)).toBe(6)
  })
}

如果测试用例较多,也可以像 Go 那样,在源码文件旁边创建测试文件,例如:index.test.ts,比如:

图片

2、TS 开箱即用,无需额外配置

与 Jest 或 Mocha 等框架相比,Vitest 避免了复杂的配置过程,不再需要为 TypeScript 测试环境单独配置编译或运行时环境。也就是之前提到的单测冷启动。

使用 Vitest,你只需要:

npx vitest xxx.test.ts

即可轻松运行测试,完全消除了“单测冷启动”的障碍。

6、总结:双赢的策略

通过解决单测的“冷启动”问题并缩短反馈链路,我们可以从根本上降低编写测试的阻力,将 写测试从短期与长期的权衡,转变为真正的双赢策略:

  • **短期:**for 开发,用更少的时间做更多的事情;

  • **长期:**for 项目,确保质量的稳定性,避免劣化。

当编写单测不再是一种负担,而是一种自发行为时,项目迭代将进入正向循环:持续的效率和质量提升;

特别是在 AI 辅助编程的背景下,迭代速度大幅加快,正反馈的作用更为突出。通过单测保障每一步的正确性,开发者不仅能够驾驭复杂任务,还能进一步释放生产力,实现真正的快速迭代和高质量交付。

-End-

原创作者|付志远


本文收藏来自互联网,仅用于学习研究,著作权归原作者所有,如有侵权请联系删除,阅读原文

9ong@TsingChan markdown 2025

部分引用格式为收藏注解,比如本句就是注解,非作者原文。