2013-03-20 7 views
11

Próbuję przezwyciężyć scenariusz, w którym klasa ma parametr konstruktora ciągów znaków, który nie może być spełniony przez żaden stary ciąg wygenerowany przez Autofixture (wartość wyglądająca Guid-y).Jak używać funkcji Autofixture (v3) z programem ICustomization, ISpecimenBuilder, aby poradzić sobie z parametrem konstruktora?

Zanim będziesz miał ochotę odpowiedzieć po prostu za pomocą linku do Mark Seemann's Ploeh blog entry on Convention-based Customizations, powiem, że odnoszę się do niego i innych wpisów na blogu jego do tego testu, którego nie mogę przejść.

Kiedy przechodzę do debugowania, widzę, że w pewnym momencie parametr konstruktora jest przekazywany z poprawną wartością, ale test nadal kończy się niepowodzeniem z wartością koloru Guid-y. Myślę, że ma to coś wspólnego z tym, że istnieje zarówno wartość parametru "kolor", i właściwość "Kolor", która ma być wypełniona przez Autofixture. Czy napisałem ISpecimenBuilder, który adresuje parametr konstruktora, ale testuję wartość właściwości publicznej (dwie różne rzeczy)?

Wiem, że to wszystko jest przesadą dla przykładu, ale wyobrażam sobie bardziej skomplikowany scenariusz, w którym użycie metody Build<T>().With() nie byłoby SUCHĄ.

niewydolnego test

[Fact] 
    public void Leaf_Color_Is_Brown() 
    { 
     // arrange 
     var fixture = new Fixture().Customize(new LeafColorCustomization()); 

     // act 
     var leaf = fixture.Create<Leaf>(); 

     // using .Build<>.With(), test passes 
     //var leaf = fixture.Build<Leaf>().With(l => l.Color, "brown").CreateAnonymous(); 

     // assert 
     Assert.True(leaf.Color == "brown"); 
    } 

SUT

public class Leaf 
    { 
     public Leaf(string color) 
     { 
      if (color != "brown") 
       throw new ArgumentException(@"NO LEAF FOR YOU!"); 

      this.Color = color; 
     } 
     public string Color { get; set; } 
    } 

Realizacja CompositeCustomization (znam AutoMoqCustomization() nie jest potrzebne w tym przykładzie)

public class LeafCustomization : CompositeCustomization 
    { 
     public LeafCustomization() 
      : base(
      new LeafColorCustomization(), 
      new AutoMoqCustomization()) { } 
    } 

Liść specyficzne ICustomization

public class LeafColorCustomization : ICustomization 
    { 
     public void Customize(IFixture fixture) 
     { 
      if (fixture == null) 
       throw new ArgumentNullException("fixture"); 

      fixture.Customizations.Add(new LeafBuilder()); 
     } 
    } 

String-konstruktor-z-imienia-of-Color specyficzne ISpecimenBuilder

public class LeafBuilder : ISpecimenBuilder 
    { 
     public object Create(object request, ISpecimenContext context) 
     { 
      var pi = request as ParameterInfo; 
      if (pi == null) 
       return new NoSpecimen(request); 

      if (pi.ParameterType != typeof(string) || pi.Name != "color") 
       return new NoSpecimen(request); 

      return "brown"; 
     } 
    } 

Odpowiedz

7

Rozwiązanie 1:

Zarejestruj, że właściwość zapisywalna Color nie powinna b e przypisać dowolną automatycznego wartość jako część post-processing:

internal class LeafColorCustomization : ICustomization 
{ 
    public void Customize(IFixture fixture) 
    { 
     fixture.Customize<Leaf>(c => c 
      .Without(x => x.Color)); 

     fixture.Customizations.Add(new LeafBuilder()); 
    } 
} 

Rozwiązanie 2:

Dodać właściwość tylko do odczytu Color:

public class Leaf 
{ 
    private readonly string color; 

    public Leaf(string color) 
    { 
     if (color != "brown") 
      throw new ArgumentException(@"NO LEAF FOR YOU!"); 

     this.color = color; 
    } 

    public string Color 
    { 
     get { return this.color; } 
    } 
} 

Od nieruchomości Color jest tylko do odczytu AutoFixture nie zamierza przypisać dla niego wartości.

Powyższe rozwiązania dotyczą również AutoFixture 2.

+0

