From the original Haskell version,
QuickCheck is a library for random testing of program properties. The programmer provides a specification of the program, in the form of properties which functions should satisfy, and QuickCheck then tests that the properties hold in a large number of randomly generated cases.
This library aims to provide similar property testing functionality for .NET.
QuickCheck is available as a NuGet package. You can add it to an existing project as you would any other public nuget package. Using the .NET CLI:
dotnet add package QuickCheckSay I've written a function to reverse Memory<int>. A property of a working reverse function is that reversing a reversed sequence should give the original sequence. We can check that this is the case for our function using QuickCheck.
using QuickCheck;
// The function to test
static Memory<T> Reverse<T>(Memory<T> memory)
{
var span = memory.Span;
var reversed = new T[span.Length];
for (var i = 0; i < span.Length; i++)
{
reversed[span.Length - i - 1] = span[i];
}
return new Memory<T>(reversed);
}
var qc = QuickChecker.CreateEmpty();
// Create and add a generator for `Memory<int>`
var generator = new ArbitraryMemoryGenerator<int>(
ArbitraryInt32Generator.Default,
Random.Shared,
maximumSize: 32);
qc.AddGenerator(generator);
// Test function and validate that for all Memory<int> (Reverse(Reverse(memory)) == memory)
var result = qc.Run(
target: static (Memory<int> memory) => Reverse(Reverse(memory)).Span.SequenceEqual(memory.Span),
validate: static (result) => result);
Console.WriteLine(result.Type); // SuccessThe main entry point for interacting with the library is the QuickChecker class.
To construct an instance with the default generators pre-registered, use an overload of the CreateDefault method.
using QuickCheck;
// Using the default options
var qc = QuickChecker.CreateDefault();
// Using custom options
var configuredQc = QuickChecker.CreateDefault(options =>
{
options.RunCount = 1000;
options.Random = new Random(42);
});To construct an instance with no pre-registered generators, use one of the CreateEmpty overloads.
using QuickCheck;
// Using the default options
var qc = QuickChecker.CreateEmpty();
// Using custom options
var configuredQc = QuickChecker.CreateEmpty(options =>
{
options.RunCount = 1000;
options.Random = new Random(42);
});To test your functions, the QuickChecker must be able to generate values for every argument type of the target function.
This library provides a number of built-in generators for common types, including many numeric types, char, and string. These are added when using CreateDefault overloads.
Generic generators are available for List<T>, Memory<T>, and tuples, when given a generator for T. These must be added manually.
To add a generator to the QuickChecker instance, use the AddGenerator method:
using QuickCheck.Generators;
var qc = QuickChecker.CreateEmpty();
qc.AddGenerator(ArbitraryInt32Generator.Default);With the QuickChecker configured, you can test a function by calling the Run method.
Say I wrote a method to divide two integers, but return 0 if the divisor is 0. However, I forgot to handle the case where the divisor is 0.
using QuickCheck;
// If b == 0, return 0, else return a / b. Oops!
static int NotSoSafeDivide(int a, int b) => a / b;
var qc = QuickChecker.CreateDefault();
var result = qc.Run(static (int a, int b) => NotSoSafeDivide(a, b));
Console.WriteLine(result.IsError); // True
Console.WriteLine(result.Exception is DivideByZeroException); // TrueThe original QuickCheck library in Haskell:
I learned about QuickCheck from Jon Gjengset's great video on the Rust implementation: