2010-01-28 188 views
6

我正在寻找使下面更简洁的方法。干燥与犀牛嘲笑

public class MyTests 
{ 
    IPresenter presenter; 

    [SetUp] 
    public void SetUp() 
    { 
     presenter = MockRepository.GenerateStub<IPresenter>(); 
    } 

    ... 
} 

特别是在创建模拟时重新指定类型似乎是多余的。例如,我可以写这样的,使用反射来获取类型并自动创建存根:

public class MyTests 
{ 
    IPresenter presenter; 

    [SetUp] 
    public void SetUp() 
    { 
     Stub(x => x.presenter); 
    } 

    void Stub(Expression<Func<MyTests, object>> expression) 
    { 
     ... 
    } 
} 

这会工作,但编译器可以不再检测演示分配,并开始发出警告。这也使得ReSharper非常不高兴。

任何人都可以提出更好的方法吗?

回答

5

这可能是有争议的,但我赞成可读性,而非单元测试中的DRY-ness *。

换句话说,在我的单元测试中,设置方法是不存在的。它们仅用于集成测试。我相信XUnit.NET也是这样。

所以要回答你的问题,我真的不会担心在每个需要测试的测试中设置模拟演示者。一些测试可能不需要模拟演示者,因此在测试运行之前设置一个测试是没有必要的。当然,我的单元测试跨度说平均十行,如果这增加或设置测试的范围(遵循AAA-Arrange,Act Assert)很大,那么我将删除重复并创建帮助程序方法。要清除这一点,对于更清洁的测试,您可以创建一个包含帮助程序方法和其他设置代码的基本测试类。*

+1

我大多同意这个(因此+1),但也想建议你可以为模拟测试创建一个内部类,如果你将有很多使用它们的测试用例。 – jonnii 2010-01-28 22:45:00

+0

@jonni - 你击败了我的编辑。但我同意。 – Finglas 2010-01-28 22:46:54

+0

@finglas这是一个协议的圈子。让我们相互在后面拍; – jonnii 2010-01-28 22:48:49

3

是的,完全不使用[Setup]和成员变量,而是使用创建方法编写Fixture Objects

夹具对象只会保持夹具的适当模拟和其他部分。

我个人使用AutoFixture as a Fixture Object,并将它设置为自动嘲讽集装箱启动,所以我没有写任何代码模拟,除非我需要明确定义一些行为。

下面是最近的样本单元测试:

[TestMethod] 
public void DeleteProductWillDeleteProductFromRepository() 
{ 
    // Fixture setup 
    var fixture = new ServiceFixture(); 
    var id = fixture.CreateAnonymous<int>(); 
    var repMock = fixture.FreezeMoq<ProductRepository>(); 

    var sut = fixture.CreateAnonymous<ProductManagementService>(); 
    // Exercise system 
    sut.DeleteProduct(id); 
    // Verify outcome 
    repMock.Verify(r => r.DeleteProduct(id)); 
    // Teardown 
} 

在这种情况下,repMock由起订量创建的,但我可以将其设置为使用犀牛制品代替。

+1

没有[设置]或[拆解]的xUnit FTW! – mxmissile 2010-01-28 22:45:38

+0

您正在交易成员变量+初始值设定项,以便在每种方法中进行额外设置,这些方法将与多个测试相结合。 自动嘲笑是我应该看看,但谢谢。 – 2010-01-29 08:58:09

0

它与C#的一般疼痛,它不会从方法的结果(与Java不同),并且在很多情况下都很痛苦(仅举一个例子,你想实现去串行化方法)。就我个人而言,我不喜欢使用var关键字,因为我想看看到底是什么类型的对象,我宁愿跳过模板中的类型名称。

无论如何,如果我的测试非常好,我倾向于初始化每个测试用例中的所有模拟。通过这种方式,您可以查看与其他测试分离的每个测试,并立即看到发生的情况。如果他们变长了,尽管我只是用你在问题开始时粘贴的讨厌的方式。

1

Michael Feathers对此有着出色的看法(参见他的介绍http://www.ndc2010.no/index.aspx?id=361621)。创建建设者,并在测试中使用它,而不是所有类型的设置。

像:

//The Class to Test 
    public class ObjectY 
    { 
     public string DoThis(IObjectX objectX) 
     { 
      return objectX.id + objectX.name; 
     } 
    } 


    [Test] 
    //The test 
    public void CreaeteTestData() 
    { 
     //Almost prosa style creation of test data 
     var testData = new ObjectXBuilder().WithId(123).WithName("ABC").Build(); 

     Assert.That(new ObjectY().DoThis(testData), Is.EqualTo("123ABC")); 

    } 


    //The Builder class - Provides easy creation testdata. 
    internal class ObjectXBuilder 
    { 
     private MockRepository _mockRepository; 
     private IObjectX _objectX; 

     public ObjectXBuilder() 
     { 
      _mockRepository = new MockRepository(); 
      _objectX = _mockRepository.Stub<IObjectX>(); 
     } 

     public ObjectXBuilder WithName(string name) 
     { 
      _objectX.name = name; 
      return this; 
     } 

     public ObjectXBuilder WithId(long id) 
     { 
      _objectX.id = id; 
      return this; 
     } 

     public IObjectX Build() 
     { 
      _mockRepository.ReplayAll(); 
      return _objectX; 
     } 

    }