4. 为数组排序函数编写多场景测试

为数组排序函数编写多场景测试 学习卡片

排序函数的测试对象与边界分析

本节针对一个整数数组升序排序函数 sortAscending(arr) 编写多场景测试。该函数接收一个整数数组,返回一个新的升序数组,不修改原数组。测试的核心不是验证排序算法本身,而是验证函数在四类典型输入下的输出正确性:已排序、逆序、含重复元素、空数组。

先明确函数契约,再据此设计用例:

  • 输入:整数数组,允许空数组、单元素、重复元素、负数。
  • 输出:新数组,元素升序排列,长度与原数组一致。
  • 不变式:原数组不被修改;输出数组元素多重集与输入一致。
sortAscending([3, 1, 2]) → [1, 2, 3] sortAscending([5, 4, 3, 2, 1]) → [1, 2, 3, 4, 5] sortAscending([2, 2, 1, 3, 3]) → [1, 2, 2, 3, 3] sortAscending([]) → []

四类场景的输入输出关系

下图把四类输入与期望输出并排呈现,同时标出每类场景真正要暴露的风险点。已排序输入检验函数是否“多此一举”地打乱顺序;逆序输入检验完整交换路径;重复元素检验相等元素的稳定性与计数;空数组检验边界处理是否抛异常或返回错误结构。

sortAscending 四类场景:输入 → 期望输出 → 风险点 ① 已排序 [1, 2, 3, 4] 输入本身有序 期望输出 [1, 2, 3, 4] 顺序不变 风险点 算法误判“已有序”而跳过 或错误地重排相等元素 ② 逆序 [5, 4, 3, 2, 1] 完全逆序 期望输出 [1, 2, 3, 4, 5] 完全反转 风险点 交换次数最多,易出现 越界或遗漏首尾元素 ③ 重复元素 [2, 2, 1, 3, 3] 含相等值 期望输出 [1, 2, 2, 3, 3] 计数不变 风险点 相等元素被误删或 比较条件写成严格不等 ④ 空数组 [] 无元素 期望输出 [] 空数组返回空数组 风险点 访问 arr[0] 抛异常 或返回 null/undefined

测试实现

使用 Jest 的 describe / it 组织四个场景,每个场景至少包含一个断言。除输出数组外,额外断言原数组未被修改,以覆盖函数契约中的不变式。

// sortAscending.js
function sortAscending(arr) {
  return [...arr].sort((a, b) => a - b);
}
module.exports = sortAscending;

// sortAscending.test.js
const sortAscending = require('./sortAscending');

describe('sortAscending', () => {
  it('已排序数组保持原顺序', () => {
    const input = [1, 2, 3, 4];
    expect(sortAscending(input)).toEqual([1, 2, 3, 4]);
  });

  it('逆序数组被完全反转', () => {
    const input = [5, 4, 3, 2, 1];
    expect(sortAscending(input)).toEqual([1, 2, 3, 4, 5]);
  });

  it('重复元素保留计数且升序排列', () => {
    const input = [2, 2, 1, 3, 3];
    expect(sortAscending(input)).toEqual([1, 2, 2, 3, 3]);
  });

  it('空数组返回空数组', () => {
    expect(sortAscending([])).toEqual([]);
  });

  it('不修改原数组', () => {
    const input = [3, 1, 2];
    const copy = [...input];
    sortAscending(input);
    expect(input).toEqual(copy);
  });
});
断言选择说明:使用 toEqual 而非 toBe,因为排序返回的是新数组,需要逐元素比较值而非引用。若函数被错误实现为原地排序并返回同一引用,toEqual 仍能通过输出断言,但“不修改原数组”用例会失败,从而暴露问题。

运行与结果判读

执行 npx jest sortAscending.test.js,预期 5 个用例全部通过。若某个场景失败,可按以下方式定位:

失败用例典型错误信息排查方向
已排序Expected [1,2,3,4] Received [4,3,2,1]比较函数方向写反,a - b 误写为 b - a
逆序Received 数组长度不符循环边界错误,首尾元素被跳过
重复元素Received [1,2,3] 缺少重复项使用了 Set 去重或比较条件写成 > 而非 >=
空数组TypeError: Cannot read property '0' of undefined未对空数组做前置判断,直接访问索引
不修改原数组Expected [3,1,2] Received [1,2,3]直接对入参调用 sort(),未先复制
注意:空数组用例必须显式编写。很多排序实现依赖 arr.length - 1 作为循环上界,空数组时该值为 -1,若循环条件写成 i <= arr.length - 1 会直接跳过,但若写成 i < arr.length - 1 则可能因类型转换产生意外行为。显式测试空数组是成本最低的边界防护。