Search code examples
c#fscheck

Is this a good strategy for composing arbitraries?


Just started looking at FsCheck, wrote several tests, and now I am wondering what is a good strategy for composing more complex arbitraries. Is registering arbitraries within arbitrary good approach? Something like this

public class DiscountAmountArbitrator
    {
        public static Arbitrary<DiscountAmount> DiscountAmounts()
        {
            Arb.Register<AmountArbitrary>();

            var toReturn = (from a in Arb.Generate<Amount>()
                            select new DiscountAmount(a))
                          .ToArbitrary();

            return toReturn;
        }
    }

 public class AmountArbitrary
    {
        public static Arbitrary<Amount> Amounts()
        {
            return Arb.Generate<decimal>().Where(x => x > 0)
                .Select(x => new Amount(x))
                .ToArbitrary();
        }
    } 

Solution

  • My advice would be, if you have different related arbitrary instances, add them to the same class and call the methods directly instead when there are dependencies between the different instances. Then when using any of the Arbitrary instances in tests, register them all at once.

    This makes your code less dependent on the order in which things are executed (Arb.Register is a fundamentally side-effecting operation, and this can lead to some unexpected results). There is no real advantage to putting Arbitrary methods on different classes. So I would rewrite what you have as follows:

    public class Arbitraries
    {
        public static Arbitrary<DiscountAmount> DiscountAmounts()
        {
            var toReturn = (from a in Amounts().Generator
                            select new DiscountAmount(a))
                          .ToArbitrary();
    
            return toReturn;
        }
    
        public static Arbitrary<Amount> Amounts()
        {
            return Arb.Generate<decimal>().Where(x => x > 0)
                .Select(x => new Amount(x))
                .ToArbitrary();
        }
    }