2013-03-15 54 views
6

鉴于这种系统测试:研究机构Autodata理论使用AutoFixture手动假货

public class MySut 
{ 
    private readonly IHardToMockDependency _hardToMockDependency; 

    public MySut(IHardToMockDependency hardToMockDependency, 
       IOtherDependency otherDependency) 
    { 
     _hardToMockDependency = hardToMockDependency; 
    } 

    public string GetResult() 
    { 
     return _hardToMockDependency.GetResult(); 
    } 
} 

public interface IOtherDependency { } 

public interface IHardToMockDependency 
{ 
    string GetResult(); 
} 

而这个单元测试:

internal class FakeHardToMockDependency : IHardToMockDependency 
{ 
    private readonly string _result; 

    public FakeHardToMockDependency(string result) 
    { 
     _result = result; 
    } 

    public string GetResult() 
    { 
     return _result; 
    } 
} 

public class MyTests 
{ 
    [Fact] 
    public void GetResultReturnsExpected() 
    { 
     string expectedResult = "what I want"; 
     var otherDependencyDummy = new Mock<IOtherDependency>(); 
     var sut = new MySut(new FakeHardToMockDependency(expectedResult), 
          otherDependencyDummy.Object); 

     var actualResult = sut.GetResult(); 

     Assert.Equal(expectedResult, actualResult); 
    } 
} 

我应该如何转换它使用AutoFixture.Xunit和AutoFixture。 AutoMoq(同时仍然使用手动假冒)?

在现实世界的测试中,手动假人会有更复杂的界面和行为。请注意,我想将匿名变量(expectedResult字符串)传递给手动伪造的构造函数。

回答

8

这里已经有一些很好的答案,但我想建议一个更简单的方法,它涉及到稍微松开FakeHardToMockDependency类的不变式。把它公开,并提供了一种方式来分配是从构造分离的结果是:我已经添加了一个内部属性,并取出从外地readonly关键字

public class FakeHardToMockDependency : IHardToMockDependency 
{ 
    private string _result; 

    public FakeHardToMockDependency(string result) 
    { 
     _result = result; 
    } 

    internal string Result 
    { 
     get { return _result; } 
     set { _result = value; } 
    } 

    public string GetResult() 
    { 
     return _result; 
    } 
} 

通知。

这使您可以将原有的测试重构这样:

[Theory, AutoMoqData] 
public void GetResultReturnsExpected_AutoDataVersion(
    [Frozen(As = typeof(IHardToMockDependency))]FakeHardToMockDependency fake, 
    MySut sut) 
{ 
    var expected = "what I want"; 
    fake.Result = expected; 

    var actual = sut.GetResult(); 

    Assert.Equal(expected, actual); 
} 

为了完整起见,这里的AutoMoqDataAttribute代码:

public class AutoMoqDataAttribute : AutoDataAttribute 
{ 
    public AutoMoqDataAttribute() 
     : base(new Fixture().Customize(new AutoMoqCustomization())) 
    { 
    } 
} 
+0

现在,你去拿我手中的金锤。 ;-)但是“内部”在这里与“公共”相比有什么不同? SUT无法以任何方式查看属性,因为它只获取界面。 – TeaDrivenDev 2013-03-16 14:33:49

+0

@GCATNM Result属性是“internal”还是“public”并不重要。就个人而言,我会让它成为'public',但是OP提供了假的'内部'类,所以我只想尽可能少地改变建议的修改以使其更可接受:) – 2013-03-16 14:56:08

+0

哇,三个很好的答案,还有一位来自作者本人!松开假的不变式是很好的。谢谢!只接受一个答案会很艰难 - 我必须尝试这些。 – TrueWill 2013-03-17 01:17:37

3

也许这还不是最地道的Autofixture设置,但肯定工程:

[Fact] 
public void GetResultReturnsExpected() 
{ 
    var fixture = new Fixture() 
     .Customize(new AutoMoqCustomization()); 

    var expectedResult = fixture.Create<string>(); 

    fixture.Register<IHardToMockDependency>(
     () => new FakeHardToMockDependency(expectedResult)); 

    var sut = fixture.Create<MySut>(); 

    var actualResult = sut.GetResult(); 

    Assert.Equal(expectedResult, actualResult); 
} 

如果你还需要使用AutoData您可以创建基于this great article在那里你可以隐藏部分或全部的灯具自己AutoMoqData custimizations。

喜欢的东西:

​​

而且你可以用它喜欢:

[Theory, MySutAutoData] 
public void GetResultReturnsExpected(MySut sut, string expectedResult) 
{ 
    var actualResult = sut.GetResult(); 

    Assert.Equal(expectedResult, actualResult); 
} 

但是你要注意,有房了大量的改进在MySutAutoDataAttribute例如:它不非常通用,如果在测试中使用多个字符串,则会导致问题Fixture.Freeze<string>();

+0

