AutoFixture使用手动假货的AutoData理论

时间:2013-03-15 21:48:06

标签: moq xunit.net autofixture automocking

鉴于此系统要测试:

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字符串)传递给手动伪造的构造函数。

3 个答案:

答案 0 :(得分:8)

这里已经有了一些很好的答案,但是我想建议一个更简单的替代方法,它会略微放松FakeHardToMockDependency类的不变量。将其公开,并提供一种方法来分配与构造函数分离的结果:

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

请注意,我已添加了内部属性,并从字段中删除了readonly关键字。

这使您能够将原始测试重构为:

[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()))
    {
    }
}

答案 1 :(得分: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实现,它告诉fixture对象如何创建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())
    {
    }
}

请务必按照Mark Seemann的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]属性应用于Theory参数,立即将其注入自动创建的SUT实例中:

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

    }

这将创建一个MySut实例和构造函数所需的IHardToMockDependency实例,您已在FakeHardToMockDependencyCustomization中为AutoFixture指定了一个规则,并且还将该实例作为fake变量。

请注意,不冻结假名仍然会给你一个正确的FakeHardToMockDependency实例,并将一个注入到sut中,但这些将是截然不同的,因为我们在自定义中注册了一个工厂委托。冻结实例将导致fixture始终为后续的接口请求返回相同的实例。

但是有一些警告:

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

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

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

如果您经常在需要测试的特定域中编写代码,那么创建一个包含自定义,属性和存根对象的库可能是有意义的,这些对象一旦放到xUnit旁边就可以随时使用。 net,AutoFixture和Moq。我知道我很高兴我建造了我的。

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

答案 2 :(得分: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,您可以在其中隐藏部分或全部灯具推荐。

类似的东西:

public class MySutAutoDataAttribute : AutoDataAttribute
{
    public MySutAutoData()
        : base(new Fixture()
            .Customize(new AutoMoqCustomization()))
    {
        Fixture.Freeze<string>();

        Fixture.Register<IHardToMockDependency>(
            () => new FakeHardToMockDependency(Fixture.Create<string>()));
    }
}

您可以像以下一样使用它:

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

    Assert.Equal(expectedResult, actualResult);
}

但是你应该注意到MySutAutoDataAttribute有很大的改进空间,例如:如果你在测试中使用多个字符串,那么它不是非常通用的,Fixture.Freeze<string>();可能会导致问题。