为什么自定义 returns 应用于两个不同的属性时是同一个实例?
Why a customization returns the same instance when applied on two different properties?
我在 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 个不同的字符串。
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)
方法每个 fixture 仅 运行s 一次。请注意,With
的参数由一个 lambda 表达式和一个对象组成。第二个参数只是一个对象,因为 C# 是急切求值的,即使你在那里放置一个方法调用,该方法调用也会在调用 With
之前求值。
然后,此自定义的作用是创建一个空的字符串列表(或者,如果您使用 CreateMany
,填充的字符串列表),并注册 Content
这样,每个创建 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 不会' t 覆盖 Do
块的效果。
这两个测试都通过了。
综上所述,您应该 avoid writable collection properties:
DO NOT provide settable collection properties.
如果您遵循 Microsoft 官方设计指南,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 个不同的字符串。
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)
方法每个 fixture 仅 运行s 一次。请注意,With
的参数由一个 lambda 表达式和一个对象组成。第二个参数只是一个对象,因为 C# 是急切求值的,即使你在那里放置一个方法调用,该方法调用也会在调用 With
之前求值。
然后,此自定义的作用是创建一个空的字符串列表(或者,如果您使用 CreateMany
,填充的字符串列表),并注册 Content
这样,每个创建 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 不会' t 覆盖 Do
块的效果。
这两个测试都通过了。
综上所述,您应该 avoid writable collection properties:
DO NOT provide settable collection properties.
如果您遵循 Microsoft 官方设计指南,AutoFixture 往往更适合您。