1

I'm trying to use FsCheck to write a basic property based test for a class that generates random DateTimeOffset values in a given interval.

[Property]
public void ValueBetweenMinAndMax(DateTimeOffset min, DateTimeOffset max)
{
    var sut = new DateTimeOffsetGenerator();
    DateTimeOffset actual = sut.Next(min, max);
    Assert.True(min <= actual);
    Assert.True(max >= actual);
}

That test fails pretty quickly when min> max because I validate the input parameters of Next() and throw an ArgumentExceptionin that case.

public DateTimeOffset Next(DateTimeOffset min, DateTimeOffset max)
{
    if (min > max)
    {
        throw new ArgumentException(nameof(min));
    }

    // ...
}

I don't want to change the implementation to swap the input parameters. And I don't want to do that in the test method either.

Is there a way to teach FsCheck to generate the min and max values with the constraint that min must never be greater that max?

Samples in C# would be greatly appreciated because my knowledge about F# is not up to par.

Sebastian Weber
  • 6,566
  • 2
  • 27
  • 46
  • 1
    if you ask for a min input and a variance input, then compute max from min+variance, you can pass that onward to express this – Ruben Bartelink Dec 21 '18 at 10:44
  • @RubenBartelink Unfortunately that would still require an Arbitrary (to generate only positive TimeSpans) plus logic to protect from overflow (DateTimeOffset.MaxValue + 10s) inside the test method. I still like the idea. It might come in handy elsewhere. – Sebastian Weber Dec 21 '18 at 14:38
  • my point is to ask for an int as an input argument to the FsCheck.xUnit [Propery], then use that `%` - i.e. preprocess and pass it on – Ruben Bartelink Dec 21 '18 at 15:47

1 Answers1

0

In the end I solved the problem like this

[Property(Arbitrary = new[] { typeof(MyArbitraries) })]
public void ValueBetweenMinAndMax((DateTimeOffset minValue, DateTimeOffset maxValue) bounds)
{
    // ...
}

public static class MyArbitraries
{
    public static Arbitrary<(DateTimeOffset minValue, DateTimeOffset maxValue)> DateTimeOffsetBounds()
    {
        return (from minValue in Arb.Generate<DateTimeOffset>()
                from maxValue in Arb.Generate<DateTimeOffset>()
                where minValue <= maxValue
                select (minValue, maxValue))
            .ToArbitrary();
    }
}

I wanted the test parameters to match the signature of the sut to keep the test as easy to understand as possible. Pre-processing parameters would have distracted from what I actually wanted to test.

Nonetheless the alternative pointed out by @Ruben is still a good one.

Sebastian Weber
  • 6,566
  • 2
  • 27
  • 46