2015-11-18 59 views
4

我想实现一个使用TDD方法的webserviceclass,它发出一堆webrequests并解释响应。我将webrequests封装在几个接口中,以便我可以轻松地嘲笑它们。当通过webserviceclass请求某个东西时,实现的方法总是返回一个包含错误对象的特定响应对象。在这个错误对象的帮助下,用户可以确定请求是否成功,以及具体的错误是什么。如何保持单元测试DRY并减少断言

编写一些测试后,我意识到,我反复了很多自己在安排阶段:

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

第2行,始终是所有测试相同。总是有一个安装程序模拟它返回的东西或引发异常。根据我测试的要求,我必须设置不同的课程方法。 我试图通过使用自动混合来解决这个问题,这样我就可以为每个webrequest编写一个ICustomization,但问题是我仍然需要访问模拟以获得特定于测试的安装程序。

另一个问题是声明阶段。由于我总是从请求中获取一个错误对象,所以如果我感到不安和错误,我只会断言错误对象。

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

所以,如果有错误,也有应该始终被填写和一些可以是错误对象的某些属性。 (如果错误是由异常触发的,异常属性应该不为空等) 据我所知,在单元测试中有几个Asserts是一个不好的习惯,所以我想尽量避免这种情况。

根据评论,有多个断言,以及重复安排的部分,我提到,并没有那么糟糕。所以我不会使用Autofixture。

+0

编写单元测试时干燥程度并不那么重要,因为它会导致脆弱的测试http://codebetter.com/karlseguin/2009/09/12/unit-testing-do-repeat-yourself/ – juharr

+0

我不'我认为几个断言是不好的。测试不同的,不相关的东西是不好的。但是回收一个对象并测试这些属性是否正确对我来说可以。 –

+0

对于你的难道,你不能有类变量,并使用Setup方法来创建它吗? –

回答

4

您绝对可以使用AutoFixture来减少您在编排阶段所需的样板代码的数量。它看起来是这样的:

[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.Xunit2AutoFixture.AutoMoq的NuGet包绘制。

当涉及到多种说法,我同意在这里的其他评论者认为它看起来并不那么糟糕,但在GOOS精神,你应该听你的测试。在这种特殊情况下,测试似乎说:response的平等比较看起来很重要。如果是这样的话,您可以考虑覆盖response上的Equals并给它结构相等而不是参考平等

+0

感谢您的详细解释,这是我真正想要的。不过,我使用Nunit而不是Xunit,但我不明白为什么这不应该与AutoFixture.Nunit2包一起使用? 当我尝试这个我得到一个System.Reflection.TargetParameterCountException。 – zlZimon

+0

@zlZimon NUnit 2的可扩展性模型非常混乱,你很幸运能够正常工作。 * AutoFixture.NUnit2 *是尽力将* AutoFixture.Xunit2 *移植到NUnit,但AFAICT,它非常片状。帮你一个忙,用xUnit.net代替。很容易从NUnit迁移到xUnit.net。 –

+0

我遵循你的建议,并迁移到XUnit2,我真的很喜欢它。不幸的是,我大量使用NUnit的类别,但这是一个不同的故事。我想知道是否通过理论属性始终初始化sut/mocks /或测试中使用的任何数据是好的做法吗?我真的很喜欢我的测试现在看起来多么干净。 – zlZimon

3

想要尽可能避免重复,测试代码应理想地写入生产代码标准,因为您可以花费尽可能多的时间来读取和维护测试代码,如生产代码。获得平衡是关键,因为你不需要像指出的那样脆弱的测试而不是相互依赖。

一般情况下,使用测试数据创建器可以在创建测试数据&对象时简化并消除重复。如果您更改对象的创建方式,这也会有所帮助,因为您只更新一个构建器方法而不是所有单个测试。

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

相关问题