genialny - to działa! Re: Rozwiązanie nr 1, czy można/należy go pominąć właściwość zapisu 'Color' wewnątrz' ISpecimenBuilder' ('LeafBuilder') zamiast wewnątrz' ICustomization' ('LeafColorCustomization')? Mam wrażenie, że wiele z tych ".Without()" zaszkodziłoby implementacji, i że byłoby czystsze (szczególnie w bardziej złożonym przykładzie), aby mieć tylko serię 'fixture.Customizations.Add (new XXXBuilder ()); 'wewnątrz' ICustomization' - czy nie rozumiem, gdzie znajdują się różne dostosowania? – Jeff

+3

@Lumirris Z pewnością poradziłbyś sobie z problemem z 'LeafBuilder'. Po prostu zajmiemy się również przypadkiem, w którym 'request' jest' PropertyInfo' oprócz przypadku, który już obsłużyłeś, gdzie jest 'ParameterInfo'. –

+0

@MarkSeemann - [YAGNI] (http://en.wikipedia.org/wiki/You_aren't_gonna_need_it) bez względu na to, czy lepiej radzić sobie z implementacją 'ISpecimenBuilder'' LeafBuilder' przy rozważaniu bardziej złożonych scenariuszy, używając Implementacja "ICustomization" do dodania wszystkich niezbędnych implementacji 'ISpecimenBuilder'? – Jeff

5

Zakładając, masz do czynienia z rzeczy ustawienie właściwości osobno, tutaj jest ograniczenie argumentem konstruktora Customization który załatwia sprawę:

class BrownLeavesCustomization : ICustomization 
{ 
    void ICustomization.Customize(IFixture fixture) 
    { 
     Func<string> notBrownGenerator = fixture.Create<Generator<string>>() 
      .SkipWhile(x => x == "Brown") 
      .First; 
     fixture.Customizations.Add( 
      ArgumentGeneratorCustomization<Leaf>.ForConstructorArgument(
       "color", 
       notBrownGenerator)); 
    } 

    static class ArgumentGeneratorCustomization<T> 
    { 
     public static ISpecimenBuilder ForConstructorArgument<TArg>(string argumentName, Func<TArg> generator) 
     { 
      return new ConstructorArgumentGenerator<TArg>(argumentName, generator); 
     } 

     class ConstructorArgumentGenerator<TArg> : ISpecimenBuilder 
     { 
      readonly string _argumentName; 
      readonly Func<TArg> _generator; 

      public ConstructorArgumentGenerator(string argumentName, Func<TArg> generator) 
      { 
       Assert.Contains(argumentName, from ctor in typeof(T).GetConstructors() from param in ctor.GetParameters() select param.Name); 
       _argumentName = argumentName; 
       _generator = generator; 
      } 

      object ISpecimenBuilder.Create(object request, ISpecimenContext context) 
      { 
       var pi = request as ParameterInfo; 
       if (pi == null) 
        return new NoSpecimen(request); 
       if (pi.Member.DeclaringType != typeof(T)) 
        return new NoSpecimen(request); 
       if (pi.Member.MemberType != MemberTypes.Constructor) 
        return new NoSpecimen(request); 
       if (pi.ParameterType != typeof(TArg)) 
        return new NoSpecimen(request); 
       if (pi.Name != _argumentName) 
        return new NoSpecimen(request); 

       return _generator(); 
      } 
     } 
    } 
} 
+0

@NikosBaxevanis Prawdziwym powodem, dla którego mam tutaj, jest sprawdzenie, czy może wywołać reakcję od ciebie i innych :) Czy znasz jakieś podobne konstrukcje w samym AF lub publicznie dostępne? Wszelkie pomysły na ulepszenia? –

1

Rozwiązanie: (na podstawie Mark Seemann na comment on this answer)

Dostosuj zarówno parametr konstruktora, jak i właściwość do zapisu w implementacji ISpecimenBuilder, i nie rób nic poza dodaniem instancji LeafBuilder w LeafColorCustomization:

public class LeafBuilder : ISpecimenBuilder 
{ 
    public object Create(object request, ISpecimenContext context) 
    { 
     var paramInfo = request as ParameterInfo; 
     if (paramInfo != null 
      && paramInfo.ParameterType == typeof(string) 
      && paramInfo.Name == "color") 
     { return "brown"; } 

     var propInfo = request as PropertyInfo; 
     if (propInfo != null 
      && propInfo.PropertyType == typeof(string) 
      && propInfo.Name == "Color") 
     { return "brown"; } 

     return new NoSpecimen(request); 
    } 
} 

internal class LeafColorCustomization : ICustomization 
{ 
    public void Customize(IFixture fixture) 
    { 
     fixture.Customizations.Add(new LeafBuilder()); 
    } 
} 
Powiązane problemy