使用AutoFixture创建递归树

我刚刚开始使用AutoFixture并拥有这个半复杂的数据结构,我想创建一些标本。 在我正在进行的测试中,我不太关心数据结构的内容。 我只想要合理的默认值。

此数据结构的一部分是递归树。 更具体地说,一个类包含一些其他类的集合,其中包含自身的子列表。 类似于:

public class A { private IEnumerable bNodes; public A(IEnumerable bNodes) { this.bNodes = bNodes; } } public class B { private IEnumerable children; public B(IEnumerable children) { this.children = children; } } 

让我们假设我不能因各种原因轻易改变这种结构。

如果我要求我的夹具创建一个ThrowingRecursionBehavior将开始咆哮B是递归的。

如果我用OmitOnRecursionBehavior替换ThrowingRecursionBehavior,我会得到一个ObjectCreateException。

如果我尝试类似:fixture.Inject(Enumerable.Empty()); 我从DictionaryFiller获得“已添加相同键的项目”。 如果我用NullRecursionBehavior替换ThrowingRecursionBehavior,也会发生同样的事情。

我想要几件事。

  • 用空的B列表创建A样本的最佳方法是什么?
  • 创建一个A标本的最佳方法是什么,其中包含一些含有少数几个孩子(一棵小树)的B孩子的B?

对于我的遗愿,指定一些递归深度可能会很好,之后使用Enumerable.Empty(或零大小的数组/ List或甚至为null)。 我知道AutoFixture可以非常灵活地扩展。 因此,我认为应该可以创建一些完全符合这一要求的样本构建器。 事实上,我会尝试使用自定义的ISpecimenBuilder,但也许有人已经拥有了一个更智能的解决方案。 例如,在RecursionGuard中修改此行是否有意义:

 public object Create(object request, ISpecimenContext context) { if (this.monitoredRequests.Any(x => this.comparer.Equals(x, request))) ... 

 public object Create(object request, ISpecimenContext context) { if (this.monitoredRequests.Count(x => this.comparer.Equals(x, request)) > maxAllowedRecursions) ... 

使用空的B列表创建A.

使用空的B列表创建A的实例很容易:

 var fixture = new Fixture(); fixture.Inject(Enumerable.Empty()); var a = fixture.Create(); 

创建一个小树

创建一棵小树要困难得多,但这是可能的。 你已经开始考虑RecursionGuard 。 为了validation这是否可行,我从RecursionGuard复制了大部分代码并创建了这个DepthRecursionGuard作为概念certificate

 public class DepthRecursionGuard : ISpecimenBuilderNode { private readonly ISpecimenBuilder builder; private readonly Stack monitoredRequests; public DepthRecursionGuard(ISpecimenBuilder builder) { if (builder == null) { throw new ArgumentNullException("builder"); } this.monitoredRequests = new Stack(); this.builder = builder; } public object Create(object request, ISpecimenContext context) { if (this.monitoredRequests.Count(request.Equals) > 1) return this.HandleRecursiveRequest(request); this.monitoredRequests.Push(request); var specimen = this.builder.Create(request, context); this.monitoredRequests.Pop(); return specimen; } private object HandleRecursiveRequest(object request) { if (typeof(IEnumerable).Equals(request)) return Enumerable.Empty(); throw new InvalidOperationException("boo hiss!"); } public ISpecimenBuilderNode Compose(IEnumerable builders) { var builder = ComposeIfMultiple(builders); return new DepthRecursionGuard(builder); } public virtual IEnumerator GetEnumerator() { yield return this.builder; } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } private static ISpecimenBuilder ComposeIfMultiple( IEnumerable builders) { var isSingle = builders.Take(2).Count() == 1; if (isSingle) return builders.Single(); return new CompositeSpecimenBuilder(builders); } } 

请注意Create方法的更改实现,以及HandleRecursiveRequestIEnumerable的特定处理。

为了使其可以从Fixture实例中使用,我还添加了这个DepthRecursionBehavior

 public class DepthRecursionBehavior : ISpecimenBuilderTransformation { public ISpecimenBuilder Transform(ISpecimenBuilder builder) { return new DepthRecursionGuard(builder); } } 

这使我能够创建一个小树:

 var fixture = new Fixture(); fixture.Behaviors.OfType() .ToList().ForEach(b => fixture.Behaviors.Remove(b)); fixture.Behaviors.Add(new DepthRecursionBehavior()); var a = fixture.Create(); 

虽然这是可能的,但在我看来,这太难了,所以我创建了一个工作项 ,以便将来更容易。


更新2013.11.13:从AutoFixture 3.13.0开始,可以通过该API配置递归深度。