Cree un accesorio para la estructura de datos recursiva con AutoFixture

Estoy trabajando en un proyecto donde tengo una estructura de datos recursiva y quiero crear un elemento para él.

La estructura de datos es XmlCommandElement , tiene un solo método ToCommand que convierte XmlCommandElement en Command .

Cada nodo en el árbol puede ser un XmlCommandElement y / o XmlCommandPropertyElement .

Ahora, para probar el comportamiento del método ToCommand , deseo obtener XmlCommandElement con algunos datos arbitrarios.

Quiero controlar la profundidad del árbol y la cantidad de instancias de XmlCommandElement y / o XmlCommandPropertyElement por nodo.

Así que aquí está el código que estoy usando para el accesorio:

 public class XmlCommandElementFixture : ICustomization { private static readonly Fixture _fixture = new Fixture(); private XmlCommandElement _xmlCommandElement; public int MaxCommandsPerDepth { get; set; } public int MaxDepth { get; set; } public int MaxPropertiesPerCommand { get; set; } public XmlCommandElementFixture BuildCommandTree() { _xmlCommandElement = new XmlCommandElement(); var tree = new Stack(); tree.Push(new XmlCommandElementNode(0, _xmlCommandElement)); while (tree.Count > 0) { var node = tree.Pop(); node.Command.Key = CreateRandomString(); node.Command.Properties = CreateProperties(); if (MaxDepth > node.Depth) { var commands = new List(); for (var i = 0; i < MaxCommandsPerDepth; i++) { var command = new XmlCommandElement(); tree.Push(new XmlCommandElementNode(node.Depth + 1, command)); commands.Add(command); } node.Command.Commands = commands.ToArray(); } } return this; } public void Customize(IFixture fixture) { fixture.Customize(c => c.FromFactory(() => _xmlCommandElement) .OmitAutoProperties()); } private static string CreateRandomString() { return _fixture.Create<Generator>().First(); } private XmlCommandPropertyElement[] CreateProperties() { var properties = new List(); for (var i = 0; i < MaxPropertiesPerCommand; i++) { properties.Add(new XmlCommandPropertyElement { Key = CreateRandomString(), Value = CreateRandomString() }); } return properties.ToArray(); } private struct XmlCommandElementNode { public XmlCommandElementNode(int depth, XmlCommandElement xmlCommandElement) { Depth = depth; Command = xmlCommandElement; } public XmlCommandElement Command { get; } public int Depth { get; } } } 

Y así es como lo estoy usando:

 xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture { MaxDepth = 2, MaxCommandsPerDepth = 3, MaxPropertiesPerCommand = 4 }.BuildCommandTree()).Create(); 

¡Esto funciona perfectamente bien! pero el problema que tengo con esto es que no es genérico , todo el punto de AutoFixture, al menos por lo que sé, es evitar hacer accesorios específicos.

Entonces, lo que realmente me gustaría hacer es algo como esto (lo encontré aquí pero no funciona para mí):

 var fixture = new Fixture(); fixture.Behaviors.OfType() .ToList() .ForEach(b => fixture.Behaviors.Remove(b)); fixture.Behaviors.Add(new DepthThrowingRecursionBehavior(2)); fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(XmlCommandElement), 3)); fixture.Behaviors.Add(new OmitOnRecursionForRequestBehavior(typeof(XmlCommandPropertyElement), 4)); xmlCommandElement = fixture.Create(); 

Aquí está todo el código de referencia:

Interfaces:

 public interface ICommandCollection : IEnumerable { ICommand this[string commandName] { get; } void Add(ICommand command); } public interface ICommandPropertyCollection : IEnumerable { string this[string key] { get; } void Add(ICommandProperty property); } public interface ICommandProperty { string Key { get; } string Value { get; } } public interface ICommand { ICommandCollection Children { get; set; } string Key { get; } ICommandPropertyCollection Properties { get; } } public interface ICommandConvertible { ICommand ToCommand(); } 

