Almost a year ago I wrote a series of blog posts on how to use property-based tests in C#.
- Part 1 – Introduction
- Part 2 – An example
- Part 3 – Finding edge cases
- Part 4 – Writing your own generators
- Part 5 – Locking input
In this series I used FsChttps://fscheck.github.io/FsCheck/heck as the library of my choice. Although originally created for F#, it also works for C# as I have demonstrated.
However as it was originally created for F#, it sometimes feels strange when using FsCheck in C#. If you prefer a more idiomatic alternative, you can have a look at CsCheck, also inspired by QuickCheck but specifically created for C#.
CsCheck offers no specific integration but can be used with any testing framework(XUnit, NUnit, MSTest, …).
Here is a small example:
using CsCheck; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using Xunit.Abstractions; | |
namespace PropertyBasedTesting.Tests | |
{ | |
public class WhenIAddTwoNumbers | |
{ | |
private readonly ITestOutputHelper testOutputHelper; | |
public WhenIAddTwoNumbers(ITestOutputHelper testOutputHelper) | |
{ | |
this.testOutputHelper = testOutputHelper; | |
} | |
int Add(int x, int y) => x + y; | |
[Fact] | |
public void TheOrderOfTheOperationsDoesntMatter() | |
{ | |
(from t in Gen.Select(Gen.Int, Gen.Int, Gen.Int) | |
select t) | |
.Sample(t => Add(Add(t.Item1, t.Item2),t.Item3) == Add(t.Item1, Add(t.Item2,t.Item3)),testOutputHelper.WriteLine); | |
} | |
[Fact] | |
public void AddingZeroIsTheSameAsDoingNothing() | |
{ | |
Gen.Int.Sample(x => Add(0, x) == x); | |
} | |
[Fact] | |
public void TheResultShouldNotDependOnTheOrderOfTheParameters() | |
{ | |
(from t in Gen.Select(Gen.Int, Gen.Int) | |
select t) | |
.Sample(t => Add(t.Item1, t.Item2) == Add(t.Item2, t.Item1)); | |
} | |
} | |
} |
CsCheck does it really well in the shrinking challenge and offers support for multiple types of tests including concurrency testing.
This is a feature I really like as concurrency related issues are really hard to find and debug. When writing this type of tests, you specify a set of operations that will be executed concurrently and then be compared with linearized execution.
Here is an example:
[Fact] | |
public void SampleConcurrent_ConcurrentDictionary() | |
{ | |
Gen.Dictionary(Gen.Int[0, 100], Gen.Byte)[0, 10].Select(l => new ConcurrentDictionary<int, byte>(l)) | |
.SampleConcurrent( | |
Gen.Int[0, 100].Select(Gen.Byte) | |
.Operation<ConcurrentDictionary<int, byte>>(t => $"d[{t.Item1}] = {t.Item2}", (d, t) => d[t.Item1] = t.Item2), | |
Gen.Int[0, 100] | |
.Operation<ConcurrentDictionary<int, byte>>(i => $"TryRemove({i})", (d, i) => d.TryRemove(i, out _)) | |
, writeLine:testOutputHelper.WriteLine); | |
} |