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

1

7

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();
Soble answered 19/10, 2017 at 18:3 Comment(2)
fscheck.github.io/FsCheck/TestData.htmlCosmetic
I looked through this site multiple times already and still couldn't figure out a way to do what I want.Soble
P
8

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);
Ptah answered 19/10, 2017 at 18:25 Comment(3)
Is this something I could have found in the official docs? If not could you please add your reference?Soble
@GoodNightNerdPride - I don't think I've seen much documentation for the C# interop, so I probably found out based on reading the source and an educated guess from the gen builder. F# computation expressions (like gen { ... }) map quite closely to the C# LINQ query syntax since they are built from the same syntactic pattern (although F#'s supports more features).Ptah
There aren't many examples in the docs for either the F# computation expressions or the LINQ syntax. Here's an example github.com/fscheck/FsCheck/blob/master/examples/… LINQ is mentioned in the second paragraph of the TestData page, and there is a small example here: fscheck.github.io/FsCheck/TestData.html#GeneratorsDenny

© 2022 - 2024 — McMap. All rights reserved.