Autofixture 生成不重复的集合(按 ID)
Autofixture generate collection without duplicates (by ID)
我关注类:
public class Foo
{
public List<DescriptionInfo> Descriptions { get; set; }
}
public class DescriptionInfo
{
public int LanguageId { get; set; }
public string Value { get; set; }
}
我想创建 Foo 实例,使用 Autofixture。但是,LanguageId 必须来自预定义列表。因此我创建了以下定制:
public class LanguageIdSpecimenBuilder : ISpecimenBuilder
{
private static readonly List<int> LanguageIds = new List<int> {
1,
2,
666,
};
public object Create(object request, ISpecimenContext context)
{
var info = request as PropertyInfo;
if (info != null)
{
if (info.Name == "LanguageID")
{
return LanguageIds.GetRandomElement();
}
}
return new NoSpecimen(request);
}
}
现在一切都很好:
Fixture fixture = new Fixture();
fixture.Customizations.Add(new LanguageIdSpecimenBuilder());
var foo = fixture.Create<Foo>();
但是,还有一个要求:一个语言ID不能有重复条目。我怎样才能做到这一点?
编辑:
例如,有效实例是:
Foo1:
- LanguageId: 1, Value: "english description_ae154c"
- LanguageId: 2, Value: "zuzulu_510b7g"
Foo2:
- LanguageId: 1, Value: "english description_87f5de"
- LanguageId: 666, Value: "chinese_35e450"
- LanguageId: 2, Value: "zuzulu_fe830d"
无效实例:
Foo1:
- LanguageId: 1, Value: "_04dcd6"
- LanguageId: 1, Value: "_66ccc4"
- LanguageId: 2, Value: "zuzulu_c05b0f"
首先 - 让我建议您的 POC 可以更精确。如果您不允许列表中具有相同 LanguageID
的描述,您可以像这样改造您的 POC:
public class Foo
{
public ISet<DescriptionInfo> Descriptions { get; set; }
}
public class DescriptionInfo
{
public int LanguageID { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
var anotherInfo = (DescriptionInfo)obj;
return anotherInfo.LanguageID == LanguageID;
}
public override int GetHashCode()
{
return LanguageID;
}
}
问题解决了:
fixture.Customizations.Add(new TypeRelay(typeof(ISet<DescriptionInfo>),
typeof(HashSet<DescriptionInfo>)));
fixture.Customizations.Add(new LanguageIdSpecimenBuilder());
var foo = fixture.Create<Foo>();
如果您不能/不想改造您的 POC,您应该在自定义构建器中关注您想要正确设置的 属性 - Descriptions
.
public object Create(object request, ISpecimenContext context)
{
var info = request as PropertyInfo;
if (info != null && info.Name == "Descriptions" && info.DeclaringType == typeof(Foo))
{
if (info.Name == "Descriptions")
{
return context.Create<List<DescriptionInfo>>()
.GroupBy(g => g.LanguageId)
.Select(g => g.First())
.ToList();
}
}
if (info != null && info.Name == "LanguageId" && info.DeclaringType == typeof(DescriptionInfo))
{
return LanguageIds.GetRandomElement();
}
return new NoSpecimen(request);
}
最后说明 - 你的代码片段是错误的 - 字符串比较区分大小写;所以它应该是 info.Name == "LanguageId"
,检查声明 属性 的类型也是个好主意 - 而不仅仅是 属性 名称。
由于您希望 ID 值在单个 Foo
实例上是唯一的,因此您最好自定义 Foo
类型本身。
您可能希望让 AutoFixture 处理 Foo
的其他部分,DescriptionInfo
值除外。一种方法是创建一个 ISpecimenCommand
,它可以用作生成值的 post 处理器:
public class UniqueIDsOnFooCommand : ISpecimenCommand
{
private static readonly int[] languageIds = new [] { 1, 2, 666, };
private static readonly Random random = new Random();
public void Execute(object specimen, ISpecimenContext context)
{
var foo = specimen as Foo;
if (foo == null)
return;
foreach (var t in
foo.Descriptions.Zip(Shuffle(languageIds), Tuple.Create))
{
var description = t.Item1;
var id = t.Item2;
description.LanguageId = id;
}
}
private static IEnumerable<T> Shuffle<T>(IReadOnlyCollection<T> source)
{
return source.OrderBy(_ => random.Next());
}
}
此实现使用现成的知名语言 ID 数组,shuffles them, and then zips them 和描述。
您可以将此命令打包在自定义中,以更改 Foo
类型的行为:
public class FooCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
SpecimenBuilderNodeFactory.CreateTypedNode(
typeof(Foo),
new Postprocessor(
new MethodInvoker(
new ModestConstructorQuery()),
new CompositeSpecimenCommand(
new AutoPropertiesCommand(),
new UniqueIDsOnFooCommand()))));
}
}
以下测试通过:
[Fact]
public void CreateFoos()
{
var fixture = new Fixture().Customize(new FooCustomization());
fixture.Behaviors.Add(new TracingBehavior());
var foos = fixture.CreateMany<Foo>();
Assert.True(
foos.All(f => f
.Descriptions
.Select(x => x.LanguageId)
.Distinct()
.Count() == f.Descriptions.Count),
"Languaged IDs should be unique for each foo.");
}
创建的 Foo
值如下所示:
Foo:
- LanguageId: 1, Value: "Valueefd1268c-56e9-491a-a43d-3f385ea0dfd8"
- LanguageId: 666, Value: "Value461d7130-7bc0-4e71-96a8-432c019567c9"
- LanguageId: 2, Value: "Valuee2604a80-f113-485c-8276-f19a97bca505"
Foo:
- LanguageId: 2, Value: "Value66eee598-1895-4c0d-b3c6-4262711fe394"
- LanguageId: 666, Value: "Valuef9a58482-13c6-4491-a690-27b243af6404"
- LanguageId: 1, Value: "Valueeb133071-dad7-4bff-b8ef-61d3505f1641"
Foo:
- LanguageId: 1, Value: "Valuea1161dc6-75e5-45a3-9333-f6bca01e882b"
- LanguageId: 666, Value: "Value31332272-5b61-4d51-8572-172b781e5f6b"
- LanguageId: 2, Value: "Value83f80569-277d-49b2-86e5-be256075835e"
综上所述,consider a different design that doesn't involve settable collection properties。
我关注类:
public class Foo
{
public List<DescriptionInfo> Descriptions { get; set; }
}
public class DescriptionInfo
{
public int LanguageId { get; set; }
public string Value { get; set; }
}
我想创建 Foo 实例,使用 Autofixture。但是,LanguageId 必须来自预定义列表。因此我创建了以下定制:
public class LanguageIdSpecimenBuilder : ISpecimenBuilder
{
private static readonly List<int> LanguageIds = new List<int> {
1,
2,
666,
};
public object Create(object request, ISpecimenContext context)
{
var info = request as PropertyInfo;
if (info != null)
{
if (info.Name == "LanguageID")
{
return LanguageIds.GetRandomElement();
}
}
return new NoSpecimen(request);
}
}
现在一切都很好:
Fixture fixture = new Fixture();
fixture.Customizations.Add(new LanguageIdSpecimenBuilder());
var foo = fixture.Create<Foo>();
但是,还有一个要求:一个语言ID不能有重复条目。我怎样才能做到这一点?
编辑: 例如,有效实例是:
Foo1:
- LanguageId: 1, Value: "english description_ae154c"
- LanguageId: 2, Value: "zuzulu_510b7g"
Foo2:
- LanguageId: 1, Value: "english description_87f5de"
- LanguageId: 666, Value: "chinese_35e450"
- LanguageId: 2, Value: "zuzulu_fe830d"
无效实例:
Foo1:
- LanguageId: 1, Value: "_04dcd6"
- LanguageId: 1, Value: "_66ccc4"
- LanguageId: 2, Value: "zuzulu_c05b0f"
首先 - 让我建议您的 POC 可以更精确。如果您不允许列表中具有相同 LanguageID
的描述,您可以像这样改造您的 POC:
public class Foo
{
public ISet<DescriptionInfo> Descriptions { get; set; }
}
public class DescriptionInfo
{
public int LanguageID { get; set; }
public string Value { get; set; }
public override bool Equals(object obj)
{
var anotherInfo = (DescriptionInfo)obj;
return anotherInfo.LanguageID == LanguageID;
}
public override int GetHashCode()
{
return LanguageID;
}
}
问题解决了:
fixture.Customizations.Add(new TypeRelay(typeof(ISet<DescriptionInfo>),
typeof(HashSet<DescriptionInfo>)));
fixture.Customizations.Add(new LanguageIdSpecimenBuilder());
var foo = fixture.Create<Foo>();
如果您不能/不想改造您的 POC,您应该在自定义构建器中关注您想要正确设置的 属性 - Descriptions
.
public object Create(object request, ISpecimenContext context)
{
var info = request as PropertyInfo;
if (info != null && info.Name == "Descriptions" && info.DeclaringType == typeof(Foo))
{
if (info.Name == "Descriptions")
{
return context.Create<List<DescriptionInfo>>()
.GroupBy(g => g.LanguageId)
.Select(g => g.First())
.ToList();
}
}
if (info != null && info.Name == "LanguageId" && info.DeclaringType == typeof(DescriptionInfo))
{
return LanguageIds.GetRandomElement();
}
return new NoSpecimen(request);
}
最后说明 - 你的代码片段是错误的 - 字符串比较区分大小写;所以它应该是 info.Name == "LanguageId"
,检查声明 属性 的类型也是个好主意 - 而不仅仅是 属性 名称。
由于您希望 ID 值在单个 Foo
实例上是唯一的,因此您最好自定义 Foo
类型本身。
您可能希望让 AutoFixture 处理 Foo
的其他部分,DescriptionInfo
值除外。一种方法是创建一个 ISpecimenCommand
,它可以用作生成值的 post 处理器:
public class UniqueIDsOnFooCommand : ISpecimenCommand
{
private static readonly int[] languageIds = new [] { 1, 2, 666, };
private static readonly Random random = new Random();
public void Execute(object specimen, ISpecimenContext context)
{
var foo = specimen as Foo;
if (foo == null)
return;
foreach (var t in
foo.Descriptions.Zip(Shuffle(languageIds), Tuple.Create))
{
var description = t.Item1;
var id = t.Item2;
description.LanguageId = id;
}
}
private static IEnumerable<T> Shuffle<T>(IReadOnlyCollection<T> source)
{
return source.OrderBy(_ => random.Next());
}
}
此实现使用现成的知名语言 ID 数组,shuffles them, and then zips them 和描述。
您可以将此命令打包在自定义中,以更改 Foo
类型的行为:
public class FooCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(
SpecimenBuilderNodeFactory.CreateTypedNode(
typeof(Foo),
new Postprocessor(
new MethodInvoker(
new ModestConstructorQuery()),
new CompositeSpecimenCommand(
new AutoPropertiesCommand(),
new UniqueIDsOnFooCommand()))));
}
}
以下测试通过:
[Fact]
public void CreateFoos()
{
var fixture = new Fixture().Customize(new FooCustomization());
fixture.Behaviors.Add(new TracingBehavior());
var foos = fixture.CreateMany<Foo>();
Assert.True(
foos.All(f => f
.Descriptions
.Select(x => x.LanguageId)
.Distinct()
.Count() == f.Descriptions.Count),
"Languaged IDs should be unique for each foo.");
}
创建的 Foo
值如下所示:
Foo:
- LanguageId: 1, Value: "Valueefd1268c-56e9-491a-a43d-3f385ea0dfd8"
- LanguageId: 666, Value: "Value461d7130-7bc0-4e71-96a8-432c019567c9"
- LanguageId: 2, Value: "Valuee2604a80-f113-485c-8276-f19a97bca505"
Foo:
- LanguageId: 2, Value: "Value66eee598-1895-4c0d-b3c6-4262711fe394"
- LanguageId: 666, Value: "Valuef9a58482-13c6-4491-a690-27b243af6404"
- LanguageId: 1, Value: "Valueeb133071-dad7-4bff-b8ef-61d3505f1641"
Foo:
- LanguageId: 1, Value: "Valuea1161dc6-75e5-45a3-9333-f6bca01e882b"
- LanguageId: 666, Value: "Value31332272-5b61-4d51-8572-172b781e5f6b"
- LanguageId: 2, Value: "Value83f80569-277d-49b2-86e5-be256075835e"
综上所述,consider a different design that doesn't involve settable collection properties。