FsCheck in C#: generate a list of two dimension arrays with the same shape
Asked Answered
C

2

13

Let's say I'm writing some code for video analysis. Here is a simplified version of a Video class:

public class Video
{
    public readonly int Width;
    public readonly int Height;
    public readonly List<int[,]> Frames;

    public Video(int width, int height, IEnumerable<int[,]> frames)
    {
        Width = width;
        Height = height;
        Frames = new List<int[,]>();
        foreach (var frame in frames)
        {
            if (frame.GetLength(0) != height || frame.GetLength(1) != width)
            {
                throw new ArgumentException("Incorrect frames dimensions");
            }
            Frames.Add(frame);
        }
    }
}

How do I make an Arbitrary<Video> and register it? How do I make a shrinker for that Arbitrary?

Tried this, couldn't understand how apply works:

public static Arbitrary<Video> Videos()
{
    var videoGen = Arb.Generate<PositiveInt>()
        .SelectMany(w => Arb.Generate<PositiveInt>(), (w, h) => new {w, h})
        .Apply( /* what is Gen<Func<a,b>> */);

    return videoGen.ToArbitrary();
}

Tried this, but couldn't plug the generator for list in here:

public static Arbitrary<Video> Videos()
{
    var videoGen = Arb.Generate<PositiveInt>()
        .SelectMany(w => Arb.Generate<PositiveInt>(), (w, h) => new Video(w, h, /* how to plug generator here? */));

    return videoGen.ToArbitrary();
}
Cartan answered 27/9, 2015 at 18:23 Comment(0)
S
15

Using Kurt Schelfthout's answer as a foundation, you can write an Arbitrary for the video class like this:

public static class VideoArbitrary
{
    public static Arbitrary<Video> Videos()
    {
        var genVideo = from w in Arb.Generate<PositiveInt>()
                       from h in Arb.Generate<PositiveInt>()
                       from arrs in Gen.ListOf(
                           Gen.Array2DOf<int>(
                               h.Item,
                               w.Item,
                               Arb.Generate<int>()))
                       select new Video(w.Item, h.Item, arrs);
        return genVideo.ToArbitrary();
    }
}

You can use this in various ways.

Plain vanilla FsCheck

Here's how to use the Video Arbitrary with plain vanilla FsCheck, here hosted within an xUnit.net test case, which isn't required: you can host this in whichever process you prefer:

[Fact]
public void VideoProperty()
{
    var property = Prop.ForAll(
        VideoArbitrary.Videos(),
        video =>
        {
            // Test goes here...
            Assert.NotNull(video);
        });
    property.QuickCheckThrowOnFailure();
}

Prop.ForAll is very useful for defining properties with custom Arbitraries. When you call QuickCheckThrowOnFailure, it's going to run the test for 'all' (by defailt: 100) values of the Video class.

Untyped xUnit.net property

You can also use the FsCheck.Xunit Glue Library, but you have to pass the Arbitrary as a weakly typed value to the attribute:

[Property(Arbitrary = new[] { typeof(VideoArbitrary) })]
public void XunitPropertyWithWeaklyTypedArbitrary(Video video)
{
    // Test goes here...
    Assert.NotNull(video);
}

This is simple and easy to understand, but there's no static type checking involved when assigning that Arbitrary property, so I'm not too fond of this approach.

Typed xUnit.net property

A better way to use FsCheck.Xunit with custom Arbitraries is to combine it with Prop.ForAll:

[Property]
public Property XUnitPropertyWithStronglyTypedArbitrary()
{
    return Prop.ForAll(
        VideoArbitrary.Videos(),
        video =>
        {
            // Test goes here...
            Assert.NotNull(video);
        });
}

Notice that the return type of this method is no longer void, but Property; the [Property] attribute understands this type and executes the test accordingly.

This third option is my preferred way of using custom Arbitraries from within xUnit.net, because it brings back compile-time checking.

Signalment answered 29/9, 2015 at 11:48 Comment(1)
I didn't know the typed xUnit.net property, that is a great option!Aphrodite
H
6

Just a spur of the moment sketch - not compiled :)

var genVideo = from w in Arb.Generate<PositiveInt>()
               from h in Arb.Generate<PositiveInt>()
               from arrs in Gen.ListOf(Gen.Array2DOf(h, w, Arb.Generate<int>))
               select new Video(w, h, arrs);
Hacksaw answered 28/9, 2015 at 14:8 Comment(1)
That gave me a good start, but I had to wiggle it a bit before it'd compile :)Signalment

© 2022 - 2024 — McMap. All rights reserved.