有谁知道如何告诉AutoFixture在执行某些属性时指定范围(最小值和最大值)
MyDataClass obj = fixture.Create<MyDataClass>();
其中MyDataClass具有属性Diameter,我只希望min:1和max:60在此属性上。
答案 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 Obsession和introduce 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 之间的新随机值。