【问题标题】:Why is AutoFixture Customization causing inherited properties to not be filled?为什么 AutoFixture Customization 会导致继承的属性不被填充?
【发布时间】:2013-02-01 02:59:51
【问题描述】:

我编写了以下自定义,并将其作为组合的一部分应用于我的大多数测试。我的实体有一个只读 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 更远的东西继承的实体,我的自定义会阻止属性自动填充。

此测试方法中的User实体填充正确:

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 还缺少什么?我首先、中间和最后应用了我的自定义设置,但无济于事。

【问题讨论】:

  • 这次this post processor还有用吗?
  • 如此处所述,我无法重现此问题...事实上,在我尝试重现此问题时,User 也没有自动填充值...跨度>
  • @RubenBartelink 我试过你的后处理器。我看到的结果与我的自定义相同。
  • @MarkSeemann 应该我的自定义“劫持”AutoFixture 的代码,使其不提供UserAwesomeUser 实例的值?
  • 我对@9​​87654328@ 不太熟悉。我知道它最终被用于Create 一个值。如您所见,我的自定义获取了夹具的引擎并将其用作Postprocessor 的样本构建器。我感觉这就是问题所在。相反,我是否应该创建自己的 ISpecimenBuilder 来生成任何 BaseEntity 的 Id,然后以某种方式让 AutoFixture 使用实例的其余属性“做它的事情”?

标签: unit-testing autofixture


【解决方案1】:

上面提供的解决方案是一个非常聪明的尝试,但不是我以前见过的。一个更惯用的解决方案是这样的:

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 的意见是:

优先考虑对象组合而不是类继承。

-Design Patterns, p. 20

AutoFixture 首先是一个 TDD 工具,而 TDD 的主要优点之一是它提供有关类设计的反馈。在这种情况下,反馈是:继承既尴尬又麻烦。重新考虑设计。

【讨论】:

  • 谢谢,马克。我昨天完成了样本构建器代码(大部分和你的一样),但不知道如何应用它(你的自定义方法)。当我这周可以恢复时,我会试一试。我和你一起讨论关于继承的组合。我使用 BaseEntity 来减少在我的所有实体中重复非常基本但非常常见的功能。至于第二级继承,我使用 FakeUser(不是 AwesomeUser)在 User 上调用一些受保护的方法进行测试。不过,看起来设计确实需要一些工作。
猜你喜欢
  • 1970-01-01
  • 2022-01-01
  • 2020-12-08
  • 2012-09-27
  • 1970-01-01
  • 1970-01-01
  • 2013-10-23
  • 1970-01-01
  • 2016-12-26
相关资源
最近更新 更多