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([]) → []
四类场景的输入输出关系
下图把四类输入与期望输出并排呈现,同时标出每类场景真正要暴露的风险点。已排序输入检验函数是否“多此一举”地打乱顺序;逆序输入检验完整交换路径;重复元素检验相等元素的稳定性与计数;空数组检验边界处理是否抛异常或返回错误结构。
测试实现
使用 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 则可能因类型转换产生意外行为。显式测试空数组是成本最低的边界防护。