是否可以重用代码进行集成和单元测试?

时间:2012-07-25 13:22:07

标签: c# unit-testing language-agnostic tdd integration-testing

我使用具有单元和集成测试的分布式系统。我试图通过在集成和单元测试之间重用代码来节省时间和维护工作。为此我实现了一个接口和2个类:假和真。 Fake 类返回一些存根数据,而 real 类会调用其他分布式服务。

我项目的当前结构

/BaseTest              
   interface IFoo
-------------------------------------
/UnitTest
   class FakeFoo : IFoo

   [TestFixture]
   class FooTest {...} //uses FakeFoo
-------------------------------------
/IntegrationTest
   class RealFoo : IFoo

   [TestFixture]
   class FooTest {...} //uses RealFoo

我想以某种方式重用两个测试的代码,所以如果我有一个测试

[Test]
public void GetBarIsNotNullTest()
{
    var foo = IoC.Current.Resolve<IFoo>();
    Bar actual = foo.GetBar();
    Assert.IsNotNull(actual);   
}

我希望此测试与两种实现一起运行:RealFooFakeFoo。到目前为止,我想到了 / UnitTest / IntegrationTest 项目之间的复制粘贴测试,但这听起来并不合适。

系统是用C#编写的,但我相信这个问题与语言无关。

任何人都有更好的想法?我做错了吗?

3 个答案:

答案 0 :(得分:4)

即使其他人在答案中有好点,这也是我最终做的事情

我为单元和集成测试创建了一个基类

[TestFixture]
public class FooBase
{
    [Test]
    public void GetBarIsNotNullTest()
    {
        var foo = IoC.Current.Resolve<IFoo>();
        Bar actual = foo.GetBar();
        Assert.IsNotNull(actual);   
    }

    //many other tests  
}

然后是来自FooBase的两个派生类。这些类只有SetUp而没有别的。即:

[TestFixture]
public class UnitTestFoo : FooBase
{
    [SetUp]
    public void SetUp()
    {
        IoC.Current.Register<IFoo, FakeFoo>();        
    }

    //nothing else here
}

[TestFixture]
public class IntegrationTestFoo : FooBase
{
    [SetUp]
    public void SetUp()
    {
        IoC.Current.Register<IFoo, RealFoo>();        
    }

    //nothing else here
}

因此,如果我现在运行我的测试,我会在父类FooBase中定义的测试为单元测试类和集成测试类运行两次,使用自己的真实假的对象。这是因为测试装置的继承。

答案 1 :(得分:2)

您的测试场景存在严重错误。

让我们先看看单元测试。您有一个依赖关系存根,可以提供可预测的结果。你有CUT应该根据存根配置给出预期的结果。到目前为止一切都很好。

但是。如果要重用测试代码(断言)进行集成测试,实际上这意味着您希望真实依赖项实现产生与单元测试中存根相同的结果。如果是这样,那么为什么不测试您的依赖关系来提供这些结果并跳过整个代码层?

<强>更新

你的例子是错的。 FakeFoo是一个存根,应该进行测试。您可以使用存根来测试某些服务上依赖的类。因此,我们假设您正在测试一些取决于Bar的课程IFoo,这意味着:

[Test]
public void GetBarIsNotNullTest()
{
    var bar = IoC.Current.Resolve<Bar>();
    var actual = bar.GetDon();
    Assert.IsNotNull(actual);   
}

并且您在测试中使用了IFoo的不同实现。

澄清我的立场

由于您在测试中复制了Act和Assert阶段,因此很可能在 CUT Bar)中测试相同的代码路径。这意味着测试重复并不比代码重复更好。

你应该确保你的CUT(Bar)在所有代码路径上都很好用假货(这将是单元测试)。然后,您应该确保依赖RealFoo)返回预期的数据(这将是集成测试,因为它适用于分布式服务)。我们无需使用BarRealFoo进行测试,因为它已经过全面测试。

答案 2 :(得分:1)

编写自己的虚假实现不会节省您的时间。创建依赖模拟要容易得多:

Mock<IFoo> fooMock = new Mock<IFoo>();

最糟糕的是设置你的模拟对象。您可以为不同的测试场景设置不同的结果:

fooMock.Setup(f => f.Bar).Returns(true);
// or
fooMock.Setup(f => f.Bar).Returns(false);

这是假的。您将拥有一个Bar属性的实现,该属性将返回truefalse

更新:单元测试和集成测试之间的唯一区别是测试的Arrange部分。 ActAssert可能相同(如果您只进行状态测试,而不进行交互测试)。但Arrange完全不同。它不只是创建直接依赖的实例。如果使用mock,则应为SUT使用的成员设置返回结果。这足以重现测试场景。但是对于真实对象,您应该将堆栈中的所有依赖项设置为某种状态,这是当前测试场景所需的。