所以我有以下类型:
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(); }
}
}
我希望AutoFixture在请求Base时交替创建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设置Value。谁能指出我哪里出错?
答案 0 :(得分:9)
使用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
之间切换,您可以像这样编写自定义:
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设置更简单,更易于维护。
答案 1 :(得分:0)
Mark Seemann提供了出色的回应。您可以为您的抽象基类型构建可重用的旋转样本构建器,如下所示:
public class RotatingSpecimenBuilder<T> : ISpecimenBuilder
{
protected const int Seed = 812039;
protected readonly static Random Random = new Random(Seed);
private static readonly List<Type> s_allTypes = new List<Type>();
private readonly List<Type> m_derivedTypes = new List<Type>();
private readonly Type m_baseType = null;
static RotatingSpecimenBuilder()
{
s_allTypes.AddRange(AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()));
}
public RotatingSpecimenBuilder()
{
m_baseType = typeof(T);
m_derivedTypes.AddRange(s_allTypes.Where(x => x != m_baseType && m_baseType.IsAssignableFrom(x)));
}
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null || t != m_baseType || m_derivedTypes.Count == 0)
{
return new NoSpecimen(request);
}
var derivedType = m_derivedTypes[Random.Next(0, m_derivedTypes.Count - 1)];
return context.Resolve(derivedType);
}
}
然后将此样本构建器注册为每个基本类型的夹具定制,如下所示:
var fixture = new Fixture.Customizations.Add(new RotatingSpecimenBuilder<YourBaseType>());