5

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

我的项目的当前结构

/BaseTest              
   接口 IFoo
-------------------------------------
/单元测试
   FakeFoo 类:IFoo

   [测试夹具]
   class FooTest {...} //使用 FakeFoo
-------------------------------------
/集成测试
   类 RealFoo : IFoo

   [测试夹具]
   class FooTest {...} //使用 RealFoo

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

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

我希望这个测试在两种实现中运行:RealFooFakeFoo. 到目前为止,我考虑过在/UnitTest/IntegrationTest项目之间复制粘贴测试,但这听起来不对。

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

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

4

3 回答 3

5

即使其他人的答案很好,这就是我最终做的

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

[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。这些类将只有 theSetUp而没有别的。IE:

[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运行两次,用于单元测试类和集成测试类,它们有自己的真实对象。这是因为测试夹具的继承性。

于 2012-07-25T14:09:36.990 回答
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因为它已经过全面测试。

于 2012-07-25T13:29:05.390 回答
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一部分。Act并且Assert可能是相同的(如果您只进行状态测试,而不进行交互测试)。但Arrange完全不同。它不仅仅是创建直接依赖的实例。如果您使用模拟,您应该为 SUT 使用的成员设置返回结果。这足以重现测试场景。但是对于真实对象,您应该将堆栈中的所有依赖项设置为当前测试场景所需的某种状态。

于 2012-07-25T13:41:12.517 回答