14

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

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

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

public class B
{
   private IEnumerable<B> children;
   public B(IEnumerable<B> children)
   {
      this.children = children;
   }
}

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

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

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

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

我想做几件事。

  • 用空的 B 列表创建 A 样本的最佳方法是什么?
  • 创建一个包含几个 B 和几个孩子的 B 孩子(一棵小树)的 A 样本的最佳方法是什么?

对于我的最后一个愿望,最好指定一些递归深度,然后使用 Enumerable.Empty (或零大小的数组/列表,甚至为空)。我知道 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)
   ...
4

1 回答 1

12

用一个空的 B 列表创建 A

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

var fixture = new Fixture();
fixture.Inject(Enumerable.Empty<B>());

var a = fixture.Create<A>();

创建一棵小树

创建一棵小树要困难得多,但这是可能的。你的想法已经步入正轨RecursionGuard。为了验证这是否可行,我复制了大部分代码RecursionGuard并将其创建DepthRecursionGuard概念证明

public class DepthRecursionGuard : ISpecimenBuilderNode
{
    private readonly ISpecimenBuilder builder;
    private readonly Stack<object> monitoredRequests;

    public DepthRecursionGuard(ISpecimenBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException("builder");
        }

        this.monitoredRequests = new Stack<object>();
        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<B>).Equals(request))
            return Enumerable.Empty<B>();

        throw new InvalidOperationException("boo hiss!");
    }

    public ISpecimenBuilderNode Compose(IEnumerable<ISpecimenBuilder> builders)
    {
        var builder = ComposeIfMultiple(builders);
        return new DepthRecursionGuard(builder);
    }

    public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
    {
        yield return this.builder;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    private static ISpecimenBuilder ComposeIfMultiple(
        IEnumerable<ISpecimenBuilder> builders)
    {
        var isSingle = builders.Take(2).Count() == 1;
        if (isSingle)
            return builders.Single();

        return new CompositeSpecimenBuilder(builders);
    }
}

注意方法的改变实现Create,以及IEnumerable<B>in的具体处理HandleRecursiveRequest

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

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

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

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

var a = fixture.Create<A>();

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


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

于 2013-06-18T12:25:14.030 回答