Clases:

 public sealed class CommandPropertyCollection : ICommandPropertyCollection { private readonly IDictionary _properties; public CommandPropertyCollection() { _properties = new ConcurrentDictionary(); } public string this[string key] { get { ICommandProperty property = null; _properties.TryGetValue(key, out property); return property.Value; } } public void Add(ICommandProperty property) { _properties.Add(property.Key, property); } public IEnumerator GetEnumerator() { return _properties.Values.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } public sealed class CommandProperty : ICommandProperty { public CommandProperty(string key, string value) { Key = key; Value = value; } public string Key { get; } public string Value { get; } } public sealed class Command : ICommand { public Command(string key, ICommandPropertyCollection properties) { Key = key; Properties = properties; } public ICommandCollection Children { get; set; } public string Key { get; } public ICommandPropertyCollection Properties { get; } } public class XmlCommandPropertyElement : ICommandPropertyConvertible { [XmlAttribute("key")] public string Key { get; set; } [XmlAttribute("value")] public string Value { get; set; } public ICommandProperty ToCommandProperty() { return new CommandProperty(Key, Value); } } 

Finalmente, la clase que estoy tratando de probar es la siguiente:

 public class XmlCommandElement : ICommandConvertible { [XmlArray] [XmlArrayItem("Command", typeof(XmlCommandElement))] public XmlCommandElement[] Commands { get; set; } [XmlAttribute("key")] public string Key { get; set; } [XmlArray] [XmlArrayItem("Property", typeof(XmlCommandPropertyElement))] public XmlCommandPropertyElement[] Properties { get; set; } public ICommand ToCommand() { ICommandPropertyCollection properties = new CommandPropertyCollection(); foreach (var property in Properties) { properties.Add(property.ToCommandProperty()); } ICommand command = new Command(Key, properties); return command; } } 

La prueba en sí se parece a esto:

 namespace Yalla.Tests.Commands { using Fixtures; using FluentAssertions; using Ploeh.AutoFixture; using Xbehave; using Yalla.Commands; using Yalla.Commands.Xml; public class XmlCommandElementTests { [Scenario] public void ConvertToCommand(XmlCommandElement xmlCommandElement, ICommand command) { $"Given an {nameof(XmlCommandElement)}" .x(() => { xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture { MaxDepth = 2, MaxCommandsPerDepth = 3, MaxPropertiesPerCommand = 4 }.BuildCommandTree()).Create(); }); $"When the object is converted into {nameof(ICommand)}" .x(() => command = xmlCommandElement.ToCommand()); "Then we need to have a root object with a key" .x(() => command.Key.Should().NotBeNullOrEmpty()); "And 4 properties as its children" .x(() => command.Properties.Should().HaveCount(4)); } } } 

Gracias a Mark Seemann! La solución final se ve así:

 public class RecursiveCustomization : ICustomization { public int MaxDepth { get; set; } public int MaxElements { get; set; } public void Customize(IFixture fixture) { fixture.Behaviors .OfType() .ToList() .ForEach(b => fixture.Behaviors.Remove(b)); fixture.Behaviors.Add(new OmitOnRecursionBehavior(MaxDepth)); fixture.RepeatCount = MaxElements; } } 

Y se puede utilizar de esta manera:

 xmlCommandElement = new Fixture().Customize(new RecursiveCustomization { MaxDepth = 2, MaxElements = 3 }).Create(); 

Puedes crear un árbol pequeño con bastante facilidad cambiando el comportamiento de recursión del Accesorio:

 [Fact] public void CreateSmallTree() { var fixture = new Fixture(); fixture.Behaviors .OfType() .ToList() .ForEach(b => fixture.Behaviors.Remove(b)); fixture.Behaviors.Add(new OmitOnRecursionBehavior(recursionDepth: 2)); var xce = fixture.Create(); Assert.NotEmpty(xce.Commands); } 

La prueba anterior pasa.