我编写了以下自定义,并将其作为复合的一部分应用于我的大多数测试中。我的实体具有只读 Id,但我在此自定义中使用其 SetId 方法,以确保所有实体都具有一些 Id(如果它们是暂时的(还没有 Id)。
public class SetEntityIdCustomization : ICustomization {
public void Customize(IFixture fixture) {
var engine = ((Fixture)fixture).Engine;
fixture.Customizations.Add(new Postprocessor(
engine, o => {
var entity = o as BaseEntity;
if (entity == null || !entity.IsTransient()) {
return;
}
entity.SetId(fixture.CreateAnonymous<Guid>());
}));
}
}
这一直很好用,直到我今天发现了一件非常奇怪的事情。如果我向测试提供直接从 BaseEntity 继承的实体之一,一切都很好,它的可写属性会自动填充。但是,如果我请求的实体继承自 BaseEntity 的更下方的内容,我的自定义会阻止属性自动填充。
此测试方法中的用户实体已正确填充:
public class User : BaseEntity {
public string Email { get; set; }
public int CoolThings { get; set; }
}
...
[Theory, AutoDomainData]
public void SomeTest(User user, ...) {
// user.Email and user.CoolThings have auto-filled values, as expected.
...
}
但是,以下测试中的 AwesomeUser 实体不会自动填充任何相同的属性。
public class AwesomeUser : User {
...
}
...
[Theory, AutoDomainData]
public void SomeOtherTest(AwesomeUser user, ...) {
// user.Email nor user.CoolThings have auto-filled values. What gives?
...
}
在这两个测试用例中,由于我的自定义,Id 属性是自动填充的。如果我删除我的自定义项,SomeOtherTest 的 AwesomeUser 实例会很好地自动填充其继承的属性。我必须假设我的定制是搞砸了事情的原因。
有没有更好的方法让我所有的 BaseEntity 实例设置它们的 ID,或者 AutoFixture 是否缺少其他东西?我首先在中间应用了我的自定义,最后应用了我的自定义,但无济于事。
提供的解决方案是一个非常聪明的尝试,但我以前没有见过。一个更惯用的解决方案是这样的:
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
new FilteringSpecimenBuilder(
new Postprocessor(
new BaseEntityBuilder(
new ConstructorInvoker(
new ModestConstructorQuery())),
new AutoPropertiesCommand().Execute),
new BaseEntitySpecification()));
}
private class BaseEntityBuilder : ISpecimenBuilder
{
private readonly ISpecimenBuilder builder;
private readonly IRequestSpecification specification;
public BaseEntityBuilder(ISpecimenBuilder builder)
{
this.builder = builder;
this.specification = new BaseEntitySpecification();
}
public object Create(object request, ISpecimenContext context)
{
if (!this.specification.IsSatisfiedBy(request))
return new NoSpecimen(request);
var b = (BaseEntity)this.builder.Create(request, context);
b.SetId((Guid)context.Resolve(typeof(Guid)));
return b;
}
}
private class BaseEntitySpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request)
{
var t = request as Type;
if (t == null)
return false;
if (!typeof(BaseEntity).IsAssignableFrom(t))
return false;
return true;
}
}
如您所见,这不是一个简单的单行代码,这表明AutoFixture是一个相当固执己见的库。在这种情况下,AutoFixture的观点是:
优先选择对象组合而不是类继承。
-设计模式,第20页
AutoFixture首先是一个TDD工具,TDD的主要优点之一是它提供了有关类设计的反馈。在这种情况下,反馈是:继承既尴尬又麻烦。重新考虑设计。