我正在尝试添加 ICustomization 实现,以将生成的固定装置值写入控制台,以帮助诊断夜间构建测试运行中的间歇性测试失败。
我遇到的问题是每个值都会被写入控制台两次。 不幸的是,我能找到的所有示例和文档都与特定类型的自定义处理相关 - 我想编写一些针对所有类型运行的东西,以便它记录生成的任何夹具的值。
下面是我迄今为止创建的简化版本(来自 LINQPad 的片段)。
void Main()
{
var fixture = new Fixture();
fixture.Customize(new CustomCustomisation());
fixture.Create<double>();
}
public class CustomCustomisation : ICustomization
{
public void Customize(IFixture fixture)
{
var engine = ((Fixture)fixture).Engine;
fixture.Customizations.Add(new Postprocessor(engine, new LogSpecimenCommand()));
}
}
public class LogSpecimenCommand : ISpecimenCommand
{
public void Execute(object specimen, ISpecimenContext context)
{
if (specimen == null || specimen is NoSpecimen) return;
Console.WriteLine($"SPECIMEN: {specimen}");
}
}
上面的输出是:
SPECIMEN: 213
SPECIMEN: 213
根据我对 AutoFixture 的调查,AutoFixture 在创建新样本时的工作方式是使用责任链。这意味着一般来说,需要 several 请求和 several 创建才能生成样本,并且对于每个创建都会发出 LogSpecimenCommand,因此您会获得两次跟踪。
请阅读扩展 AutoFixture 以了解我的答案的上下文。在那里,我看到 AutoFixture 提供了一个 TracingBehaviour ,它似乎涵盖了您的使用情况。
TracingBehaviour
将发出样本请求和创建的痕迹。如果配置您的灯具以使用此行为:
var fixture = new Fixture();
fixture.Behaviors.Add(new TracingBehavior());
fixture.Create<double>();
它将将此输出写入控制台:
Requested: AutoFixture.Kernel.SeededRequest
Requested: System.Double
Created: 140
Created: 140
请注意如何需要两个不同的请求,
AutoFixture.Kernel.SeededRequest
和System.Double
(及其相应的创建)来生成double
样本。另外值得注意的是,每个条目的输出缩进暗示有一种方法可以根据请求和创建在责任链中的位置对它们进行排序。
看到 TracingBehaviour 的实现以及相关类型 TracingBuilder 和 TraceWriter 后,我成功生成了一个自定义 TracingBehaviour 和 CustomTraceWriter,它们还利用 TracingBuilder
以自定义格式写入控制台。
TracingBuilder
是引发事件的样本生成器:
public event EventHandler<SpecimenCreatedEventArgs> SpecimenCreated;
每当创建样本时。通过使用自定义 ISpecimenBuilderTransformation,我们可以将 ISpecimenBuilder 转换为 ISpecimenBuilderNode。根据该接口的 remarks,这意味着特定的 ISpecimenBuilder 将不再是生成样本的类,但它将把它传递给另一个 SpecimenBuilder:
每个 AutoFixture.Kernel.ISpecimenBuilder 都有一次处理请求的机会,如果不能做到这一点,则要求下一个 ISpecimenBuilder 处理该请求。当 ISpecimenBuilder 实例返回有用的样本时,将返回该实例,并忽略来自较低优先级 SpecimenBuilder 实例的结果(如果有)。理论上,我们可以在扁平的责任链中对所有 SpecimenBuilder 实例进行排序,但 SpecimenBuilderNode 接口在更深的图中定义了“父”节点的职责。每个 ISpecimenBuilder 节点构成图中的一个中间节点,而其本身是其他节点的父节点。在退化情况下,当 ISpecimenBuilderNode 没有子节点时,它实际上成为叶节点。否则,叶节点通常是 ISpecimenBuilder 实例。考虑到这一点,创建一个
CustomTracingBehaviour
变换,它受到AutoFixture的
TracingBehaviour
的启发,但更简单,以及一个基于AutoFixture的
CustomTraceWriter
的
TraceWriter
。我们的
CustomTraceWriter
包含对从转换传递的
TracingBuilder
的引用。在事件处理程序
OnSpecimenCreated
内,您可以按照所需的格式编写跟踪。在
SpecimenCreatedEventArgs
里面,既包含了被创造的样本,也包含了创造事件的深度。经过一些快速测试后,我发现 1 是可能的最低深度,因此如果您在写入迹线之前检查深度,则可以确保为与单个样本相对应的整个请求和创建链仅创建一个迹线。
public class CustomTracingBehavior : ISpecimenBuilderTransformation
{
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new CustomTraceWriter(new TracingBuilder(builder));
}
}
public class CustomTraceWriter : ISpecimenBuilder, ISpecimenBuilderNode, IEnumerable<ISpecimenBuilder>, IEnumerable
{
public TracingBuilder Tracer { get; }
public CustomTraceWriter(TracingBuilder tracer)
{
Tracer = tracer;
Tracer.SpecimenCreated += OnSpecimenCreated;
}
private void OnSpecimenCreated(object sender, SpecimenCreatedEventArgs e)
{
if (e.Specimen == null || e.Specimen is NoSpecimen) return;
if (e.Depth > 1) return;
Console.WriteLine($"SPECIMEN: {e.Specimen}");
}
public object Create(object request, ISpecimenContext context)
{
return Tracer.Create(request, context);
}
public ISpecimenBuilderNode Compose(IEnumerable<ISpecimenBuilder> builders)
{
var compositeSpecimenBuilder = new CompositeSpecimenBuilder(builders);
ISpecimenBuilder builder = compositeSpecimenBuilder.Compose(builders);
return new CustomTraceWriter(new TracingBuilder(builder));
}
public IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return Tracer.Builder;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
然后,在实例化您的装置后添加自定义行为:
var fixture = new Fixture();
fixture.Behaviors.Add(new CustomTracingBehavior());
fixture.Create<double>();
这将产生以下输出:
SPECIMEN: 227