我在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个不同的字符串。
答案 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往往更适合您。