是否可以组合 Autofixture 定制(通过 NUnit3 属性)?如果可以,如何组合?
Can Autofixture customizations (via NUnit3 attributes) be composed and if so - how?
我正在进行一些测试,我想在其中编写一些封装在 Autofixture 自定义中的对象设置逻辑 类。这是我所拥有的相关部分:
public class UseSpecificConstructorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<MyObject>(c => c.FromFactory((string s) => new MyObject(s)));
}
}
public class ExecuteMethodCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<MyObject>(c => c.Do(x => x.AMethodIWantToExecute()));
}
}
对于这些定制中的每一个,我都有相应的 CusomizeAttribute
实现,其中 returns 定制实例。现在在我的测试中(NUnit3.x)我想做这样的事情:
[Test,AutoData]
public void ThisIsMyTestMethod([UseSpecificConstructor,ExecuteMethod] MyObject obj)
{
// Here I would like the object to have been created using the specified
// string constructor AND the method 'AMethodIWantToExecute' to have been executed.
}
不过,我看到的行为表明,在这种情况下,只使用了两种自定义设置中的一种 - 似乎是最后指定的那个。这在某种程度上是有道理的,因为 Autofixture 是关于 "creating" 对象的,而不是在创建后修改它们。将两个自定义项(在内部对应于样本生成器)用于创建同一对象没有意义。
有没有办法使用 Autofixture 来实现我所描述的?我想我正在寻找的是一种类似于定制的机制,它不使用样本生成器,而是公开一个 "specimen customizer",以 post-在样本生成器完成其后处理对象工作。这种机制只能设置属性并使用 .Do()
(不能使用 .FromFactory()
等)。如果它不存在,那么我可能会去请求它的功能,但也许它确实存在,但我没有看到它。
我意识到我也可以将这个 "object configuration after creation" 逻辑移到测试本身中,但它不止几行那么长,我不想重复它。此外 - 设置逻辑与测试并不特别相关,它只是让对象进入基本有效状态。 "Cognitively speaking" 该逻辑属于对象创建逻辑; IE:开发人员不想考虑它。
围绕我自己的源代码进行了更多research/poking,我想我找到了答案。解决方案在于 Fixture behaviors.
行为是 decorator objects around ISpecimenBuilder
and so they can post-process the result from a specimen builder and perform extra work. Behaviors implement the interface ISpecimenBuilderTransformation
. It seems that at most one specimen builder can fulfil a request for Autofixture to create an object (following chain of responsibility 模式),但 可以在此之上应用无限数量的 行为,执行各种任务,例如修改之前的结果它被退回了。
行为是 added/removed to/from 通过 fixture.Behaviors
的夹具。
2021 年 4 月更新:此技术的一个示例
我现在已经在一个开源项目中使用了这种技术 can link to that as an example。请注意,链接的项目正在使用 XUnit,但您会看到属性和语法与 NUnit3 Autofixture 集成相同。
下面列出了相关部分。
建议:将您的属性命名为“WithXyz”
CustomizeAttribute
的实施使用标本生成器转换(也称为“行为”)实际上并没有创建标本,而是对其进行了修改。因此,根据属性的修改方式而不是创建方式来命名属性。
创建一个post-加工样本命令class
Autofixture 提供了一个名为ISpecimenCommand
的接口,用于对样本执行post-处理。创建一个实现此接口并包装委托的小 class。我发现使该命令通用化很有用且方便:
public class PostprocessingCommand<T> : ISpecimenCommand where T : class
{
readonly Action<T,ISpecimenContext> builderCustomizer;
public void Execute(object specimen, ISpecimenContext context)
{
var builder = (T) specimen;
builderCustomizer(builder, context);
}
public PostprocessingCommand(Action<T,ISpecimenContext> builderCustomizer)
{
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
}
}
创建仅匹配正确类型的规范
Autofixture 再次有一个名为 IRequestSpecification
的接口,用于匹配样本请求。目的是确定是否应在所得样本上使用给定的 post 处理操作。
了解规范 匹配标本请求而不是创建的标本本身 非常重要。样本请求通常是 System.Type
.
的实例
public class IsInstanceOf : IRequestSpecification
{
readonly Type type;
public bool IsSatisfiedBy(object request) => request != null && type.IsAssignableFrom(request);
public IsInstanceOf(Type type)
{
this.type = type ?? throw new ArgumentNullException(nameof(type));
}
}
您的标本生成器转换将这些结合在一起
样本生成器转换(又名 行为)class 将样本命令和请求规范结合在一起,并使用 Autofixture 的内置 Postprocessor
class。我再次发现将其设为通用很方便。
public class PostprocessingTransformation<T> : ISpecimenBuilderTransformation where T : class
{
readonly Action<T,ISpecimenContext> builderCustomizer;
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
var command = new PostprocessingCommand<T>(builderCustomizer);
var spec = new IsInstanceOf(typeof(T));
return new Postprocessor(builder, command, spec);
}
public PostprocessingTransformation(Action<T,ISpecimenContext> builderCustomizer)
{
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
}
}
编写自定义以使用委托创建行为实例
一个小的自定义 class 将用于实例化行为实例并将其传递给 Action<T,ISpecimenContext>
,后者将对适当的样本执行实际的 post 处理。
它是此自定义的一个实例,应从您的 CustomizeAttribute
实施中返回。
public class MySpecificCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
var behavior = new PostprocessingTransformation<MySpecificType>((specimen, context) => {
// Write your logic for modifying an instance of MySpecificType
// here. It will be the parameter named 'specimen'.
// You may also use 'context' to access other Autofixture features.
});
fixture.Behaviors.Add(behavior);
}
}
我正在进行一些测试,我想在其中编写一些封装在 Autofixture 自定义中的对象设置逻辑 类。这是我所拥有的相关部分:
public class UseSpecificConstructorCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<MyObject>(c => c.FromFactory((string s) => new MyObject(s)));
}
}
public class ExecuteMethodCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<MyObject>(c => c.Do(x => x.AMethodIWantToExecute()));
}
}
对于这些定制中的每一个,我都有相应的 CusomizeAttribute
实现,其中 returns 定制实例。现在在我的测试中(NUnit3.x)我想做这样的事情:
[Test,AutoData]
public void ThisIsMyTestMethod([UseSpecificConstructor,ExecuteMethod] MyObject obj)
{
// Here I would like the object to have been created using the specified
// string constructor AND the method 'AMethodIWantToExecute' to have been executed.
}
不过,我看到的行为表明,在这种情况下,只使用了两种自定义设置中的一种 - 似乎是最后指定的那个。这在某种程度上是有道理的,因为 Autofixture 是关于 "creating" 对象的,而不是在创建后修改它们。将两个自定义项(在内部对应于样本生成器)用于创建同一对象没有意义。
有没有办法使用 Autofixture 来实现我所描述的?我想我正在寻找的是一种类似于定制的机制,它不使用样本生成器,而是公开一个 "specimen customizer",以 post-在样本生成器完成其后处理对象工作。这种机制只能设置属性并使用 .Do()
(不能使用 .FromFactory()
等)。如果它不存在,那么我可能会去请求它的功能,但也许它确实存在,但我没有看到它。
我意识到我也可以将这个 "object configuration after creation" 逻辑移到测试本身中,但它不止几行那么长,我不想重复它。此外 - 设置逻辑与测试并不特别相关,它只是让对象进入基本有效状态。 "Cognitively speaking" 该逻辑属于对象创建逻辑; IE:开发人员不想考虑它。
围绕我自己的源代码进行了更多research/poking,我想我找到了答案。解决方案在于 Fixture behaviors.
行为是 decorator objects around ISpecimenBuilder
and so they can post-process the result from a specimen builder and perform extra work. Behaviors implement the interface ISpecimenBuilderTransformation
. It seems that at most one specimen builder can fulfil a request for Autofixture to create an object (following chain of responsibility 模式),但 可以在此之上应用无限数量的 行为,执行各种任务,例如修改之前的结果它被退回了。
行为是 added/removed to/from 通过 fixture.Behaviors
的夹具。
2021 年 4 月更新:此技术的一个示例
我现在已经在一个开源项目中使用了这种技术 can link to that as an example。请注意,链接的项目正在使用 XUnit,但您会看到属性和语法与 NUnit3 Autofixture 集成相同。
下面列出了相关部分。
建议:将您的属性命名为“WithXyz”
CustomizeAttribute
的实施使用标本生成器转换(也称为“行为”)实际上并没有创建标本,而是对其进行了修改。因此,根据属性的修改方式而不是创建方式来命名属性。
创建一个post-加工样本命令class
Autofixture 提供了一个名为ISpecimenCommand
的接口,用于对样本执行post-处理。创建一个实现此接口并包装委托的小 class。我发现使该命令通用化很有用且方便:
public class PostprocessingCommand<T> : ISpecimenCommand where T : class
{
readonly Action<T,ISpecimenContext> builderCustomizer;
public void Execute(object specimen, ISpecimenContext context)
{
var builder = (T) specimen;
builderCustomizer(builder, context);
}
public PostprocessingCommand(Action<T,ISpecimenContext> builderCustomizer)
{
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
}
}
创建仅匹配正确类型的规范
Autofixture 再次有一个名为 IRequestSpecification
的接口,用于匹配样本请求。目的是确定是否应在所得样本上使用给定的 post 处理操作。
了解规范 匹配标本请求而不是创建的标本本身 非常重要。样本请求通常是 System.Type
.
public class IsInstanceOf : IRequestSpecification
{
readonly Type type;
public bool IsSatisfiedBy(object request) => request != null && type.IsAssignableFrom(request);
public IsInstanceOf(Type type)
{
this.type = type ?? throw new ArgumentNullException(nameof(type));
}
}
您的标本生成器转换将这些结合在一起
样本生成器转换(又名 行为)class 将样本命令和请求规范结合在一起,并使用 Autofixture 的内置 Postprocessor
class。我再次发现将其设为通用很方便。
public class PostprocessingTransformation<T> : ISpecimenBuilderTransformation where T : class
{
readonly Action<T,ISpecimenContext> builderCustomizer;
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
var command = new PostprocessingCommand<T>(builderCustomizer);
var spec = new IsInstanceOf(typeof(T));
return new Postprocessor(builder, command, spec);
}
public PostprocessingTransformation(Action<T,ISpecimenContext> builderCustomizer)
{
this.builderCustomizer = builderCustomizer ?? throw new ArgumentNullException(nameof(builderCustomizer));
}
}
编写自定义以使用委托创建行为实例
一个小的自定义 class 将用于实例化行为实例并将其传递给 Action<T,ISpecimenContext>
,后者将对适当的样本执行实际的 post 处理。
它是此自定义的一个实例,应从您的 CustomizeAttribute
实施中返回。
public class MySpecificCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
var behavior = new PostprocessingTransformation<MySpecificType>((specimen, context) => {
// Write your logic for modifying an instance of MySpecificType
// here. It will be the parameter named 'specimen'.
// You may also use 'context' to access other Autofixture features.
});
fixture.Behaviors.Add(behavior);
}
}