TestingUtils: Randomization
$ dotnet add package Jds.TestingUtils.RandomizationThis collection of randomization test utilities supports creating test arrangements.
See API Documentation.
All examples below use the thread-safe, static
Jds.TestingUtils.Randomization.Randomizer.Sharedinstance ofIRandomizationSource, which generates random values usingSystem.Random.Shared. ThisIRandomizationSourceis advised for most tests.If a different algorithm is needed, e.g., a cryptographically strong random number generator is required, consider creating a . It uses provided delegates to supply values when requested.
Jds.TestingUtils.Randomization.ArrangedRandomizationSourceFunc<IEnumerable<>>If a specific set of random-seeming data is needed, consider creating a Jds.TestingUtils.Randomization.DeterministicRandomizationSource. It uses provided IEnumerable<> sources to supply values when requested.
Jds.TestingUtils.Randomization NuGet Package and Add usingAdd Jds.TestingUtils.Randomization NuGet package to the test project.
Add the extensions to your test files with the following using statement:
using Jds.TestingUtils.Randomization;All examples below assume the following additional using statements:
using System;
using System.Collections.Generic;
using System.Linq;IRandomizationSource.Boolean()
bool.IRandomizationSource.Byte()
byte, greater than or equal to byte.MinValue, and less than byte.MaxValue.IRandomizationSource.ByteInRange(byte minInclusive, byte maxExclusive)
byte, greater than or equal to minInclusive, and less than maxExclusive.IRandomizationSource.Double()
double, using IRandomizationSource.NextDouble. The value should be greater than or equal to 0.0, and less than 1.0.IRandomizationSource.Float()
float, using IRandomizationSource.NextFloat. The value should be greater than or equal to 0.0, and less than 1.0.IRandomizationSource.Int()
int, using IRandomizationSource.NextIntInRange. The value should be greater than or equal to int.MinValue, and less than int.MaxValue.IRandomizationSource.IntInRange(int minInclusive, int maxExclusive)
int, using IRandomizationSource.NextIntInRange. The value should be greater than or equal to minInclusive, and less than maxExclusive.IRandomizationSource.IntNegative()
int, using IRandomizationSource.NextIntInRange. The value should be greater than or equal to int.MinValue, and less than 0.IRandomizationSource.IntPositive()
int, using IRandomizationSource.NextIntInRange. The value should be greater than or equal to 0, and less than int.MaxValue.IRandomizationSource.Long()
long, using IRandomizationSource.NextLongInRange. The value should be greater than or equal to long.MinValue, and less than long.MaxValue.IRandomizationSource.LongInRange(long minInclusive, long maxExclusive)
long, using IRandomizationSource.NextLongInRange. The value should be greater than or equal to minInclusive, and less than maxExclusive.IRandomizationSource.LongNegative()
long, using IRandomizationSource.NextLongInRange. The value should be greater than or equal to long.MinValue, and less than 0.IRandomizationSource.LongPositive()
long, using IRandomizationSource.NextLongInRange. The value should be greater than or equal to 0, and less than long.MaxValue.IRandomizationSource.Enumerable<T>(Func<T> factory, int inclusiveMinCount, int exclusiveMaxCount)IRandomizationSource.Enumerable<T>(Func<int, T> factory, int inclusiveMinCount, int exclusiveMaxCount)
IEnumerable<T> of a randomly-generated length using values provided by a factory method.Randomizer.Shared.Enumerable(Guid.NewGuid, 3, 7) - which would generate a sequence of 3 to 6 Guid values.IRandomizationSource.RandomEnumValue<TEnum>()
enum of type specified (TEnum).Randomizer.Shared.RandomEnumValue<System.Net.HttpStatusCode>()IRandomizationSource.RandomListItem<T>(IReadOnlyList<T> items)IReadOnlyList<T>.GetRandomItem<T>()
items.Randomizer.Shared.RandomListItem(System.Linq.Enumerable.Range(1, 20).ToArray())IRandomizationSource.RandomListItem<T>(IReadOnlyList<(T Item, double Weight)> weightedItems)IRandomizationSource.RandomListItem<T>(IReadOnlyList<(T Item, int Weight)> weightedItems)IReadOnlyList<(T Key, double Weight)>.GetWeightedRandomItem<T>()IReadOnlyList<(T Key, int Weight)>.GetWeightedRandomItem<T>()
.Item from provided weightedItems; item selection is weighted based upon relative .Weight.Randomizer.Shared.WeightedRandomListItem(
new[] { ("Sure", 1000), ("Likely", 500), ("Possible", 200), ("Unlikely", 50), ("Rare", 5), ("Apocryphal", 1) }
);IRandomizationSource.GetWeightedRandomKey<T>(IReadOnlyDictionary<T, double> weightedKeys)IRandomizationSource.GetWeightedRandomKey<T>(IReadOnlyDictionary<T, int> weightedKeys)IReadOnlyDictionary<T, double>.GetWeightedRandomKey<T>()IReadOnlyDictionary<T, int>.GetWeightedRandomKey<T>()
.Key from provided weightedItems; item selection is weighted based upon relative .Value.Randomizer.Shared.WeightedRandomKey(new Dictionary<string, double>
{
{ "North", 0.4 }, { "East", 0.1 }, { "West", 0.1 }, { "South", 0.4 }
});IRandomizationSource.RandomString(int length, IReadOnlyList<char> chars)
string of length characters, using provided chars. Random selections from chars are concatenated until reaching length characters.IRandomizationSource.RandomString(int length, IReadOnlyList<string> strings)
string of length characters, using provided strings. Random selections from strings are concatenated until reaching length characters. The result is truncated to length characters.IRandomizationSource.RandomStringLatin(int length, bool uppercase = false, bool alphanumeric = false)
string of length characters using ASCII Latin characters. Uses a - z by default. If uppercase, uses A - Z. If alphanumeric, also includes 0 - 9 with either casing.IRandomizationSource.MailAddress()IRandomizationSource.MailAddress(int length)
System.Net.Mail.MailAddress. The generated System.Net.Mail.MailAddress.User will be a dot-atom form of local-part (see RFC-2822 section 3.4.1). The generated System.Net.Mail.MailAddress.Host will be a domain (see RFC-1035 section 2.3.1).IRandomizationSource.MailAddressAddrSpec(int length)IRandomizationSource.MailAddressAddrSpec((int LocalPartLength, int DomainLength) componentLengths)
string according to addr-spec (see RFC-2822 section 3.4.1). The generated local-part will be of dot-atom form (see RFC-2822 section 3.4.1). The generated domain will be of dot-atom form (see RFC-2822 section 3.4.1), and its value will be generated as a RFC-1035 section 2.3.1 domain.IRandomizationSource.DomainName(int length)IRandomizationSource.DomainName(IReadOnlyList<int> domainLabelLengths)
string according to domain (see RFC-1035 section 2.3.1).IRandomizationSource.RandomUrl(int hostLength, int pathLength = 0, int queryLength = 0, int fragmentLength = 0, string scheme = "https", int? port = null)
string URL according to RFC-3986 URI syntax. The host segment is generated using IRandomizationSource.DomainName(int length).IRandomizationSource.CreateMarkovGenerator(IReadOnlyCollection<IReadOnlyList<T>> sources, int chainLength = 1) where T : notnull, IEquatable<T>
Func<int, IReadOnlyList<T>> which accepts an int maxLength and uses a Markov Chain model, derived from sources, to generate sequences of T of up to length maxLength.
chainLength determines how many T are grouped to determine Markov Chain probability.IRandomizationSource.CreateMarkovGenerator(IEnumerable<string> sources, int chainLength = 1)
Func<int, string> which accepts an int maxLength and uses a Markov Chain model, derived from sources, to generate strings of up to length maxLength.
chainLength determines how many characters in each input string are grouped to determine Markov Chain probability.var exampleFruitNames = new[]
{
"apple", "apricot", "avocado", "banana", "blackberry", "blackcurrant", "blueberry", "boysenberry", "cantaloupe",
"caper", "cherry", "cranberry", "elderberry", "fig", "gooseberry", "grape", "grapefruit", "guava", "jujube",
"kiwi", "kumquat", "lemon", "lime", "lychee", "mango", "mulberry", "olive", "orange", "papaya", "pear",
"persimmon", "pineapple", "plantain", "plum", "pomegranate", "raspberry", "starfruit", "strawberry", "tangerine",
"watermelon"
};
Func<int, string> fruitGenerator = Randomizer.Shared.CreateMarkovGenerator(sources: exampleFruitNames, chainLength: 2);
string generatedFruit = fruitGenerator(maxLength: 12);
string possiblyLongerGeneratedFruit = fruitGenerator(maxLength: 20);
string likelyShorterGeneratedFruit = fruitGenerator(maxLength: 6);IRandomizationSource.GenerateRandomMarkov<T>(IReadOnlyCollection<IReadOnlyList<T>> sources, int? maxLength = null, int chainLength = 1)
IReadOnlyList<T> based upon a Markov Chain model, derived from sources.
chainLength determines how many items in each input IReadOnlyList<T> are grouped to determine Markov Chain probability.IRandomizationSource.CreateMarkovGenerator() (above), instead of this function, unless only a single value is needed.
IRandomizationSource.GenerateRandomMarkov(IEnumerable<string> sources, int? maxLength = null, int chainLength = 1)
string based upon a Markov Chain model, derived from sources..
chainLength determines how many characters in each input string are grouped to determine Markov Chain probability.IRandomizationSource.CreateMarkovGenerator() (above), instead of this function, unless only a single value is needed.
var exampleFruitNames = new[]
{
"apple", "apricot", "avocado", "banana", "blackberry", "blackcurrant", "blueberry", "boysenberry", "cantaloupe",
"caper", "cherry", "cranberry", "elderberry", "fig", "gooseberry", "grape", "grapefruit", "guava", "jujube",
"kiwi", "kumquat", "lemon", "lime", "lychee", "mango", "mulberry", "olive", "orange", "papaya", "pear",
"persimmon", "pineapple", "plantain", "plum", "pomegranate", "raspberry", "starfruit", "strawberry", "tangerine",
"watermelon"
};
string similarGeneratedFruit = Randomizer.Shared.GenerateRandomMarkov(sources: exampleFruitNames, maxLength: 12, chainLength: 2);
string slightlySimilarGeneratedFruit = Randomizer.Shared.GenerateRandomMarkov(sources: exampleFruitNames, maxLength: 20, chainLength: 1);
string verySimilarGeneratedFruit = Randomizer.Shared.GenerateRandomMarkov(sources: exampleFruitNames, maxLength: 15, chainLength: 3);The Lorem Ipsum random generators create Latin-like words, sentences, and paragraphs. They use Markov Chain models trained on multiple Latin sources: the traditional Lorem Ipsum excerpts of Cicero's De Finibus Bonorum et Malorum, and excerpts of René Descartes's Meditationes de Prima Philosophia.
IRandomizationSource.LoremIpsumParagraph((int WordCount, int MaxWordLength) paragraphParameters)
string of paragraphParameters.WordCount words, broken into a random number of sentences. Each word in the paragraph is no more than paragraphParameters.MaxWordLength characters.IRandomizationSource.LoremIpsumSentence((int WordCount, int MaxWordLength) sentenceParameters)
string of sentenceParameters.WordCount words, each word no more than sentenceParameters.MaxWordLength characters. Sentences always begin with a capital letter and end with a period (.).IRandomizationSource.LoremIpsumWord(int maxLength)
string of no more than maxLength characters.The name generators use Markov Chain models trained on public census and government data.
IRandomizationSource.DemographicsBirthDateTime(DateTime? relativeTo = null)IRandomizationSource.DemographicsBirthDateTime((int MinAgeInYears, int MaxAgeInYearsExclusive) ageRange, DateTime? relativeTo = null)
DateTime within the ageRange specified, relative to relativeTo. If not provided, ageRange defaults to (MinAgeInYears: 18, MaxAgeInYearsExclusive: 96). If not provided, relativeTo defaults to DateTime.UtcNow.IRandomizationSource.DemographicsForenameUsa(int maxLength)
string of no more than maxLength characters. Generated names have an initial capital letter and all subsequent characters are lowercase.IRandomizationSource.DemographicsSurnameUsa(int maxLength)
string of no more than maxLength characters. Generated names have an initial capital letter and all subsequent characters are lowercase.This library does not provide a domain-specific language. However, by creating your own static methods extending IStatefulRandomizationSource<TState> (where TState should be your generator options), you can easily construct a domain-specific language to generate complex or custom types.
This library's unit tests include an example Domain-Specific Language which generates characters for a role-playing game. The documented example illustrates:
- creating a randomization state object (to provide configuration)
- creating a domain value object (the thing we want to generate)
- creating a domain-specific language (a
staticmethod extendingIStatefulRandomizationSource<TState>which used the configuration and generated the value object)- creating unit tests which use the domain-specific language
This functionality was already possible by extending IRandomizationSource with methods accepting parameters. However, IStatefulRandomizationSource<TState> provides greater flexibility. By embedding state object types into your extension methods, you can simplify verbose generator method signatures.
The IStatefulRandomizationSource<TState> extends IRandomizationSource with a public TState State { get; } property.
Use Randomizer.WithState() static methods to create a new stateful randomizer based upon an initial state value.
Use the .WithState(TState initialState) extension method on IRandomizationSource to create a derived stateful randomizer using that randomization source instance.
In some scenarios, you may want to modify the existing randomization source state (e.g., when designing a "builder" interface). This is handled using some monadic methods.
It is intended that the state values used in a randomization source are immutable. When the state needs to change, the following monadic methods allow you to derive a new
IStatefulRandomizationSource<TState>using the current state value.
IStatefulRandomizationSource<TStateCurrent,TStateNew>.Bind(Func<TStateCurrent,IStatefulRandomizationSource<TStateNew>>)
Bind to replace the stateful randomization source with a new stateful randomization source derived from current state. This is an uncommon IStatefulRandomizationSource operation, normally only used when swapping the underlying IRandomizationSource.IStatefulRandomizationSource<TStateCurrent,TStateNew>.Map(Func<TStateCurrent,TStateNew>)
Map to replace the state contained within the stateful randomization source with a new state (which may be of the same type or another). This is the most common state operation.