使用 AutoFixture 创建递归树 [英] Creating recursive tree with AutoFixture

查看:22
本文介绍了使用 AutoFixture 创建递归树的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have just started using AutoFixture and have this semi-complex data structure that I would like to create some specimen for. In the tests I am working with I don't care too much about content of the data structure. I just want reasonable default values.

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

Part of this data structure is a recursive tree. More specific, one class holds a collection of some other class that contains a list of children of itself. Something akin to:

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;
   }
}

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

Lets assume I cannot easily change this structure for various reasons.

如果我让我的装置创建 A ThrowingRecursionBehavior 将开始吠叫 B 是递归的.

If I ask my fixture to create A ThrowingRecursionBehavior will start barking about B being recursive.

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

If I replace ThrowingRecursionBehavior with OmitOnRecursionBehavior I get an ObjectCreateException.

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

If I try something like: fixture.Inject(Enumerable.Empty()); I get "An item with the same key has already been added" from the DictionaryFiller. The same thing happens if I replace ThrowingRecursionBehavior with NullRecursionBehavior.

有几件事我想要.

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

对于我的最后一个愿望,在使用 Enumerable.Empty(或零大小的数组/列表或什至 null)之后指定一些递归深度可能会很好.我知道 AutoFixture 的扩展非常灵活.所以我想应该可以创建一些完全做到这一点的样本构建器.事实上,我会尝试使用自定义的 ISpecimenBuilder,但也许有人已经有了更聪明的解决方案.例如,在 RecursionGuard 中修改这一行是否有意义:

For my last wish it could be nice to specify some recursion depth after which Enumerable.Empty was used (or a zero sized array / List or even null). I know that AutoFixture is very flexible to extend. So I suppose it should be possible to create some specimen builder that does exactly this. In fact I will try fooling around with a custom ISpecimenBuilder, but perhaps someone has a smarter solution already. For example, would it make sense to modify this line in 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 的实例很容易:

It's easy to create an instance of A with an empty list of Bs:

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

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

创建一棵小树

创建一棵小树要困难得多,但它是可能的.您对 RecursionGuard 的思考已经步入正轨.为了验证这是否可行,我从 RecursionGuard 复制了大部分代码并创建了这个 DepthRecursionGuard 作为概念证明:>

It's much more difficult to create a small tree, but it's possible. You're already on track with your thinking about RecursionGuard. In order to verify if this could work, I copied most of the code from RecursionGuard and created this DepthRecursionGuard as a proof of concept:

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方法的改变实现,以及HandleRecursiveRequestIEnumerable的具体处理.

Notice the changed implementation of the Create method, as well as the specific handling of IEnumerable<B> in HandleRecursiveRequest.

为了让 Fixture 实例可用,我还添加了这个 DepthRecursionBehavior:

In order to make this usable from a Fixture instance, I also added this DepthRecursionBehavior:

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

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

This enabled me to create a small tree:

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>();

虽然这是可能的,但在我看来,这太难了,所以我创建了 a工作项,以便将来更轻松.

While this is possible, it's, in my opinion, too hard, so I've created a work item to make it easier in the future.

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

Update 2013.11.13: From AutoFixture 3.13.0, the recursion depth can be configured via that API.

这篇关于使用 AutoFixture 创建递归树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