我知道我能做到这一点,但我的问题是专门这样做这与AutoDataAttribute的后裔。你能举一个这样的例子吗? – TrueWill 2013-03-15 22:38:24

+0

@TrueWill对不起,我错过了你想用AutoDataAttribute解决问题的标题。我已经用可能的实现更新了我的答案。 – nemesv 2013-03-15 22:49:18

+0

然而,冻结这样的字符串让你无法控制它。 – TeaDrivenDev 2013-03-16 00:37:19

4

根据您需要传递给手动假冒的参数类型,您可能可以使用参数化属性,类似于AutoFixture的内置InlineAutoDataAttribute

鉴于这些

public interface IHardToMockDependency 
{ 
    string Value { get; } 
} 

public class FakeHardToMockDependency : IHardToMockDependency 
{ 
    private readonly string _value; 

    public FakeHardToMockDependency(string value) 
    { 
     _value = value; 
    } 

    #region IHardToMockDependency Members 

    public string Value 
    { 
     get { return this._value; } 
    } 

    #endregion IHardToMockDependency Members 
} 

您创建一个ICustomization实现,讲述了一个固定对象如何创建IHardToFakeDependency接口系统的实现:

public class FakeHardToMockDependencyCustomization : ICustomization 
{ 
    private readonly string _value; 

    public FakeHardToMockDependencyCustomization(string value) 
    { 
     _value = value; 
    } 

    #region ICustomization Members 

    public void Customize(IFixture fixture) 
    { 
     fixture.Register<IHardToMockDependency>(() => new FakeHardToMockDependency(this._value)); 
    } 

    #endregion ICustomization Members 
} 

注意,这需要知道你想要的字符串当然要通过。

接下来,你卷起这跟你想在一个CompositeCustomization使用其他自定义:

public class ManualFakeTestConventions : CompositeCustomization 
{ 
    public ManualFakeTestConventions(string value) 
     : base(new FakeHardToMockDependencyCustomization(value), new AutoMoqCustomization()) 
    { 
    } 
} 

确保您始终把自定义的顺序从最具体到最一般的,由马克解释here塞曼。

现在你创建一个使用这种定制的AutoDataAttribute实现:

public class ManualFakeAutoDataAttribute : AutoDataAttribute 
{ 
    public ManualFakeAutoDataAttribute(string value) 
     : base(new Fixture().Customize(new ManualFakeTestConventions(value))) 
    { 
    } 
} 

这现在可以以同样的方式被用作InlineAutoDataAttribute

public class ManualFakeTests 
{ 
    [Theory, ManualFakeAutoData("iksdee")] 
    public void ManualFake(IHardToMockDependency fake) 
    { 
     Assert.IsType<FakeHardToMockDependency>(fake); 
     Assert.Equal("iksdee", fake.Value); 
    } 
} 

你也可以把它注入通过将[Frozen]属性应用于理论参数,立即自动创建SUT实例:

[Theory, ManualFakeAutoData("iksdee")] 
    public void SutWithManualFake([Frozen] IHardToMockDependency fake, MySut sut) 
    { 

    } 

这将创建一个MySut实例和所需要的构造,您已在FakeHardToMockDependencyCustomization给出AutoFixture规则的IHardToMockDependency实例,也给你,很实例作为fake变量。

请注意,不冻结假冒仍然会给你一个正确的FakeHardToMockDependency实例以及注入一个实例,但这些将是不同的,因为我们已经在自定义中注册了一个工厂代理。冻结实例会导致灯具始终为接口的后续请求返回相同的实例。

这有几个注意事项,但是:

  • 你没有提到你作为参数传递的字符串,所以你必须有它在那里作为一个字符串字面量的两倍。例如,您可以在测试类中使用字符串常量来解决此问题。
  • 可以在.NET中用作属性参数的类型数量是有限的。只要你只需要基本类型,你应该没问题,但是在参数列表中调用构造函数等是不可能的。
  • 只有当您需要实例时才应该使用此属性IHardToFakeDependency;否则你总是必须传入一个字符串参数。如果您需要使用一组标准自定义设置,请创建另一个仅包含这些设置的属性。
  • 如果您同时需要InlineAutoDataAttribute的功能,则还需要创建另一个结合两者功能的属性。

根据具体情况,你可能也想看看xUnit.net的PropertyDataAttribute,但我几乎没有发现自己使用。

一般来说,在我看来,理解如何使用自定义和自动数据属性以及何时以及如何创建自己的属性是有效使用AutoFixture的关键,并且真正使它可以节省您的工作量。

如果您经常在需要测试的特定域中编写代码,创建一个包含自定义,属性和存根控件对象的库,在将其放置在xUnit旁边后随时可以使用, .net,AutoFixture和Moq。我知道我很高兴我建立了我的。

哦,还有:有一个很难嘲笑的依赖可能指向一个设计问题。为什么这很难嘲笑?

+0

谢谢;很有帮助!我同意这是一种代码味道,表明潜在的设计问题。 – TrueWill 2013-03-16 04:21:37