提问人:spacePirate 提问时间:7/28/2023 最后编辑:jonrsharpespacePirate 更新时间:8/14/2023 访问量:100
如何编写测试用例来检查数组中随机创建数组时的数字是否唯一?
How to write a test case to check if numbers in an array are unique when the array is created randomly?
问:
我正在为一个彩票函数编写一个规范,该函数应该创建一个介于 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()
})
彩票函数(带有 if 语句以避免重复):
// 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
}
答:
您需要将生成随机数的函数替换为测试双精度值(也称为 mock)。配置“mock”(实际上是一个测试存根)以返回某个数字序列(它们不会是随机的,您可以决定返回哪些值),然后验证函数生成的随机数列表是否包含您期望它包含的数字,给定测试存根生成的“随机”数字。lottery()
一个简单的例子,使用 Jest:
// I assume that the function `randomNbr()` is exported by file `randomNbr.js`
import { randomNbr } from 'randomNbr';
jest.mock('randomNbr');
it('generates a list of unique values', () => {
randomNbr.mockReturnValueOnce(7);
randomNbr.mockReturnValueOnce(2);
randomNbr.mockReturnValueOnce(1);
randomNbr.mockReturnValueOnce(7);
randomNbr.mockReturnValueOnce(6);
randomNbr.mockReturnValueOnce(3);
randomNbr.mockReturnValueOnce(3);
randomNbr.mockReturnValueOnce(7);
randomNbr.mockReturnValueOnce(2);
randomNbr.mockReturnValueOnce(5);
randomNbr.mockReturnValueOnce(9);
randomNbr.mockReturnValueOnce(2);
randomNbr.mockReturnValueOnce(8);
// ... put here at least the number of unique values that `lottery()` returns
const numbers = lottery();
expect(numbers).toEqual([7, 2, 1, 6, 3, 5, 9]);
});
假设输入是已知的(每次调用返回的值),并且算法需要生成 7 个随机数,并且没有重复,则预期的输出也是已知的。randomNbr()
测试为绿色后,可以重构函数以改进其代码。lottery()
现在它使用 Array.find()
而不是查找列表是否包含某个值。Array.includes()
速度更快,它更好地表达了代码意图。读者无需阅读文档即可理解使用的代码;他们必须阅读文档以了解返回的内容以及返回时间。Array.includes()
Array.includes()
Array.find()
undefined
此外,循环和令人困惑,循环甚至不正确。如果生成重复数字,则生成包含 7 个以上值的列表。如果 ,由于某种原因卡住了,并且总是返回相同的值(就像在上面的测试中发生的那样),则永远不会完成。for
i = -1
for
randomNbr()
lottery()
randomNbr()
lottery()
让我们尝试从需求开始编写代码。该函数必须:lottery()
- 返回 7 个值;
- 这些值必须是随机的;
- 这些值必须不同。
第三个要求告诉我们,有时,它必须生成 7 个以上的随机值。限制为 7 次迭代的循环不正确。让我们生成随机的不同值,直到我们有 7 个。for
// using `const` and arrow functions for anything else than inline callbacks
// is a stupid fashion that has many drawbacks and no benefit
function lottery() {
// start with an empty list
let result = []
do {
// generate a random number
const nb = randomNbr(49);
// check for duplicates
if (result.includes(nb)) {
// generated a duplicate, ignore it, restart the loop
continue;
}
// it's good, keep it in the list
result.push(nb)
// stop when the list reaches the desired size
} while (result.length < 7);
return result
}
TDD+随机很难,而且已经很难了很久。
我发现唯一令人满意的一般方法是将问题重构为三个部分
- 一个确定性函数,它接受一个位序列并计算一个值
- 随机位的来源
- 将两者结合在一起的功能
第一个功能是我们可以通过测试来驱动的东西。另外两个部分与更难约束的东西耦合,因此我们通过确保代码“如此简单,显然没有缺陷”来弥补。
对于您的特定问题:只有 (49 选择 7) 个数组满足您的约束。因此,一个按索引“查找”满足示例的约束的函数,结合范围内的随机值源(49 选择 7)可以满足您的需求。
考虑这个问题的一个方便方法是,将我们可能的数组集合想象成一个“压缩顺序”的序列,我们的(随机生成的)索引用于将该特定解决方案从序列中拉出。
现在,您可能不希望 (49 选择 7) 数组在内存中徘徊,因此您真正想要的是一个生成器函数,该函数在给定可接受范围内的索引时生成答案。
好消息是:一旦你研究了这个概念,生成器功能就不太难实现。我从中学到的解释在这里,这里是对同一问题的另一种看法。
我唯一能想到的测试随机行为就是反复运行测试,直到你确信它不会失败。如果我想测试一枚公平的硬币,我必须把它翻转足够多的次数,才能看到它“几乎”是 50% 的正面和 50% 的反面。
同样,如果我想确保这个数组是唯一的,请在每次测试运行中多次运行检查。这应该会增加我们的信心,但不会达到100%。您可以运行检查 999,999,999,它仅在第 1,000,000,000 次失败,但这极不可能。
根据业务用例的不同,这种不太可能的机会可能非常昂贵。比如,阵列的唯一性可能每百万年失败一次,但如果失败,公司将无法恢复或可能面临法律后果等。在这种情况下,我建议对数组的唯一性进行断言。测试以确保在数组不唯一时触发此断言。如果在每次运行中运行 1000 次唯一性测试没有涵盖极端情况,这将为您提供生产安全网。
最后,我建议使用 Set 而不是 Array。它不仅可以保证独特性,还可以更好地传达意图。这里起作用的是集合的长度。
实现应该不太容易出错。我们将随机生成的值添加到集合中,当集合达到所需的大小时,我们就完成了。这是一个更具确定性的解决方案。
评论