2013-03-20 78 views
11

我想克服一个场景,其中一个类有一个字符串构造函数参数,它不能被Autofixture(Guid-y的值)生成的任何旧字符串满足。如何在ICustomization中使用Autofixture(v3),ISpecimenBuilder处理构造函数参数?

在你试图简单地回答Mark Seemann's Ploeh blog entry on Convention-based Customizations的链接之前,让我说我一直在引用它和他的这个测试的其他博客条目,这是我无法通过的。

当我在调试中逐步完成时,我可以看到在某些时候构造函数参数传入了有效值,但测试仍然失败,出现Guid-y Color值。我认为这与“颜色”参数值是由“自动混合”填充的“颜色”属性有关。是我写了一个ISpecimenBuilder来解决构造函数参数,但我正在测试公共属性值(两种不同的东西)?

我知道这一切都是矫枉过正的例子,但我想象一个更复杂的情况下,使用Build<T>().With()方法不会干。

失败的测试

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

的CompositeCustomization实施(我知道AutoMoqCustomization()不需要在这个例子中)

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

叶具体ICustomization

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

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

String的构造函数与 - 名色特有的最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"; 
     } 
    } 

回答

7

解决方案1 ​​

注册Color可写属性不应该b È分配任何自动的值作为后处理的一部分:

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

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

溶液2

充分利用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; } 
    } 
} 

由于Color属性是只读AutoFixture不会为其分配值。

上述解决方案也适用于AutoFixture 2。

+0

辉煌 - 工程! Re:解决方案#1,可以/应该指定跳过'ISpecimenBuilder'('LeafBuilder')内部的'Color'可写属性,而不是'ICustomization'('LeafColorCustomization')内部?我的印象是许多'.Without()'警告会污染执行,并且它会更干净(特别是在一个更复杂的例子中)除了一系列'fixture.Customizations.Add(新的XXXBuilder( ));'ICustomization'内部 - 我不知道不同的定制属于哪里? – Jeff 2013-03-20 21:33:50

+3

@Lumirris你肯定可以在'LeafBuilder'中处理这个问题。你只需要处理'request'是一个'PropertyInfo'的情况,除了你已经处理了'ParameterInfo'的地方。 – 2013-03-21 09:45:51

+0

@MarkSeemann - [YAGNI](http://en.wikipedia.org/wiki/You_aren't_gonna_need_it)问题尽管如此,在考虑更复杂的场景时,最好从'ISpecimenBuilder'实现'LeafBuilder'内处理,使用'ICustomization'实现只添加所有必需的'ISpecimenBuilder'实现? – Jeff 2013-03-21 17:20:44

5

假设你分开处理的属性设置的东西,这里是一个构造函数的参数限制Customization其中的伎俩:

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我在这里的真正原因是看它是否会引起你的反应等等:)知道AF本身或公共可用的任何类似结构?任何改进想法? – 2013-03-21 14:50:35

1

解决方案:(基于马克西曼的comment on this answer

容纳ISpecimenBuilder实现中的构造函数参数和可写属性,除了将LeafBuilder实例添加到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()); 
    } 
} 
相关问题