如何使用 Autofixture 中的 RandomRangedNumberCustomization 来确保参数在一定范围内?

How do I use the RandomRangedNumberCustomization in Autofixture to ensure parameters are in a certain range?

我有一个 class 有两个参数,如图所示:

    public double X { get; private set; }

    public double Y { get; private set; }

    public Point(double x, double y)
    {
        if (x > 90 || x < -90)
            throw new ArgumentOutOfRangeException("latitude");

        if (y > 180 || y < -180)
            throw new ArgumentOutOfRangeException("longitude");

        X = x;
        Y = y;
    }

构造函数中设置了相应的属性,所以我需要告诉AutoFixture创建一个Pointclass,参数在guard子句指定的范围内。我对 RandomRangedNumberCustomization class 的用法感到有点困惑。我做了以下事情:

        var xRange = new RangedNumberRequest(typeof(double), -90.0, 90.0);
        var yRange = new RangedNumberRequest(typeof (double), -180.0, 180.0);
        var dummyContext = new DelegatingSpecimenContext();
        var generator = new RandomRangedNumberGenerator();
        var x = (double)generator.Create(latitudeRange, dummyContext);
        var y = (double) generator.Create(longitudeRange, dummyContext);

这将在我的范围内生成数字,因此我可以创建一个点并输入这些生成的数字,但我在自定义方面遗漏了一些东西。任何帮助和/或指导将不胜感激。

谢谢!

应该有几种方法可以为此自定义 AutoFixture。 – 以下是其中一些:

有数据注释

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(
        [Range( -90,  90)]double x,
        [Range(-180, 180)]double y)
    {
        if (x > 90 || x < -90)
            throw new ArgumentOutOfRangeException("latitude");

        if (y > 180 || y < -180)
            throw new ArgumentOutOfRangeException("longitude");

        this.X = x;
        this.Y = y;
    }
}

[Fact]
public void CreatePointDoesNotThrow()
{
    var fixture = new Fixture();
    Assert.DoesNotThrow(() => fixture.Create<Point>()); // Passes.
}

带发电机

public class Point
{
    public double X { get; private set; }
    public double Y { get; private set; }

    public Point(double x, double y)
    {
        if (x > 90 || x < -90)
            throw new ArgumentOutOfRangeException("latitude");

        if (y > 180 || y < -180)
            throw new ArgumentOutOfRangeException("longitude");

        this.X = x;
        this.Y = y;
    }
}

[Fact]
public void CreatePointDoesNotThrow()
{
    var fixture = new Fixture();
    var x = new Generator<int>(fixture).First(pt => pt >  -90 && pt <  90);
    var y = new Generator<int>(fixture).First(pt => pt > -180 && pt < 180);
    fixture.Customize<Point>(c => c
        .FromFactory(() => new Point(x, y)));

    Assert.DoesNotThrow(() => fixture.Create<Point>()); // Passes.
}

除了 Nikos Baxevanis 建议的解决方案外,还有另一种选择。您可以实现一个始终以特定方式处理 Pointxy 参数的约定:

public class PointCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new XBuilder());
        fixture.Customizations.Add(new YBuilder());
    }

    private class XBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as ParameterInfo;
            if (pi == null ||
                pi.Name != "x" ||
                pi.Member.DeclaringType != typeof(Point))
                return new NoSpecimen(request);

            return context.Resolve(
                new RangedNumberRequest(typeof(double), -90d, 90d));
        }
    }

    private class YBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as ParameterInfo;
            if (pi == null ||
                pi.Name != "y" ||
                pi.Member.DeclaringType != typeof(Point))
                return new NoSpecimen(request);

            return context.Resolve(
                new RangedNumberRequest(typeof(double), -180d, 180d));
        }
    }
}

(以上代码还有重构的空间)

给定 PointCustomization,此测试通过:

[Fact]
public void CreatePointDoesNotThrow()
{
    var fixture = new Fixture().Customize(new PointCustomization());
    var e = Record.Exception(() => fixture.Create<Point>());
    Assert.Null(e);
}