在测试期间,我一直在测试一段接收数字列表的代码,并且应该返回列表中不存在的新随机密钥。 有效范围是介于1到1,000,000之间的任何数字 - 这使得在测试中难以暴力破解。
测试此方法的最佳方法是什么?我考虑过用较小的范围(比方说100)进行测试,但考虑到基本的随机化算法,一旦列表接近其最大尺寸,这也会花费太长时间。
答案 0 :(得分:3)
您可以在1-1000000中选择一个随机数,然后线性向前搜索,直到找到一个空闲位置(最终在1000000无法匹配后重叠为1)。 这样,数字的分布不是线性的(当集合大部分为空时,但随后变得越来越糟),但这比每次检查一个随机值要快得多(我希望随机性的偏差对于测试而言并不重要)但OTOH你确定你只需要一次对random()的调用,并且它不能用超过1000000次的检查来找到空的空间。
答案 1 :(得分:1)
我想知道你是否可以分两部分打破你的功能(或测试或两者):
调用方法的事实必须返回一个不在列表中的值。
public class RandomGenerator {
public int getValue() {
return `<random implementation>`;
}
}
public class RandomNewGenerator {
RandomGenerator randomGenerator = new RandomGenerator();
public int getValue(List<Integer> ints) {
// note that a Set<Integer> would be more efficient
while(true) {
Integer i = randomGenerator.getValue();
if (!ints.contains(i)) {
return i;
}
}
}
}
在实际代码中,我会更改内容(使用接口,使用Spring注入等等)...
这样,在您对RandomNewGenerator的测试中,您可以使用返回已知值系列的实现覆盖RandomGenerator。 然后,您可以在不面对任何随机的情况下测试您的RandomNewGenerator。
我相信这确实是JUnit测试的精神,使它们变得简单,快速,甚至更好:可重复!最后的质量实际上允许您的测试用作回归测试,这非常方便。
public class RandomNewGeneratorTest {
// do the set up
private List<Integer> empties = ...//
private List<Integer> basics = ... // set up to include 1,2, 7, 8
private class Random extends RandomNewGenerator {
int current;
Random(int initial) {
current = initial;
}
public int getValue() {
return current++; // incremental values for test, not random
}
}
public void testEmpty() {
RandomNewGenerator randomNewGenerator = new RandomNewGenerator();
// do a simple injection of dependency
randomNewGenerator.randomGenerator = new Random(1);
// random starts at 1, builds up
assertEquals(1, randomNewGenerator.getValue(empties);
assertEquals(2, randomNewGenerator.getValue(empties);
assertEquals(3, randomNewGenerator.getValue(empties);
assertEquals(4, randomNewGenerator.getValue(empties);
}
public void testBasic() {
RandomNewGenerator randomNewGenerator = new RandomNewGenerator();
// do a simple injection of dependency
randomNewGenerator.randomGenerator = new Random(5);
// random starts at 5, builds up
// I expect 7, 8 to be skipped
assertEquals(5, randomNewGenerator.getValue(basics);
assertEquals(6, randomNewGenerator.getValue(basics);
assertEquals(9, randomNewGenerator.getValue(basics);
}
}
请注意,此代码仅为原始样本。你可以以任何你需要的方式改变它,例如通过向随机生成器提供它必须返回的一系列值。例如,您可以测试连续两次返回相同的数字。
答案 2 :(得分:0)
长什么意思?将值与列表中的1.000.000值进行比较应该只需要几毫秒。我看不到其他解决方案,然后比较所有值除了列表已排序,你可以缩小范围进行检查。您当然可以对列表进行排序,然后执行不超过20步的二进制搜索,但排序将比线性搜索更加昂贵。
我刚刚在一台非常慢的电脑上进行了测试,在C#中扫描一个给定数字的1.000.000数字的列表需要大约20毫秒。使用数组需要14毫秒。那不够快吗?二进制搜索在0.3微秒内完成了这项工作。最后使用哈希集查找只花了大约90纳秒。
如果你必须编写算法,我建议做一个简单的技巧。带到列表 - 一个带有分配的编号,一个带有未分配的编号,从1到1.000.000的所有编号开始。如果您需要一个新数字,只需获得一个介于零(包含)和未分配数字列表长度(随机数)之间的随机数,选择此索引处的数字并将其移动到指定的数字列表。完成。
我也测试了这种方法,使用未分配的数字的哈希集来加速删除和分配的列表花了大约460毫秒来从未分配的数字列表中获取所有1.000.000个数字。数字。在给定范围内生成新的唯一随机数仅约460纳秒。您必须使用排序字典来避免随机数生成器和散列算法之间的干扰。
最后你也可以把1到1.000.000之间的数字,但是它们放入一个列表中,将它们拖放一段时间,然后从列表中取出一个接一个。除了洗牌之前的最初时间,这将立即运行。
答案 3 :(得分:0)
可能有效的一种方法是获取初始列表并为1到1,000,000的所有索引i
填充100万个元素向量,如果i
,则填充1,如果为i
,则填充0 s
未被采纳。
计算初始列表的大小,调用此大小j
。
生成随机数0 <= j < s
,j
。循环遍历数组,找到{{1}}元素为0,然后返回该元素。
编辑:仔细检查一下@ lapo的回答 - 我的回答似乎相同,但速度有点慢。
答案 4 :(得分:0)
一旦您选择的数字组开始变得过满,Lapo答案的分布就不是线性的。通过以下修改,您将获得均匀的整数分布:
将一组初始数字保存在一个位数组中,其中位数组中的每个元素都对应于初始数组中的数字。 True表示项目中存在项目,否则为false。
接下来,创建一个1到1,000,000的整数数组。 Shuffle数组。这组数字将是您的新密钥。
按住指向新密钥列表中最后一个索引的指针。如果要生成新密钥,请将指针递增到新密钥中的下一个项目;您可以在恒定时间内测试它是否已在初始设置中选择。如果该项已存在于集合中,则将指针递增到新键中的下一项,否则返回该项并将其在位数组中的状态设置为true。