6

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();
Good Night Nerd Pride
  • 8,245
  • 4
  • 49
  • 65

1 Answers1

7

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);
Lee
  • 142,018
  • 20
  • 234
  • 287
  • Is this something I could have found in the [official docs](https://fscheck.github.io/FsCheck/TestData.html)? If not could you please add your reference? – Good Night Nerd Pride Oct 19 '17 at 18:31
  • 2
    @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). – Lee Oct 20 '17 at 08:48
  • 2
    There aren't many examples in the docs for either the F# computation expressions or the LINQ syntax. Here's an example https://github.com/fscheck/FsCheck/blob/master/examples/FsCheck.CSharpExamples/Program.cs#L150 LINQ is mentioned in the second paragraph of the TestData page, and there is a small example here: https://fscheck.github.io/FsCheck/TestData.html#Generators – Kurt Schelfthout Oct 21 '17 at 11:32