发布于 2026-01-06 1 阅读
0

新手指南:JavaScript 测试入门指南

新手指南:JavaScript 测试入门指南

封面图片由弗雷德·耶稣拍摄

最近,我的同事们在编写单元测试时,最常问我的问题之一就是:我应该测试什么?无论使用哪种语言,主要的挑战之一在于如何知道要检查什么,以及如何确保测试覆盖所有可能的错误——这几乎是不可能完成的任务。接下来,我想向你介绍一种编写测试的方法,帮助你在用 JavaScript 编写单元测试时更加自信高效。

值得一提的是,本文的目的并非讨论单元测试如何才能让你的应用程序完全无bug,也不是探讨你应该使用哪种技术/库;这些话题我们以后再谈。不过,我假设你确实想用JavaScript编写单元测试,并且正在寻找编写有效测试的指南。

单元测试的性质

首先,你需要搭建一个支持单元测试的技术栈。从原生 JavaScript 到 React,许多优秀的工具都提供了 API 来简化这个过程,所以我将提供一些有用的链接来帮助你完成这一步,例如JestReact 测试库

之后,社区对单元测试的外观和结构达成了一些共识:

  • 原子性:每个断言都必须有明确的目的,并且只测试函数的一个(带有预期结果的)结果或部分;
  • 独立性:它必须不依赖于任何其他组件,或者在无法做到这一点的情况下,必须对其进行模拟。这里的依赖关系指的是此特定断言中的所有组件都必须正常工作,例如提交表单并发送请求。在本例中,该请求不属于提交测试;
  • 纯粹性:断言不得有任何函数返回值无法处理的副作用;例如:给定特定参数,返回值应该是可预测的;
  • 仅业务逻辑:编写断言,确保方法、规则和函数的主要目的按预期工作,并且不会引起副作用、其他模块的反应,甚至 UI 更改。

三个步骤

我发现描述这个概念的最佳方法是将其分为三个步骤,这将在编写单元测试时起到指导作用:

目的

这听起来可能很显而易见,但编写单元测试最重要的目的是确保实现按预期运行,并防止在开发过程中出现回归问题。换句话说,测试可以让你知道未来的某些更改是否会破坏被测函数的功能。

因此,为了实现这个目标,你需要问问自己这个函数的目的是什么:

  • 它应该起到什么作用?又不应该起到什么作用?
  • 基于“这些”论点,回报是什么?
  • 返回的值是否足够可预测?
  • 在日期实现方面:在不同的时区进行测试,结果如何?它还能正常工作吗?
  • React 组件会渲染什么?事件是否被触发?
  • 此外,在 React 组件中:它是否支持国际化、点击事件、状态更新或任何值得测试的动态变化?
describe('pxToRem - Purpose', () => {
  it('should receive a number and return the value in rem', () => {
    const result = pxToRem(16)

    expect(result).toBe('1rem')
  })

  it('should round the number to keep only one decimals', () => {
    const result = pxToRem(22)

    expect(result).toBe('1.3rem')
  })

  it('should try to convert a string in a number', () => {
    const result = pxToRem('16')

    expect(result).toBe('1rem')
  })

  it('should convert a number to rem with the body font-size argument', () => {
    const input = 16
    const bodyFontSize = 20

    const result = pxToRem(input, bodyFontSize)

    expect(result).toBe('0.8em')
  })
})
Enter fullscreen mode Exit fullscreen mode

在这里,您可以花更多时间编写断言,因为它需要涵盖所有内部条件、分支和实现变体。这将使您更有信心确保您的应用程序能够正常运行。

避免错误

一旦你确定一切运行正常,并返回了预期结果,下一个目标就是尽可能地找出函数的缺陷。重点在于涵盖所有未处理的错误,并创建真实世界的场景。

例如,有时你无法控制参数和上下文,因此了解你的实现将如何运行是很有必要的:

  • 传递错误的参数(例如 undefined、null 或无效日期),或者不传递某些参数(即使是必需的参数);
  • 在不同的上下文中执行,或者执行函数的次数超出预期;
  • 无论是否进行类型检查(如 Typescript 或 Flow),尽量混合所有参数(在某些情况下,我们无法控制数据流);
  • 尽可能模拟真实场景进行测试;任何能抛出错误的方法都是有效的。
describe('pxToRem - Avoiding error', () => {
  it('should return 1rem if the argument is null', () => {
    const result = pxToRem(null)

    expect(result).toBe('1rem')
  })

  it('should return 1rem if the argument is not a number', () => {
    const result = pxToRem(NaN)

    expect(result).toBe('1rem')
  })

  it('should work correctly with the 2nd argument is invalid', () => {
    const result = pxToRem(16, null)

    expect(result).toBe('1rem')
  })
})
Enter fullscreen mode Exit fullscreen mode

当然,在发现所有错误和遗漏之处之后,就该进行修正了!

改进 - 维护

编写单元测试有时可能会引出其他任务。的确,在编写测试的过程中,你可能会意识到你的实现可以改进,甚至可以拆分成更小的部分。但请记住,这些更改和改进会提高代码的可维护性,并且所有代码部分都会受到断言的影响。请思考以下问题:

  • 有没有办法让它更容易维护?
  • 能否将其分成更小的部分?
  • 代码的每个部分都经过测试了吗?
  • 有什么办法让它转得更快吗?
  • 我曾经读到过,如果某件事值得测试,那么它也值得记录下来。我强烈建议你尝试描述你是如何构建它的,更重要的是,为什么要这样构建。未来的你能理解这种实现方式吗?

下一步

我认为单元测试不仅仅是用你喜欢的框架编写断言,而是一个完整的体系。你需要有正确的思维方式,并且愿意重构许多文件才能实现目标。另一方面,你需要一个起点,我想这可以为你指明方向,让你进一步编写更复杂的测试,并找到你最喜欢的断言编写方式。

一旦你熟悉了这里介绍的所有内容,我建议你了解一些方法论,例如TDDBDD;以及一些不同的应用程序测试方法,例如端到端测试和集成测试


原文发表于danilowoz.com


文章来源:https://dev.to/danilowoz/beginner-guide-where-to-start-testing-in-javascript-4886