如何通过AutoFixture创建TestClass时修复某些属性的范围

时间:2014-03-27 15:33:04

标签: unit-testing autofixture

有谁知道如何告诉AutoFixture在执行某些属性时指定范围(最小值和最大值)

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

其中MyDataClass具有属性Diameter,我只希望min:1和max:60在此属性上。

3 个答案:

答案 0 :(得分:12)

数据注释

最简单的方法可能是使用Data Annotation来装饰属性本身,尽管我自己并不是这方面的忠实粉丝:

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

AutoFixture将尊重[Range]属性的值。

<强>公约基于

在我看来,更好的方法是基于约定的方法,它不依赖于不可执行的属性:

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));
    }
}

此通过测试演示了如何使用它:

[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));
}

有关详细信息,请参阅this other, very closely related SO Q&A

克服原始痴迷

或许更好的方法是听取您的测试,打击Primitive Obsessionintroduce a custom type - 在这种情况下,是Diameter值对象。

这通常是我的首选方法。

答案 1 :(得分:0)

马克的解决方案效果很好,但是我想要一个更通用的版本,这样我就不必为每个属性都编写一个版本。

    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));
        }
    }

用法:

    [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));
    }

或更通用的,但是有点危险(用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));
        }
    }

答案 2 :(得分:0)

您可以在实例化夹具时简单地添加一个特定的 ICustomization<MyDataClass>

IFixture fixture = new Fixture();
fixture.Customize<MyDataClass>(c => c
  .With(x => x.Diameter, () => new Random().Next(1, 61)); // maxValue is excluded, thus 61

现在,每当您使用 fixture.Create<MyDataClass>() 时,都会在创建的实例上设置一个介于 1 到 60 之间的新随机值。