【问题标题】:How to fix a range on some properties when create a TestClass by AutoFixture通过 AutoFixture 创建 TestClass 时如何修复某些属性的范围
【发布时间】:2014-03-27 15:33:03
【问题描述】:

如何告诉 AutoFixture 在某些属性上指定范围(最小值和最大值)

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

MyDataClass 有属性 Diameter 并且我只想要这个属性的 min:1 和 max:60?

【问题讨论】:

    标签: unit-testing autofixture


    【解决方案1】:

    您可以在实例化夹具时简单地添加特定的ICustomization&lt;MyDataClass&gt;:

    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&lt;MyDataClass&gt;() 时,都会在创建的实例上设置一个介于 1 和 60 之间的新随机值。

    【讨论】:

    • 这并不像其他答案那样“正确”,但这正是我想要的 - 谢谢!
    【解决方案2】:

    数据注释

    最简单的方法可能是用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 Obsession和introduce a custom type——在本例中是Diameter值对象。

    这通常是我的首选方法。

    【讨论】:

    • 我不想使用 DataAnnotations,因为我需要一个后端使用的类并且没有 UI。有没有一种更简单的方法来实现这一点而不必创建一个类(尽管是一个小类)?我可能有 2-3 个测试需要这个整数值范围。仅在少数几个将使用它的地方添加一个类似乎有点多 - 并且仅在一个 int 类型的属性上。我不需要对所有 int 类型进行范围限制。
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-23
      • 2023-02-23
      • 1970-01-01
      • 2014-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多