Создание рекурсивного дерева с помощью AutoFixture
Я только начал использовать 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 рекурсивен.
Если я заменяю ThrowingRecursionBehavior на OmitOnRecursionBehavior, я получаю исключение ObjectCreateException.
Если я попробую что-то вроде: fixture.Inject(Enumerable.Empty()); Я получаю "Элемент с таким же ключом уже был добавлен" из DictionaryFiller. То же самое происходит, если я заменю ThrowingRecursionBehavior на NullRecursionBehavior.
Есть несколько вещей, которые я бы хотел.
- Каков наилучший способ создать образец A с пустым списком Bs?
- Каков наилучший способ создать образец 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)
...
1 ответ
Создание A с пустым списком Bs
Создать экземпляр A с пустым списком B легко:
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>
в 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.