Logo
Section 1 / 12

单元测试基础

学习单元测试的基础知识。

导入

单元测试基础

学习单元测试的基础知识。

从无工具开始

我们先通过一个纯 JavaScript 的函数单元测试示例来入门。
这里只使用代码,不依赖任何工具。

我们测试的函数是一个简单的 flatMap 实现(参考 MDN 文档)。

我们将设计四个测试用例,确保我们的实现符合标准行为。

测试用例 1

1var arr = [1, 2, 3, 4]; 2 3arr.flatMap((x) => [x, x * 2]); 4// [1, 2, 2, 4, 3, 6, 4, 8]

该代码接收一个数字数组,并对每个元素应用映射函数 x => [x, x * 2]。这会生成一个数组的数组:[[1, 2], [2, 4], [3, 6], [4, 8]],然后将其扁平化为单层数组。

预期结果是 [1, 2, 2, 4, 3, 6, 4, 8]。

测试用例 2

1let arr1 = ["it's Sunny in", '', 'California always']; 2 3arr1.map((x) => x.split(' ')); 4// [['it's','Sunny','in'],[''],['California', 'always']] 5 6arr1.flatMap((x) => x.split(' ')); 7// ['it's','Sunny','in', '', 'California', 'always']

该代码接收一个字符串数组,对每个字符串应用 x.split(" ") 映射函数。这会产生不同长度的子数组组成的数组:[["it's","Sunny","in"],[""],["California", "always"]],然后进行扁平化处理。

预期结果是 ["it's","Sunny","in", "", "California", "always"]。

测试用例 3

1let arr1 = [1, 2, 3, 4]; 2 3arr1.map((x) => [x * 2]); 4// [[2], [4], [6], [8]]

该代码接收一个数字数组,对每个元素应用 x => [x * 2] 映射函数,生成一个包含单个元素的数组的数组:[[2], [4], [6], [8]],然后将其扁平化为单层数组。

预期结果是 [2, 4, 6, 8]。

测试用例 4

1arr1.flatMap((x) => [x * 2]); 2// [2, 4, 6, 8] 3 4// 仅扁平化一层 5arr1.flatMap((x) => [[x * 2]]); 6// [[2], [4], [6], [8]]

该代码接收一个数字数组,对每个元素应用 x => [[x * 2]] 映射函数,生成一个嵌套了多层数组的结构:[[[2]], [[4]], [[6]], [[8]]],然后进行扁平化处理。

预期结果是 [[2], [4], [6], [8]],这验证了 flatMap 只会将一层嵌套的数组展开。

1// 2Array.prototype.flatMap = function(cb) { 3 return []; 4 // 返回 this.map(cb).reduce((acc, n) => acc.concat(n), []); 5} 6 7 8 9// 测试用例 1 10const numbers = [1, 2, 3, 4]; 11 12const actual = numbers.flatMap(x => [x, x * 2]); 13const expected = '1,2,2,4,3,6,4,8'; 14console.log('测试用例 1 通过:', actual.join() === expected); 15 16 17 18// 测试用例 2 19const strings = ["it's Sunny in", "", "California"]; 20 21const actual1 = strings.flatMap(x => x.split(" ")); 22const expected1 = ["it's", "Sunny", "in", "", "California"]; 23console.log('测试用例 2 通过:', actual1.join() === expected1.join()); 24 25 26 27// 测试用例 3 28const numbers2 = [1, 2, 3, 4]; 29const actual2 = numbers2.flatMap(x => [x * 2]); 30const expected2 = [2, 4, 6, 8]; 31console.log('测试用例 3 通过:', actual2.join() === expected2.join()); 32 33// 测试用例 4 34// 仅扁平化一层 35console.log('测试用例 4 通过:', numbers.flatMap(x => [[x * 2]]).join() === [[2], [4], [6], [8]].join());

我们最初这样实现函数:

1Array.prototype.flatMap = function (cb) { 2 return []; 3};

此时运行程序,输出如下:

1测试用例 1 通过: false 2测试用例 2 通过: false 3测试用例 3 通过: false 4测试用例 4 通过: false

这是因为我们尚未真正实现该函数,它只是返回一个空数组。

尝试自行实现该函数,或取消第 4 行注释,将第 3 行替换为该行代码,重新运行。

此时应输出:

1测试用例 1 通过: true 2测试用例 2 通过: true 3测试用例 3 通过: true 4测试用例 4 通过: true

测试方法

  1. 获取函数的实际输出结果(actual)。
  2. 将其与预期结果(expected)进行比较。
  3. 根据是否匹配,判断测试用例是否通过。
...
单元测试提供输入,获取实际结果并与预期结果对比

详细分析

  • 我们从创建一个 flatMap 的兼容性实现开始。
  • 然后编写一个占位实现,始终返回空数组 []。

测试用例 1:

  1. 验证 flatMap 能正确处理包含多个元素的数组。

  2. 准备输入数据 numbers。

  3. 执行函数并获取实际结果:

    1const numbers = [1, 2, 3, 4]; 2const actual = numbers.flatMap(x => [x, x * 2]);
  4. 准备预期结果:

    1const expected = '1,2,2,4,3,6,4,8'; 2console.log('测试用例 1 通过:', actual.join() === expected);
  5. 输出实际结果与预期结果的比对结果。

测试用例 2:

  1. 验证 flatMap 能正确处理字符串类型的数据。

  2. 准备输入数据:const strings = ["it's Sunny in", "", "California"];。

  3. 获取实际结果:

    1const actual1 = strings.flatMap(x => x.split(' '));
  4. 准备预期结果:

    1const expected1 = ["it's", "Sunny", "in", "", "California"];
  5. 比较 actual 和 expected 并输出结果:

    1console.log('测试用例 2 通过:', actual1.join() === expected1.join());

测试用例 3:

此用例与测试用例 1 非常相似,唯一的区别是回调函数生成的是长度为 1 的数组。

测试用例 4:

  1. 验证 flatMap 能正确处理深层嵌套的数组,并且仅展开一层。

  2. 在一行中完成实际结果与预期结果的比较:

    1console.log('测试用例 4 通过:', numbers.flatMap(x => [[x * 2]]).join() === [[2], [4], [6], [8]].join());

其中,actual 是 numbers.flatMap(x => [[x * 2]]),复用了第一个例子中的 numbers;expected 是 [[2], [4], [6], [8]].join()。

我们已经完成测试了吗?

这些测试确保了四个具体场景能够正常工作。虽然还有许多其他可能的使用场景,但我们无需全部覆盖。

只要保证核心使用场景有充分的测试覆盖即可。如果后续发现代码中存在缺陷,我们可以新增针对性的测试用例来捕获该问题。因此,测试本身永远不会真正“完成”。

这就是测试的本质:它们随着代码库一起持续演进和变化!

概述

使用 Jasmine 运行单元测试