随机生成框架






4.60/5 (6投票s)
一个 .NET 泛型框架,用于生成各种类型的随机值。
背景
我想我们都有过这样的经历,觉得需要一个比简陋的 Random
类更复杂的随机生成器。
几年前,当我开始为我的一个项目进行负载测试的数据生成研究时,我迫切地需要一个全面的随机生成框架,但我找不到任何,于是我开始着手编写一些代码。在我们的项目中,数据被加载到一个巨大的 OLTP 数据库中,所有以前的数据生成应用程序都在插入硬编码的值。不用说,这些索引上的统计数据看起来与我们在真实环境中将会拥有的完全不同;因此,负载测试从一开始就存在缺陷。所以,我不仅需要随机数据生成,还需要能够创建或多或少相同数据频率分布的东西,因为如果我使用纯随机数据,索引会在所有值之间几乎均匀分布。
首先,我为主要的原始类型创建了一个随机生成器,然后是一个泛型随机生成器,它可以从列表中选择项,因此可以用于任何类型。然后,我扩展了这种类型以允许自定义的概率分布。我认为结果是一个简单但灵活且可扩展的框架,我希望它能减少你的开发时间,并使你能够自定义编写自己的 RandomGens。这可以用于数据生成,但更重要的是用于使用看起来像真实数据来设置单元测试。
框架
所有随机生成器都继承自 RandomGeneratorBase<T>
类。这个类有一个 System.Random
类型的成员变量,它是所有随机生成的基石。在不深入研究 System.Random
细节的情况下,这个类需要一个种子来启动。如果你提供相同的种子,你将始终获得相同的随机数链,也就是说,它根本不是随机的。Environment.TickCount
是一个有用的种子,但如果你在代码的同一部分创建相似的随机生成器,那么它们很有可能都会创建相似的随机数,因为它们是在相同的 TickCount
时创建的。因此,在 RandomGeneratorBase<T>
中已经实现了一个随机种子生成。
原始随机生成器包括
BooleanRandomGen
IntegerRandomGenerator
DoubleRandomGenerator
StringRandomGenerator
DateRandomGenerator
我还包括了一些特殊的随机生成器
NameRandomGenerator
,它生成名称或单词作为字符串EnumRandomGenerator<T>
ListRandomGenerator<T>
PrioritisedListRandomGenerator<T>
使用这些类非常简单。这段代码片段可能比任何文档都更有用
public static void Main(string[] args)
{
Console.WriteLine("Random generation framework");
Console.WriteLine("===========================");
Console.WriteLine();
// boolean
Console.WriteLine();
Console.WriteLine("Boolean:");
BooleanRandonGen bg = new BooleanRandonGen();
for (int i = 0; i < 5; i++)
Console.WriteLine(bg.GetRandom());
// integer
Console.WriteLine();
Console.WriteLine("Integers between 10 and 100 (exclusive 100):");
IntegerRandomGenerator ig = new IntegerRandomGenerator(10, 100);
for (int i = 0; i < 5; i++)
Console.WriteLine(ig.GetRandom());
// double
Console.WriteLine();
Console.WriteLine("Doubles between 10.0 and 100.0 (exclusive 100.0):");
DoubleRandomGenerator dg = new DoubleRandomGenerator(10, 100);
for (int i = 0; i < 5; i++)
Console.WriteLine(dg.GetRandom());
// date
Console.WriteLine();
Console.WriteLine("Dates between 01/01/1969 and 28/02/2009:");
DateRandomGenerator tg =
new DateRandomGenerator(new DateTime(1969,1,1),
new DateTime(2009,2,28));
for (int i = 0; i < 5; i++)
Console.WriteLine(tg.GetRandom());
// digit up to 10 chars
Console.WriteLine();
Console.WriteLine("String digits up to 10 chars " +
"(including 10 and could be zero length):");
StringRandomGenerator sg1 = new StringRandomGenerator(10,
CharacterType.Digit);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + sg1.GetRandom() + "\"");
// letters between 10 to 50 chars
Console.WriteLine();
Console.WriteLine("Letters between 10 to 50 chars (including 50):");
StringRandomGenerator sg2 = new StringRandomGenerator(10,50,
CharacterType.LowerCase |
CharacterType.UpperCase);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + sg2.GetRandom() + "\"");
// letters between 10 to 20 chars padded
Console.WriteLine();
Console.WriteLine("Letters between 10 to 20 chars padded (including 20):");
StringRandomGenerator sg3 = new StringRandomGenerator(10, 20,
CharacterType.LowerCase | CharacterType.UpperCase, true);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + sg3.GetRandom() + "\"");
// random words
Console.WriteLine();
Console.WriteLine("Random words:");
NameRandomGenerator ng1 = new NameRandomGenerator( NameType.Word);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + ng1.GetRandom() + "\"");
// random male and female forenames
Console.WriteLine();
Console.WriteLine("Random male and female forenames:");
NameRandomGenerator ng2 = new NameRandomGenerator(
NameType.FemaleName | NameType.MaleName);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + ng2.GetRandom() + "\"");
// random enum
Console.WriteLine();
Console.WriteLine("Random enum:");
EnumRandomGenerator<nametype> eg = new EnumRandomGenerator<nametype>();
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + eg.GetRandom() + "\"");
// random from a list
Console.WriteLine();
Console.WriteLine("Random from a list:");
ListRandomGenerator<string> lg1 = new ListRandomGenerator<string>(
new string[] { "John", "George", "Jorge", "Jose", "Jack", "Jimi" });
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + lg1.GetRandom() + "\"");
// unique random from a list
Console.WriteLine();
Console.WriteLine("Unique random from a list:");
ListRandomGenerator<string> lg2 = new ListRandomGenerator<string>(
new string[] { "John", "George", "Jorge",
"Jose", "Jack", "Jimi" }, true, true);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + lg2.GetRandom() + "\"");
// random from a random prioritiased list with max score of 10
Console.WriteLine();
Console.WriteLine("random from a prioritised list " +
"with random scores (max=1000):");
PrioritisedListRandomGenerator<string> pg1 =
new PrioritisedListRandomGenerator<string>(
new string[] { "John", "George", "Jorge",
"Jose", "Jack", "Jimi" },1000);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + pg1.GetRandom() + "\"");
// random from a prioritiased list
Console.WriteLine();
Console.WriteLine("random from a prioritised list with predefined scores:");
Dictionary<string,> namesAndScores = new Dictionary<string,int>();
namesAndScores.Add("Divorced", 1);
namesAndScores.Add("Single", 4);
namesAndScores.Add("Married", 5);
PrioritisedListRandomGenerator<string> pg2 =
new PrioritisedListRandomGenerator<string>(namesAndScores);
for (int i = 0; i < 5; i++)
Console.WriteLine("\"" + pg2.GetRandom() + "\"");
}
好的,这里有几点。
你可能已经注意到了范围可以是排他性或包容性的。System.Random
的整数随机生成在最小值处是包容的,在最大值处是**排他性**的,IntegerRandomGenerator
和 DoubleRandomGenerator
也是如此。也就是说,创建一个最小值为 20、最大值为 50 的 IntegerRandomGenerator
将生成 20 到 49 之间的数字。StringRandomGenerator
的长度参数设计不同,为了方便起见:它是**包容性**的。因为,对于填充长度为 50 的表的 VARCHAR
字段,创建一个最大长度为 50 的 StringRandomGenerator
比 51(以包含长度 50)更自然。
NameRandomGenerator
是 ListRandomGenerator<T>
的一种特殊类型,它从预定义的名称或单词列表中选取名称或单词。如果你需要随机的男性或女性姓名、姓氏或随机单词,它有时比常规的 StringRandomGenerator
效果更好、更自然。用真正的姓名来测试系统肯定比用 StringRandomGenerator
生成的乱码感觉要好。
EnumRandomGenerator<T>
是一个用于返回枚举随机值的实用程序,其中 T
是一个枚举。不幸的是,.NET 泛型语义不允许在泛型类型定义的 where 子句中强制限制 T
为枚举类型。但是,很明显,只有当 T
是枚举时它才有效。
ListRandomGenerator<T>
是一个泛型随机生成器,可以用于从提供的任何类型的列表中随机选择项。如果为“unique”(唯一)参数传递 true,它将从列表中返回唯一值。它通过创建列表的内部副本并删除它返回的项来实现这一点。
PrioritisedListRandomGenerator<T>
是一个特殊的 ListRandomGenerator<T>
,用于从列表中返回具有预定义或随机但对于特定设置恒定的比例的随机项,也就是说,某些值可能被返回的次数更多(带有优先级分数),因此称为 PrioritisedListRandomGenerator
。你可以传递字典中每个项的相对频率/比例(它们是整数,频率越高,选中该项的几率越高),或者让 PrioritisedListRandomGenerator<T>
本身在开始时随机设置频率。后一种方法是通过传递最大比例来工作的,PrioritisedListRandomGenerator<T>
将为每个项分配从 1 到最大的随机比例,然后根据分数初始化其内部列表。PrioritisedListRandomGenerator<T>
对于填充具有不均匀值分布的数据库字段特别有用。例如,如果你的一个字段是“婚姻状况”,在现实世界中,单身和已婚的值的可能性会比丧偶或离婚的可能性更大,但如果你使用 EnumRandomGenerator<T>
随机填充它们,它们的比例将几乎相同。
编写自己的 RandomGen
创建自己的随机类以返回自定义类型的随机实例非常容易。你所需要做的就是继承 RandomGeneratorBase<T>
,将 T
定义为你选择的类型,然后重写 GetRandom
并实现你类型的随机生成。例如,如果你正在创建一个 CustomerRandomGen
,而你的客户有名字、姓氏和客户编号,只需保留 NameRandomGenerator
的两个实例成员用于名字和姓氏,以及一个 IntegerRandomGenerator
实例用于生成随机数,然后创建并返回一个具有随机值的 Customer
实例。
用法和最终说明
关于单元测试的一点说明。我在单元测试中大量使用随机生成,对我来说,这是这个框架的日常用法,而不是偶尔的负载测试和数据生成。我认为纯粹主义者可能会认为测试总是必须产生一致的结果。我也这样认为,但我所生活的世界并非如此,事实上,我更喜欢在我编写的测试中加入一些随机性,这样,如果我的代码因为无法预见的情况而在生产环境的随机条件下失败,这种情况就会在我开发机器上触发,从而为我节省了麻烦和痛苦。