2016-02-22 2 views
2

я следующие классы:Autofixture генерировать коллекцию без дубликатов (по идентификатору)

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

Однако, есть еще одно требование: не должно быть никаких дублированных записей для одного идентификатора языка. Как я могу это достичь?

EDIT: Например, допустимые экземпляры являются:

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" 

Invalid экземпляр:

Foo1: 
- LanguageId: 1, Value: "_04dcd6" 
- LanguageId: 1, Value: "_66ccc4" 
- LanguageId: 2, Value: "zuzulu_c05b0f" 
+0

Список допустимых идентификаторов языков содержит три значения. Что произойдет, если вы создадите четыре экземпляра Foo? –

+0

Я добавил примеры. Это требование относится к конкретным экземплярам Foo, а не по всему миру. – piotrwest

ответ

3

Прежде всего - позвольте мне предложить свое POCes может быть более точными. Если вы не позволяете описания с одинаковым LanguageID в списке, вы можете перестроить ваш POCes как:

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

Если вы не можете/не хотите переделать свои POCes, вы должны сосредоточиться в пользовательском построителе на свойстве, которое вы хотите установить правильно - 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", также отличная идея проверить тип, объявляющий свойство, а не только имя свойства.

+0

Спасибо за подробный ответ. В самом деле, есть ошибка случая и проверка типа объявления - очень хорошая идея. – piotrwest

2

Поскольку вы хотите, чтобы идентификационные значения были уникальными на одном экземпляре Foo, вам, вероятно, будет лучше всего настроить тип Foo.

Возможно, вы захотите, чтобы AutoFixture справилась с другими частями Foo, за исключением значений DescriptionInfo. Один из способов сделать это, чтобы создать ISpecimenCommand, который может быть использован в качестве постпроцессора генерируемых значений:

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

Эта реализация использует готовый набор хорошо известных идентификаторов языка, shuffles them, а затем 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.

+0

Также неплохое решение, даже мне кажется, что это немного менее «хакерский», чем ответ на @ ondrej-svejdar. Однако, поскольку он был первым, я буду принимать его ответ как принятый. – piotrwest

+1

@piotrwest Не беспокойтесь. Важная часть заключается в том, что вы получили полезный ответ на свой вопрос. –