所以我有以下几种类型:
public abstract class Base
{
public string Text { get; set; }
public abstract int Value { get; set; }
}
public class BaseImplA : Base
{
public override int Value { get; set; }
}
public class BaseImplB : Base
{
public override int Value
{
get { return 1; }
set { throw new NotImplementedException(); }
}
}
我希望当请求Base时AutoFixture交替创建BaseImplA和BaseImplB。
var fixture = new Fixture().Customize(new TestCustomization());
var b1 = fixture.Create<Base>();
var b2 = fixture.Create<Base>();
问题是BaseImplB从Value属性 setter 中抛出NotImplementedException。因此,我创建了以下定制:
public class TestCustomization : ICustomization
{
private bool _flag;
private IFixture _fixture;
public void Customize(IFixture fixture)
{
_fixture = fixture;
fixture.Customize<BaseImplB>(composer =>
{
return composer.Without(x => x.Value);
});
fixture.Customize<Base>(composer =>
{
return composer.FromFactory(CreateBase);
});
}
private Base CreateBase()
{
_flag = !_flag;
if (_flag)
{
return _fixture.Create<BaseImplA>();
}
return _fixture.Create<BaseImplB>();
}
}
但是正在发生的事情是没有为BaseImplA或BaseImplB设置值。谁能指出我要去哪里错了?
最佳答案
使用AutoFixture 3.18.5+,这并不是很难做到的。这里至少有两个不同的问题在起作用:
处理BaseImplB BaseImplB
类需要特殊处理,这很容易处理。您只需要指示AutoFixture忽略Value
属性即可:
public class BCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<BaseImplB>(c => c.Without(x => x.Value));
}
}
这会省略
Value
属性,但会照常创建BaseImplB
实例,包括填写其他任何可写属性,例如Text
属性。在不同的实现之间交替
为了在
BaseImplA
和BaseImplB
之间交替,您可以编写如下的Customization:public class AlternatingCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new AlternatingBuilder());
}
private class AlternatingBuilder : ISpecimenBuilder
{
private bool createB;
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null || t != typeof(Base))
return new NoSpecimen(request);
if (this.createB)
{
this.createB = false;
return context.Resolve(typeof(BaseImplB));
}
this.createB = true;
return context.Resolve(typeof(BaseImplA));
}
}
}
它只是处理对
Base
的请求,并将对BaseImplA
和BaseImplB
的交替请求中继到context
。包装
您可以将两个自定义项(以及其他(如果有的话))打包在一起,例如:
public class BaseCustomization : CompositeCustomization
{
public BaseCustomization()
: base(
new BCustomization(),
new AlternatingCustomization())
{
}
}
这将使您能够根据需要请求
BaseImplA
,BaseImplB
和Base
;以下测试证明了这一点:[Fact]
public void CreateImplA()
{
var fixture = new Fixture().Customize(new BaseCustomization());
var actual = fixture.Create<BaseImplA>();
Assert.NotEqual(default(string), actual.Text);
Assert.NotEqual(default(int), actual.Value);
}
[Fact]
public void CreateImplB()
{
var fixture = new Fixture().Customize(new BaseCustomization());
var actual = fixture.Create<BaseImplB>();
Assert.NotEqual(default(string), actual.Text);
Assert.Equal(1, actual.Value);
}
[Fact]
public void CreateBase()
{
var fixture = new Fixture().Customize(new BaseCustomization());
var actual = fixture.CreateMany<Base>(4).ToArray();
Assert.IsAssignableFrom<BaseImplA>(actual[0]);
Assert.NotEqual(default(string), actual[0].Text);
Assert.NotEqual(default(int), actual[0].Value);
Assert.IsAssignableFrom<BaseImplB>(actual[1]);
Assert.NotEqual(default(string), actual[1].Text);
Assert.Equal(1, actual[1].Value);
Assert.IsAssignableFrom<BaseImplA>(actual[2]);
Assert.NotEqual(default(string), actual[2].Text);
Assert.NotEqual(default(int), actual[2].Value);
Assert.IsAssignableFrom<BaseImplB>(actual[3]);
Assert.NotEqual(default(string), actual[3].Text);
Assert.Equal(1, actual[3].Value);
}
有关版本控制的说明
这个问题浮出了AutoFixture中的一个错误,因此,在AutoFixture 3.18.5之前的AutoFixture版本中,此答案无法正常工作。
关于设计的说明
AutoFixture最初是作为测试驱动开发(TDD)的工具而构建的,而TDD只是关于反馈的。本着GOOS的精神,您应该听听测试。如果测试难以编写,则应考虑API设计。 AutoFixture倾向于放大这种反馈,在这里似乎也是如此。
如OP中所述,该设计违反了Liskov Substitution Principle,因此您应考虑采用另一种设计,而不是这种情况。这种替代设计也可能使AutoFixture设置更简单,更易于维护。