Autofixture - create a float, double or decimal with remainder
Asked Answered
W

3

11

How do I modify AutoFixture create method for float, double and decimal, so that when these types get created they will also have a remainder?

Currently I do this, but this throws exception.

var fixture = new Fixture();
fixture.Customize<double>(sb => sb.FromFactory<double>(d => d * 1.33));   //This should add remainder
var value = fixture.Create<double>();
Winebibber answered 16/7, 2013 at 9:31 Comment(0)
G
14

Attempting to redefine a type (double) by using a value of the same type (double) will, indeed, yield an infinite recursion. However, you can easily make this work by changing the seed input into another type - e.g. an int:

var fixture = new Fixture();
fixture.Customize<double>(c => c.FromFactory<int>(i => i * 1.33));
var value = fixture.Create<double>();

Doubles will now tend to have fractional values too.

Gough answered 17/7, 2013 at 6:12 Comment(2)
I guess I haven't really thought about how things work in the background and that customization was creating an infinite recursion. Thanks for clarifying this!Winebibber
I didn't realize AutoFixture generates only integral floating point numbers! that seems completely odd/wrong. I just wrote my own CreateDecimal, etc. extension methods that add Random.NextDouble() to the result, but I've been using Create<decimal> for some time assuming the results would have fractional parts.Crackleware
E
6

One option is to use a custom ISpecimenBuilder:

var fixture = new Fixture();
fixture.Customizations.Add(
    new RandomDoublePrecisionFloatingPointSequenceGenerator());

The RandomDoublePrecisionFloatingPointSequenceGenerator could look like below:

internal class RandomDoublePrecisionFloatingPointSequenceGenerator
    : ISpecimenBuilder
{
    private readonly object syncRoot;
    private readonly Random random;

    internal RandomDoublePrecisionFloatingPointSequenceGenerator()
    {
        this.syncRoot = new object();
        this.random = new Random();
    }

    public object Create(object request, ISpecimenContext context)
    {
        var type = request as Type;
        if (type == null)
            return new NoSpecimen(request);

        return this.CreateRandom(type);
    }

    private double GetNextRandom()
    {
        lock (this.syncRoot)
        {
            return this.random.NextDouble();
        }
    }

    private object CreateRandom(Type request)
    {
        switch (Type.GetTypeCode(request))
        {
            case TypeCode.Decimal:
                return (decimal)
                    this.GetNextRandom();

            case TypeCode.Double:
                return (double)
                    this.GetNextRandom();

            case TypeCode.Single:
                return (float)
                    this.GetNextRandom();

            default:
                return new NoSpecimen(request);
        }
    }
}
Eliason answered 16/7, 2013 at 20:58 Comment(3)
Nikos thanks for the help, but Mark's answer is more inline with what I was searching.Winebibber
+1 Any reason for making this SpecimenBuilder thread safe - do you do this for all your builders? (I understand why a Random needs to be guarded bit it never occurred to me to add this guarding in a SpecimenBuilder. I'd never though of Fixture and/or Generators emanating from same as being guaranteed threadsafe. I certainly can understand how, esp given @ploeh's Immutability work in V3.0 that it might be relatively easy enough to achieve). Or have I missed a blog post :PAntagonistic
+1 This one could also work without the thread safety. Almost all numeric sequence generators are thread safe though.. Most of them (if not all) were created prior to 3.0.Eliason
C
0

There doesn't seem to be a way to intercept/modify created values, but a solution I think I prefer is to use a second Fixture.

public static IFixture FixDecimals(
   this IFixture me
) {
   var f = new Fixture();
   var r = new Random();
   me.Register<float>(() => f.Create<float>() + r.NextSingle());
   me.Register<double>(() => f.Create<double>() + r.NextDouble());
   me.Register<decimal>(() => f.Create<decimal>() + (decimal)r.NextDouble());
   return me;
}

I think 'normally' you could use something like the following, but it doesn't work in these cases because these are value types.

// doesn't work
var r = new Random();
me.Customize<float>(f => f.Do(_f => _f += r.NextSingle()));

This doesn't work because Do isn't a function but wants you to modify the 'instance', but since these are value types, modifying the 'instance' does nothing because we have a value not an object instance.

Crackleware answered 28/8, 2023 at 3:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.