新手指南:JavaScript 测试入门指南
最近,我的同事们在编写单元测试时,最常问我的问题之一就是:我应该测试什么?无论使用哪种语言,主要的挑战之一在于如何知道要检查什么,以及如何确保测试覆盖所有可能的错误——这几乎是不可能完成的任务。接下来,我想向你介绍一种编写测试的方法,帮助你在用 JavaScript 编写单元测试时更加自信高效。
值得一提的是,本文的目的并非讨论单元测试如何才能让你的应用程序完全无bug,也不是探讨你应该使用哪种技术/库;这些话题我们以后再谈。不过,我假设你确实想用JavaScript编写单元测试,并且正在寻找编写有效测试的指南。
单元测试的性质
首先,你需要搭建一个支持单元测试的技术栈。从原生 JavaScript 到 React,许多优秀的工具都提供了 API 来简化这个过程,所以我将提供一些有用的链接来帮助你完成这一步,例如Jest和React 测试库。
之后,社区对单元测试的外观和结构达成了一些共识:
- 原子性:每个断言都必须有明确的目的,并且只测试函数的一个(带有预期结果的)结果或部分;
- 独立性:它必须不依赖于任何其他组件,或者在无法做到这一点的情况下,必须对其进行模拟。这里的依赖关系指的是此特定断言中的所有组件都必须正常工作,例如提交表单并发送请求。在本例中,该请求不属于提交测试;
- 纯粹性:断言不得有任何函数返回值无法处理的副作用;例如:给定特定参数,返回值应该是可预测的;
- 仅业务逻辑:编写断言,确保方法、规则和函数的主要目的按预期工作,并且不会引起副作用、其他模块的反应,甚至 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')
})
})
在这里,您可以花更多时间编写断言,因为它需要涵盖所有内部条件、分支和实现变体。这将使您更有信心确保您的应用程序能够正常运行。
避免错误
一旦你确定一切运行正常,并返回了预期结果,下一个目标就是尽可能地找出函数的缺陷。重点在于涵盖所有未处理的错误,并创建真实世界的场景。
例如,有时你无法控制参数和上下文,因此了解你的实现将如何运行是很有必要的:
- 传递错误的参数(例如 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')
})
})
当然,在发现所有错误和遗漏之处之后,就该进行修正了!
改进 - 维护
编写单元测试有时可能会引出其他任务。的确,在编写测试的过程中,你可能会意识到你的实现可以改进,甚至可以拆分成更小的部分。但请记住,这些更改和改进会提高代码的可维护性,并且所有代码部分都会受到断言的影响。请思考以下问题:
- 有没有办法让它更容易维护?
- 能否将其分成更小的部分?
- 代码的每个部分都经过测试了吗?
- 有什么办法让它转得更快吗?
- 我曾经读到过,如果某件事值得测试,那么它也值得记录下来。我强烈建议你尝试描述你是如何构建它的,更重要的是,为什么要这样构建。未来的你能理解这种实现方式吗?
下一步
我认为单元测试不仅仅是用你喜欢的框架编写断言,而是一个完整的体系。你需要有正确的思维方式,并且愿意重构许多文件才能实现目标。另一方面,你需要一个起点,我想这可以为你指明方向,让你进一步编写更复杂的测试,并找到你最喜欢的断言编写方式。
一旦你熟悉了这里介绍的所有内容,我建议你了解一些方法论,例如TDD和BDD;以及一些不同的应用程序测试方法,例如端到端测试和集成测试。
原文发表于danilowoz.com
文章来源:https://dev.to/danilowoz/beginner-guide-where-to-start-testing-in-javascript-4886