使用AutoFixture为递归数据结构创建夹具

我正在开发一个项目,我有一些递归数据结构,我想为它创建一个夹具。

数据结构是XmlCommandElement ,它有一个方法ToCommandXmlCommandElement转换为Command

树上的每个节点都可以是XmlCommandElement和/或XmlCommandPropertyElement

现在,为了测试ToCommand方法的行为,我想用一些任意数据获取XmlCommandElement

我想控制每个节点的树的深度和XmlCommandElement和/或XmlCommandPropertyElement的实例数量。

所以这是我用于灯具的代码:

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

这就是我使用它的方式:

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

这完全没问题! 但我遇到的问题是它不是通用的 ,至少据我所知,AutoFixture的重点在于避免制作特定的灯具。

所以我真正想要做的就是这样(在这里找到它,但它对我不起作用。):

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

以下是所有参考代码:

接口:

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

类别:

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

最后,我要测试的课程如下:

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

测试本身看起来像这样:

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

感谢Mark Seemann! 最终解决方案如下所示:

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

并且可以像这样使用:

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

您可以通过更改Fixture的递归行为轻松地创建一个小树:

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

上述测试通过。