如何编写可靠测试用例验证随机彩票数组的元素唯一性?
如何确保彩票函数的去重测试不偶然通过?
我正在编写一个彩票函数的规格说明,要求生成包含7个1到49之间随机数的数组,数组中的数字必须唯一,像[1, 2, 3, 4, 5, 6, 6]这种存在重复值的结果是无效的。
现在我有个测试用例用于检查数组数字是否唯一,但它成了“不稳定测试”——可能随机通过或失败。我目前通过多次运行测试用例来“解决”这个问题,但想找到更优雅的方案,需要一种可靠的方式验证lottery()函数能持续避免重复值。
现有测试代码
it('TC05 - lottery() each array item must be a unique number', () => { // function to check if an array has duplicates const hasDuplicates = arr => { return (new Set(arr)).size !== arr.length; } // run check for duplicates x times const REPETITIONS = 32 let seriesOfTests = [] for (let i = 0; i < REPETITIONS; i++) { if (hasDuplicates(lottery())) { seriesOfTests.push(true) } else { seriesOfTests.push(false) } } // check series to ensure each single test passed let seriesSummery = seriesOfTests.includes(true) // test case expect(seriesSummery).must.be.false() })
现有彩票函数代码
// lottery returns an array consists of 7 random numbers between 1 and 49 const lottery = () => { let result = [] for (let i = 0; i < 7; i++) { const newNbr = randomNbr(49) if (result.find(el => el === newNbr) === undefined) { result.push(newNbr) } else { i -= 1 } } return result }
更优雅的解决方案
1. 模拟随机数生成,强制触发重复场景
直接测试随机逻辑容易出现不稳定问题,更可靠的方式是mock掉randomNbr函数,让它返回固定的重复序列,强制彩票函数遇到重复值,验证其是否能正确处理重复情况。
以Mocha+Sinon测试框架为例:
it('TC05 - lottery() must handle duplicate numbers correctly', () => { // 模拟randomNbr,先返回重复值,再返回新值 const mockRandom = sinon.stub() .onCall(0).returns(5) .onCall(1).returns(5) // 生成重复值 .onCall(2).returns(10) .onCall(3).returns(15) .onCall(4).returns(20) .onCall(5).returns(25) .onCall(6).returns(30) .onCall(7).returns(35); // 第7次调用得到不重复的第7个值 // 替换原randomNbr为mock函数 sinon.replace(global, 'randomNbr', mockRandom); const result = lottery(); // 验证结果长度为7,且包含所有预期唯一值 expect(result.length).to.equal(7); expect([5,10,15,20,25,30,35].every(num => result.includes(num))).to.be.true; // 恢复原函数 sinon.restore(); })
这种方式完全可控,不会出现随机波动,能100%验证函数的去重逻辑是否正确。
2. 简化现有重复测试的写法
如果不想使用mock,也可以优化现有多次运行的代码,让逻辑更直观:
it('TC05 - lottery() each array item must be a unique number', () => { const hasDuplicates = arr => new Set(arr).size !== arr.length; const REPETITIONS = 32; // 遍历测试,一旦发现重复立即断言失败 for (let i = 0; i < REPETITIONS; i++) { const nums = lottery(); expect(hasDuplicates(nums)).to.be.false; } })
无需收集结果再统一判断,只要某次出现重复,测试会立刻失败,逻辑更简洁。
3. 优化函数实现(可选)
当前彩票函数通过生成随机数后检查重复、重复则重试的逻辑没问题,但可以优化实现方式,从根源避免重复值的产生:比如先生成1-49的完整数组,再用洗牌算法打乱顺序,最后取前7个值,这样天然保证数值唯一。
优化后的函数代码:
const lottery = () => { // 生成1到49的数组 const nums = Array.from({length:49}, (_,i) => i+1); // Fisher-Yates洗牌算法 for (let i = nums.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [nums[i], nums[j]] = [nums[j], nums[i]]; } // 取前7个值 return nums.slice(0,7); }
这种实现下,只要洗牌逻辑正确,就不会出现重复值,测试只需验证数组长度、数值范围和唯一性各一次即可,无需多次运行。
内容的提问来源于stack exchange,提问作者spacePirate
相关产品推荐
相关产品推荐

