如何在保持类型自定义的同时使用AutoFixture构建自定义属性?

Ben*_*son 14 .net c# unit-testing autofixture

我试图使用autofixture来创建一个对象,但它们是我想要始终默认的某些属性(其余的可以自动生成).但是,每当我设置自定义时,它都会在我使用自定义构建时被覆盖.

void Main()
{
    var fixture = new Fixture();
    fixture.Customize<Person>(composer => composer.With(p => p.Name, "Ben"));

    var person = fixture.Build<Person>()
        .With(p => p.DateOfBirth, new DateTime(1900, 1, 1))
        .Create();

    /*  RESULT OF person below
    Name    null
    DateOfBirth 1/1/1900
    StreetAddress   StreetAddressafd6b86b-376a-4355-9a9c-fbae34731453
    State   State019e867b-ac5e-418f-805b-a64146bc06bc
    */
}

public class Person
{
    public string Name { get; set;}

    public DateTime DateOfBirth { get; set;}

    public string StreetAddress { get; set;}

    public string State { get; set;}
}
Run Code Online (Sandbox Code Playgroud)

"Name"和"DateOfBirth"属性自定义不会发生冲突,因此我不知道为什么Name最终为null.我希望名字是"本".

如何获得它以便应用两种自定义(即Name ="Ben"和DateOfBirth = 1/1/1900?

Enr*_*lio 16

正如@DavidOsborne正确指出的那样,您所看到的行为是按照设计的.

一个更好的做法是组织在单独的类您的自定义,然后根据需要通过特定的测试场景启用它们.

自定义对象实现ICustomization接口,其作用是以Fixture特定方式配置对象.这是一个例子:

public class AllPersonsAreNamedBen : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.Name, "Ben"));
    }
}

public class AllPersonsAreBornIn1900 : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.DateOfBirth, new DateTime(1900, 1, 1)));
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以Fixture使用该Customize方法启用特定的自定义,例如:

fixture.Customize(new AllPersonsAreNamedBen());
Run Code Online (Sandbox Code Playgroud)

要么:

fixture.Customize(new AllPersonsAreBornIn1900());
Run Code Online (Sandbox Code Playgroud)

您还可以使用以下CompositeCustomization类将多个自定义组合为一个新自定义:

public class AllPersonsAreNamedBenAndAreBornIn1900 : CompositeCustomization
{
    public AllPersonsAreNamedBenAndAreBornIn1900()
        : base(new AllPersonsAreNamedBen(),
               new AllPersonsAreBornIn1900())
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

在这一点上你可以简单地说:

fixture.Customize(new AllPersonsAreNamedBenAndAreBornIn1900());
Run Code Online (Sandbox Code Playgroud)

但是,请记住,自定义应用于一个Fixture问题的顺序:最后一个获胜并可能覆盖以前的,如@MarkSeemann在评论中指出的那样.这也是设计上的.

因此,虽然您可以组合适用于不同类型的现有自定义,但在这种特定情况下,由于两个自定义都针对相同类型,因此您必须创建新的自定义以封装所Person组合类型的所有设置:

public class AllPersonsAreNamedBenAndAreBornIn1900 : CompositeCustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Person>(composer =>
            composer.With(p => p.Name, "Ben")
                    .With(p => p.DateOfBirth, new DateTime(1900, 1, 1)));
    }
}
Run Code Online (Sandbox Code Playgroud)

作为一般规则,保持您的自定义设置较小且专注,使您可以在不同的测试中重用它们,并将它们组合到特定的测试场景中.

  • 不是'AllPersonsAreNamedBen`要[覆盖](http://blog.ploeh.dk/2012/07/31/TheorderofAutoFixtureCustomizationsmatter)`AllPersonsAreBornIn1900`? (3认同)
  • 我纠正了。由于这两种定制都适用于同一类型,因此它们绝对会。感谢您的呼唤!这是 AutoFixture 时不时让我困扰的一个方面——我希望我可以像这样逐步组合多个自定义,如此“糟糕”,以至于我有时会忘记这是不可能的。 (2认同)

Dav*_*rne 10

这看起来像是设计:

请注意,Build方法链最好被理解为一次性Customization.它绕过Fixture实例上的所有自定义.相反,它可以在构建特定样本时进行细粒度控制.但是,在大多数情况下,添加基于约定的ICustomization选项是更好,更灵活的选项.

...来自Build()方法的文档.

我很欣赏这可能不是一个理想的答案.但是,文档确实提供了关于如何到达一个文档的提示.