How to fix a range on some properties when create a TestClass by AutoFixture

社会主义新天地 提交于 2020-01-14 07:51:12

问题


Does anyone know how can I tell AutoFixture to specify a range (min and max) on some properties when doing

MyDataClass obj = fixture.Create<MyDataClass>();

where MyDataClass has property Diameter and I only want min:1 and max:60 on this property.


回答1:


Data Annotations

The easiest approach is probably adorning the property itself with a Data Annotation, although I'm not myself a huge fan of this:

public class MyDataClass
{
    [Range(1, 60)]
    public decimal Diameter { get; set; }
}

AutoFixture will respect the [Range] attribute's values.

Convention-based

A better approach is, in my opinion, a convention-based approach that doesn't rely on non-enforceable attributes:

public class DiameterBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi == null ||
            pi.Name != "Diameter" ||
            pi.PropertyType != typeof(decimal))
            return new NoSpecimen(request);

        return context.Resolve(
            new RangedNumberRequest(typeof(decimal), 1.0m, 60.0m));
    }
}

This passing test demonstrates how to use it:

[Fact]
public void ResolveRangeLimitedType()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(new DiameterBuilder());
    var actual = fixture.Create<Generator<MyDataClass>>().Take(100);
    Assert.True(actual.All(x => 1 <= x.Diameter && x.Diameter <= 60));
}

For more details, please refer to this other, very closely related SO Q&A.

Overcoming Primitive Obsession

Perhaps an even better approach is to listen to your tests, combat Primitive Obsession, and introduce a custom type - in this case, a Diameter Value Object.

This is often my preferred approach.




回答2:


Solution by Mark works well, but I wanted a more generic version of it so that I didn't have to write a version of it for every property.

    public class RangeLimiter<T> : ISpecimenBuilder
    {
        private readonly Expression<Func<T, decimal>> _selector;
        private readonly (decimal, decimal) _range;

        public RangeLimiter(Expression<Func<T, decimal>> selector, (decimal, decimal) range)
        {
            _selector = selector;
            _range = range;
        }
        public object Create(object request, ISpecimenContext context)
        {
            var prop = (PropertyInfo)((MemberExpression)_selector.Body).Member;
            var pi = request as PropertyInfo;
            if (pi == null || pi.Name != prop.Name || pi.PropertyType != typeof(decimal))
                return new NoSpecimen();

            return context.Resolve(
                new RangedNumberRequest(typeof(decimal), _range.Item1, _range.Item2));
        }
    }

Usage:

    [Fact]
    public void ResolveRangeLimitedType()
    {
        var fixture = new Fixture();
        fixture.Customizations.Add(new RangeLimiter<MyDataClass>(c => c.Diameter, (1, 12)));
        var actual = fixture.Create<Generator<MyDataClass>>().Take(100);
        Assert.True(actual.All(x => 1 <= x.Diameter && x.Diameter <= 60));
    }

Or an even more generic, but a bit dangerous (tested with int/decimal):

    public class RangeLimiter<T, TNum> : ISpecimenBuilder where TNum : struct
    {
        private readonly Expression<Func<T, TNum>> _selector;
        private readonly (TNum, TNum) _range;

        public RangeLimiter(Expression<Func<T, TNum>> selector, (TNum, TNum) range)
        {
            _selector = selector;
            _range = range;
        }
        public object Create(object request, ISpecimenContext context)
        {
            var prop = (PropertyInfo)((MemberExpression)_selector.Body).Member;
            var pi = request as PropertyInfo;
            if (pi == null || pi.Name != prop.Name || pi.PropertyType != typeof(TNum))
                return new NoSpecimen();

            return context.Resolve(
                new RangedNumberRequest(typeof(TNum), _range.Item1, _range.Item2));
        }
    }


来源:https://stackoverflow.com/questions/22692281/how-to-fix-a-range-on-some-properties-when-create-a-testclass-by-autofixture

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!