单元测试中的重复代码

svr*_*ist 11 c# nunit unit-testing moq autofixture

我们发现自己在许多测试用例中编写重复的夹具/模拟设置 - 就像这种情况:

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var encodingMock = fixture.Freeze<Mock<IEncodingWrapper>>();
var httpClientMock = fixture.Freeze<Mock<IHttpWebClientWrapper>>();
var httpResponseMock = fixture.Freeze<Mock<IHttpWebResponseWrapper>>();
var httpHeaderMock = fixture.Freeze<Mock<IHttpHeaderCollectionWrapper>>();
var etag = fixture.CreateAnonymous<string>();
byte[] data = fixture.CreateAnonymous<byte[]>();
Stream stream =  new MemoryStream(data);

encodingMock.Setup(m => m.GetBytes(It.IsAny<string>())).Returns(data);
httpHeaderMock.SetupGet(m => m[It.IsAny<string>()]).Returns(etag).Verifiable();
httpClientMock.Setup(m => m.GetResponse()).Returns(httpResponseMock.Object);
httpResponseMock.Setup(m => m.StatusCode).Returns(HttpStatusCode.OK);
httpResponseMock.SetupGet(m => m.Headers).Returns(httpHeaderMock.Object);
httpResponseMock.Setup(m => m.GetResponseStream()).Returns(stream);
Run Code Online (Sandbox Code Playgroud)

根据测试应该是自包含的并且从头到尾可读的想法,我们不使用神奇的Setup/Teardown方法.

我们可以以任何方式(AutoFixture自定义,辅助方法)减少这些测试的"笨拙的工作"吗?

Mar*_*ann 15

来自不断增长的面向对象软件(GOOS)提出了一个很好的建议:如果测试难以编写,那么它就是关于被测系统(SUT)的API的反馈.考虑重新设计SUT.在这个特定的例子中,看起来SUT至少有四个依赖关系,这可能表明违反了单一责任原则.是否有可能重构门面服务

GOOS的另一个重要建议是

在上面的示例中,看起来您需要为真正查询的方法执行大量Moq设置.这也表明了测试气味.在某处有Demeter违法吗?是否有可能削减方法链?


Nik*_*nis 11

您可以创建一个复合Customization,它将使用所有包含的自定义来自定义fixture.

public class HttpMocksCustomization : CompositeCustomization
{
    public HttpMocksCustomization()
        : base(
            new AutoMoqCustomization(),
            new HttpWebClientWrapperMockCustomization(),
            new HttpWebResponseWrapperMockCustomization()
            // ...
            )
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

每个定制都可以定义如下:

public class HttpWebClientWrapperMockCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var mock = new Mock<IHttpWebClientWrapper>();
        mock.Setup(m => m.GetResponse()).Returns(httpResponseMock.Object);

        fixture.Inject(mock);
    }
}

public class HttpWebResponseWrapperMockCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var mock = new Mock<IHttpWebResponseWrapper>();
        mock.Setup(m => m.StatusCode).Returns(HttpStatusCode.OK);

        fixture.Inject(mock);
    }
}

// The rest of the Customizations.
Run Code Online (Sandbox Code Playgroud)

然后在测试方法中你可以这样做:

var fixture = new Fixture().Customize(new HttpMocksCustomization());
Run Code Online (Sandbox Code Playgroud)

这样,当您请求Mock实例时,您不必重复设置步骤.我们之前定制的那个将被退回:

var httpClientMock = fixture.Freeze<Mock<IHttpWebClientWrapper>>();
Run Code Online (Sandbox Code Playgroud)

但是,如果您使用xUnit.net,事情可以进一步简化.

您可以创建一个AutoDataAttribute派生类型,以提供由AutoFixture生成的自动生成的数据样本作为xUnit.net的Theory属性的扩展:

public class AutoHttpMocksDataAttribute : AutoDataAttribute
{
    public AutoHttpMocksDataAttribute()
        : base(new Fixture().Customize(new HttpMocksCustomization()))
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在您的测试方法中,您可以将Mocks作为参数传递:

[Theory, AutoHttpMocksData]
public void MyTestMethod([Freeze]Mock<IHttpWebClientWrapper> httpClientMock, [Freeze]Mock<IHttpWebResponseWrapper> httpResponseMock)
{
    // ...
} 
Run Code Online (Sandbox Code Playgroud)

  • 此外,正如Enrico指出的那样,您可以阅读他的优秀文章http://goo.gl/AFmMi,其中详细描述了自定义. (2认同)