使用AutoFixture创建用于递归数据结构的固定装置 [英] Create a fixture for recursive data structure with AutoFixture
问题描述
我正在一个有递归数据结构的项目中,我想为其创建一个固定装置.
数据结构为 XmlCommandElement
,它具有单个方法 ToCommand
,该方法将 XmlCommandElement
转换为 Command
./p>
树上的每个节点可以是 XmlCommandElement
和/或 XmlCommandPropertyElement
.
现在,为了测试方法 ToCommand
的行为,我想获取一些任意数据的 XmlCommandElement
.
我想控制每个节点的树的深度和 XmlCommandElement
和/或 XmlCommandPropertyElement
的实例数量.
这是我用于灯具的代码:
公共类XmlCommandElementFixture:ICustomization{私有静态只读Fixture _fixture = new Fixture();私人XmlCommandElement _xmlCommandElement;public int MaxCommandsPerDepth {get;放;}public int MaxDepth {get;放;}public int MaxPropertiesPerCommand {get;放;}公共XmlCommandElementFixture BuildCommandTree(){_xmlCommandElement =新的XmlCommandElement();var tree = new Stack< XmlCommandElementNode>();tree.Push(new XmlCommandElementNode(0,_xmlCommandElement));而(tree.Count> 0){var node = tree.Pop();node.Command.Key = CreateRandomString();node.Command.Properties = CreateProperties();如果(MaxDepth> node.Depth){var命令=新的List< XmlCommandElement>();对于(var i = 0; i< MaxCommandsPerDepth; i ++){var command = new XmlCommandElement();tree.Push(新的XmlCommandElementNode(node.Depth + 1,命令));命令.添加(命令);}node.Command.Commands =命令.ToArray();}}返回这个;}公共无效的自定义(IFixture固定装置){Fixture.Customize< XmlCommandElement>(c => c.FromFactory(()=> _xmlCommandElement).OmitAutoProperties());}私有静态字符串CreateRandomString(){return _fixture.Create< Generator< string>>().First();}私人XmlCommandPropertyElement [] CreateProperties(){var properties = new List< XmlCommandPropertyElement>();for(var i = 0; i< MaxPropertiesPerCommand; i ++){properties.Add(new XmlCommandPropertyElement {键= CreateRandomString(),值= CreateRandomString()});}返回properties.ToArray();}私有结构XmlCommandElementNode{公共XmlCommandElementNode(int深度,XmlCommandElement xmlCommandElement){深度=深度;命令= xmlCommandElement;}公共XmlCommandElement命令{}public int Depth {get;}}}
这就是我的使用方式:
xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture {MaxDepth = 2MaxCommandsPerDepth = 3,MaxPropertiesPerCommand = 4} .BuildCommandTree()).Create< XmlCommandElement>();
这很好用!但是我遇到的问题是它不是 generic ,至少就我所知,AutoFixture的全部目的是避免制造特定的灯具.
所以我真正想做的就是这样(找到它 最后,我要测试的课程如下: 测试本身如下所示: 感谢Mark Seemann!最终的解决方案如下所示: 并且可以像这样使用: 您可以通过更改Fixture的递归行为来轻松创建一棵小树: 以上测试通过. I'm working on a project where I have some recursive data structure and I want to create a fixture for it. The data structure is Each node on the tree can be a Now, in order to test the behaviour of the method I want to control the depth of the tree and the amount of instances of So here is the code I'm using for the fixture: And this is how I'm using it: This works perfectly fine! but the issue I have with it is it isn't generic, the whole point of AutoFixture at least as far as I know is to avoid making specific fixtures. So what I would really like to do is something like this (found it here but it doesn't work for me.): Here is all the code for reference: Interfaces: Classes: Finally, the class I'm trying to test is as follow: The test itself looks like this: Thanks to Mark Seemann! the final solution looks like this: And can be used like this:
You can fairly easily create a small tree by changing the Fixture's recursion behaviour: The above test passes. 这篇关于使用AutoFixture创建用于递归数据结构的固定装置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
公共类XmlCommandElement:ICommandConvertible{[XmlArray][XmlArrayItem("Command",typeof(XmlCommandElement))]XmlCommandElement []公共命令{get;放;}[XmlAttribute("key")]公共字符串Key {get;放;}[XmlArray][XmlArrayItem("Property",typeof(XmlCommandPropertyElement))]公共XmlCommandPropertyElement []属性{放;}公共ICommand ToCommand(){ICommandPropertyCollection属性=新的CommandPropertyCollection();foreach(属性"中的var属性){properties.Add(property.ToCommandProperty());}ICommand command = new Command(Key,properties);返回命令;}}
命名空间Yalla.Tests.Commands{使用夹具;使用FluentAssertions;使用Ploeh.AutoFixture;使用Xbehave;使用Yalla.Commands;使用Yalla.Commands.Xml;公共类XmlCommandElementTests{[设想]公共无效的ConvertToCommand(XmlCommandElement xmlCommandElement,ICommand命令){$给出一个{nameof(XmlCommandElement)}".x(()=>{xmlCommandElement = new Fixture().Customize(new XmlCommandElementFixture {MaxDepth = 2MaxCommandsPerDepth = 3,MaxPropertiesPerCommand = 4} .BuildCommandTree()).Create< XmlCommandElement>();});$当对象转换为{nameof(ICommand)}时".x(()=>命令= xmlCommandElement.ToCommand());那么我们需要一个带有钥匙的根对象".x(()=> command.Key.Should().NotBeNullOrEmpty());和4个属性作为其子代".x(()=> command.Properties.Should().HaveCount(4));}}}
公共类RecursiveCustomization:ICustomization{public int MaxDepth {get;放;}公共诠释MaxElements {get;放;}公共无效的自定义(IFixture固定装置){夹具.行为.OfType< ThrowingRecursionBehavior>().ToList().ForEach(b =>夹具.Behaviors.Remove(b));Fixture.Behaviors.Add(new OmitOnRecursionBehavior(MaxDepth));Fixture.RepeatCount = MaxElements;}}
xmlCommandElement = new Fixture().Customize(new RecursiveCustomization {MaxDepth = 2最大元素= 3}).Create< XmlCommandElement>();
[事实]公共无效CreateSmallTree(){var fixture = new Fixture();夹具.行为.OfType< ThrowingRecursionBehavior>().ToList().ForEach(b =>夹具.Behaviors.Remove(b));Fixture.Behaviors.Add(new OmitOnRecursionBehavior(recursionDepth:2));var xce = Fixture.Create< XmlCommandElement>();Assert.NotEmpty(xce.Commands);}
XmlCommandElement
, it has a single method ToCommand
that converts XmlCommandElement
to Command
.XmlCommandElement
and/or XmlCommandPropertyElement
.ToCommand
I want to fetch XmlCommandElement
with some arbitrary data.XmlCommandElement
and/or XmlCommandPropertyElement
per node.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<XmlCommandElementNode>();
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<XmlCommandElement>();
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<XmlCommandElement>(c => c.FromFactory(() => _xmlCommandElement)
.OmitAutoProperties());
}
private static string CreateRandomString()
{
return _fixture.Create<Generator<string>>().First();
}
private XmlCommandPropertyElement[] CreateProperties()
{
var properties = new List<XmlCommandPropertyElement>();
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<XmlCommandElement>();
var fixture = new Fixture();
fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
.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<XmlCommandElement>();
public interface ICommandCollection : IEnumerable<ICommand>
{
ICommand this[string commandName] { get; }
void Add(ICommand command);
}
public interface ICommandPropertyCollection : IEnumerable<ICommandProperty>
{
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<string, ICommandProperty> _properties;
public CommandPropertyCollection()
{
_properties = new ConcurrentDictionary<string, ICommandProperty>();
}
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<ICommandProperty> 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<XmlCommandElement>();
});
$"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));
}
}
}
public class RecursiveCustomization : ICustomization
{
public int MaxDepth { get; set; }
public int MaxElements { get; set; }
public void Customize(IFixture fixture)
{
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.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<XmlCommandElement>();
[Fact]
public void CreateSmallTree()
{
var fixture = new Fixture();
fixture.Behaviors
.OfType<ThrowingRecursionBehavior>()
.ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior(recursionDepth: 2));
var xce = fixture.Create<XmlCommandElement>();
Assert.NotEmpty(xce.Commands);
}