Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FsCheck: How to generate test data that depends on other test data?

FsCheck has some neat default Arbitrary types to generate test data. However what if one of my test dates depends on another?

For instance, consider the property of string.Substring() that a resulting substring can never be longer than the input string:

[Fact]
public void SubstringIsNeverLongerThanInputString()
{
    Prop.ForAll(
        Arb.Default.NonEmptyString(),
        Arb.Default.PositiveInt(),
        (input, length) => input.Get.Substring(0, length.Get).Length <= input.Get.Length
    ).QuickCheckThrowOnFailure();
}

Although the implementation of Substring certainly is correct, this property fails, because eventually a PositiveInt will be generated that is longer than the genereated NonEmptyString resulting in an exception.

Shrunk: NonEmptyString "a" PositiveInt 2 with exception: System.ArgumentOutOfRangeException: Index and length must refer to a location within the string.

I could guard the comparison with an if (input.Length < length) return true; but that way I end up with lots of test runs were the property isn't even checked.

How do I tell FsCheck to only generate PositiveInts that don't exceed the input string? I presume I have to use the Gen<T> class, but it's interface is just hella confusing to me... I tried the following but still got PositiveInts exceeding the string:

var inputs = Arb.Default.NonEmptyString();
// I have no idea what I'm doing here...
var lengths = inputs.Generator.Select(s => s.Get.Length).ToArbitrary();

Prop.ForAll(
    inputs,
    lengths,
    (input, length) => input.Get.Substring(0, length).Length <= input.Get.Length
).QuickCheckThrowOnFailure();
like image 283
Good Night Nerd Pride Avatar asked Oct 19 '17 18:10

Good Night Nerd Pride


1 Answers

You can create generators which depend on values generated from another using SelectMany. This also allows you to use the LINQ query syntax e.g.

var gen = from s in Arb.Generate<NonEmptyString>()
          from i in Gen.Choose(0, s.Get.Length - 1)
          select Tuple.Create(s, i);

var p = Prop.ForAll(Arb.From(gen), t =>
{
    var s = t.Item1.Get;
    var len = t.Item2;
    return s.Substring(0, len).Length <= s.Length;
});

Check.Quick(p);
like image 200
Lee Avatar answered Nov 19 '22 10:11

Lee