如何保持单元测试干燥并减少断言

How to keep Unit tests DRY and reducing Asserts

我正在尝试使用 TDD 方法实现一个 web 服务类,它发出一堆 web 请求并解释响应。我将 webrequests 封装在几个接口中,以便我可以轻松地模拟它们。当通过 webserviceclass 请求某些东西时,实现的方法总是 returns 一个包含错误对象的特定响应对象。借助此错误对象,用户可以确定请求是否成功,以及具体错误是什么。

写了一堆测试后,我意识到我在 Arrange 阶段重复了很多次:

var mock = new Mock<ISomeWebservices>();
var sut = new MyWebServiceClass(mock.Object);
mock.Setup(foo=>foo.SomeRequest(someData)).Returns(@"{""user"": ""12345"",""somedata"": ""60"",""someotherdata"":""2015-09-01T12:00:00.200Z""}");
sut.SomeRequest(someData,s=> response = s);

所有测试的前两行始终相同。总是有一个模拟设置 returns 某些东西或抛出异常。根据我正在测试的请求,我当然必须设置不同的方法。 我尝试使用 Autofixture 来解决这个问题,这样我就可以为每个 Web 请求编写一个 ICustomization,但问题是我仍然需要访问模拟才能进行特定于测试的设置。

另一个问题是 Assert 阶段。因为我总是从请求中得到一个错误对象,所以如果我是预期和错误,我只会断言错误对象。

    Assert.That(response.Error.Type,Is.EqualTo(ErrorInfo.ErrorType.IllegalToken));
    Assert.That(response.Error.Message, Is.Not.Null);
    Assert.That(response.Error.AdvisedAction, Is.Not.Null);
    Assert.That(response.Error.RawData, Is.Not.Null);

所以如果出现错误,错误对象的一些属性应该始终填写,有些可以。 (f.e。如果错误是由异常触发的,则异常 属性 不应为 null 等) 据我所知,在单元测试中使用多个断言是一种不好的做法,所以我想尽可能避免这种情况。

[编辑] 根据评论,拥有多个断言以及重复我提到的编排部分并不是那么糟糕。所以我不会为此使用 Autofixture。

您想尽可能避免重复,理想情况下,测试代码应按照生产代码标准编写,因为您可以花与生产代码一样多的时间来阅读和维护测试代码。获得平衡是关键,因为您不希望像指出的那样进行脆弱的测试而不是相互依赖。

通常使用测试数据构建器来简化和删除创建测试数据和对象时的重复。如果您更改对象的创建方式,这也会有所帮助,因为您只更新一个构建器方法而不是所有单独的测试。

每个测试多个断言很好(可能是必不可少的),只要确保断言是相关的并且测试坚持一个概念,有助于快速识别错误。

您当然可以使用 AutoFixture 来减少 Arrange 阶段所需的样板代码量。它可能看起来像这样:

[Theory, AutoMoqData]
public void Test(
    [Frozen]Mock<ISomeWebservices> mock,
    MyWebServiceClass sut,
    object someData,
    object response)
{
    mock.Setup(foo => foo.SomeRequest(someData)).Returns(@"{""user"": ""12345"",""somedata"": ""60"",""someotherdata"":""2015-09-01T12:00:00.200Z""}");
    sut.SomeRequest(someData, s => response = s);
    // Assertions go here...
}

在这里,我不得不猜测 someDataresponse 的类型,但如果它们与 object 不同,您只需将它们声明为该类型即可.

在上面的例子中,[AutoMoqData]属性是这样定义的:

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

所有其他类型和 AutoFixture 功能都来自 AutoFixture.Xunit2 and AutoFixture.AutoMoq NuGet 包。

当谈到多重断言时,我同意这里其他评论者的看法,它看起来并没有那么糟糕,但本着 GOOS 的精神,您应该 听取您的测试。在这种特殊情况下,测试似乎在说:response 的等式比较看起来很重要。 如果是这种情况,您可以考虑覆盖 Equals response 并给它 结构平等 而不是 参考平等.