为什么定制在应用于两个不同的属性时返回相同的实例?

时间:2018-03-07 20:02:15

标签: c# nunit autofixture

我在AutoFixture 3.50.7和4.0(Nunit 3.7)上遇到这种奇怪的行为:

当我使用vanilla Fixture对象生成包含两个相同类型属性的对象时,测试通过。

当我应用此(假设为虚拟)自定义时,测试失败。生成的列表实际上是同一个实例。

发生了什么事?

[TestFixture]
public class Test
{
    [Test]
    public void Succeeds()
    {
        var fixture = new Fixture();

        var container = fixture.Create<Container>();

        ReferenceEquals(container.Content1.Strings, container.Content2.Strings).Should().BeFalse();
    }

    [Test]
    public void Fails()
    {
        var fixture = new Fixture().Customize(new TestContentCustomization());

        var container = fixture.Create<Container>();

        ReferenceEquals(container.Content1.Strings, container.Content2.Strings).Should().BeFalse();
    }
}

public class TestContentCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Content>(c => c
            .With(x => x.Strings, new List<string>()));
    }
}

public class Container
{
    public Content Content1 { get; set; }
    public Content Content2 { get; set; }
}

public class Content
{
    public IList<string> Strings { get; set; }
}

如果我要求AutoFixture生成fixture.CreateMany<string>(3).ToList()而不是new List<string>(),测试也会失败,但当然,这次列表包含3个不同的字符串。

1 个答案:

答案 0 :(得分:1)

Customize<T> API可能令人困惑,这完全是我的错,但是当您完成自定义定义时,内部的代理只运行一次。我理解为什么使用委托看起来像延迟执行,但事实并非如此;这是一个DSL。这有时会导致混淆,这表明它应该采用不同的设计,但现在,十年后,这就是我们的API。

OP中的自定义等同于这样做:

public void Customize(IFixture fixture)
{
    var strings = new List<string>();
    fixture.Customize<Content>(c => c
        .With(x => x.Strings, strings));
}

Customize(IFixture)方法每个灯具只运行一次。请注意,With的参数由lambda表达式和对象组成。第二个参数只是一个对象,并且由于急切地评估了C#,即使你在那里调用了一个方法,该方法调用也会在调用With之前进行评估。

这个自定义的作用是创建一个空的字符串列表(或者,如果你使用CreateMany,一个填充的字符串列表),并注册Content这样,每次a创建了Content对象,为其Strings属性分配了该特定对象。

要解决此问题,您可以执行以下操作:

public void Customize(IFixture fixture)
{
    fixture.Customize<Content>(c => c
        .Without(x => x.Strings)
        .Do(x =>
        {
            x.Strings = fixture.CreateMany<string>().ToList();
        }));
}

Do方法为您提供了真正的延迟执行,但是(IIRC)没有严格定义它执行的顺序,因此您还应该使用.Without(x => x.Strings)来确保AutoFixture不会覆盖Do阻止的影响。

这通过了两个测试。

所有这一切,你应该avoid writable collection properties

  

不要提供可设置的集合属性。

如果您遵循Microsoft的官方设计指南,AutoFixture往往更适合您。