测试界面而不是实现的用途是什么?

时间:2015-07-08 00:16:44

标签: c# unit-testing nsubstitute

我是nSubstitute的新手。我在很多文章中都看过这样的代码示例。什么用于测试界面?以下测试何时失败?以下测试是否会因IMathService的实际实现而失败?这里的想法是首先在TDD中写这个,然后删除替代品并把真正的实现?或者想法是测试一些真正的实现,但用Nsubstitute替换依赖关系?如果是,这个例子没有显示出来。我很困惑。

public interface IMathService
{
    int Add(int a, int b);
}

[Test]
public void nSubstituteMockingInterface()
{
    var mathService = Substitute.For<IMathService>();
    mathService.Add(2,3).Returns(4);
    Assert.IsTrue(mathService.Add(2, 3) == 4);
}

让我说我有以下实施,上述测试会失败吗? Substitute.For()行会返回MyMathService的实例吗?我相信,它不是。如果是,如果我有多个IMathService实现

会发生什么
class MyMathService:  IMathService
{
    public int Add(int a, int b)
    {
        throw new Exception("error");
    }
}

1 个答案:

答案 0 :(得分:2)

您完全正确,这些片段不是您通常使用隔离框架的方式。这些只是展示如何使用nSubsitute库的用法示例。在您的示例中,测试将通过,因为它使用的是动态生成的对象,该对象遵循IMathService接口,但编程为在使用参数2和3调用其Add方法时返回4.实际的实现不会在任何地方使用测试

nSubstitute及其替代品可用于类及其协作者的交互测试。您可以使用这些类型的隔离框架来单独测试类。通常,您可以创建受测系统(SUT)的具体实例,但为其协作者提供虚假实现。然后,您可以设置对假协作者的期望,或断言调用某些方法来验证SUT是否与其协作者正确交互。您可以使用隔离框架为您自动执行重复性工作,而不是手动滚动自己的假合作者。

一个例子希望能让事情变得更加清晰。假设我正在研究Foo类,它正在使用我还没有实现的合作者IBar。 IBar将与一个真实的数据库交谈,所以我不想在我的Foo隔离单元测试中使用真正的IBar实现:

public class Foo
{
    private readonly IBar _bar;

    public Foo(IBar bar)
    {
        _bar = bar;
    }

    public void DoSomething()
    {
        _bar.DoSomethingElse();
    }
}

public interface IBar
{
    void DoSomethingElse();
}

public class AnImplementationOfBar : IBar
{
    public void DoSomethingElse()
    {
        throw new System.NotImplementedException();
    }
}

[Test]
public void Foo_interacts_correctly_with_its_bar()
{
    var fakeBar = Substitute.For<IBar>();
    var foo = new Foo(fakeBar);

    foo.DoSomething();

    fakeBar.Received().DoSomethingElse(); //This is the assertion of the test and verifies whether fakeBar.DoSomethingElse has been called.
}

public static void Main(string[] args)
{
    //Production code would look like this:
    var foo = new Foo(new AnImplementationOfBar());
    //next line throws because AnImplementationOfBar.DoSomethingElse has not been implemented yet
    foo.DoSomething();
}

替代品可以在大致两种情况下使用:

  1. 检查是否发生了正确的交互(如上面我使用Received()方法回答的代码示例)。通常, mock 这个词用于伪造。
  2. 向SUT提供虚拟数据,以便生产代码执行某个代码路径。通常, stub 这个词用于这种假货。 IMathService测试是使用Returns()方法配置存根的典型示例。
  3. 如果您熟悉单元测试的Arrange-Act-Assert定义:Stub是测试的 Arrange 阶段的一部分,因为它们设置了您的SUT所需的一些虚拟数据。模拟是断言部分的一部分,因为您使用它们来验证是否发生了特定的交互。

    这种技术在某种称为外向TDD(也称为伦敦学校TDD)的测试驱动开发中大量使用。它允许您完全实现一个类并为其协作者指定合同,而无需实际实现协作者本身,而只是创建接口。它也可以在其他场景中使用,例如,当您进行单元测试并且不想使用真实数据库,网络流量,第三方依赖关系和/或一个非常困难或使用缓慢的类时测试

    有关这些主题的更多信息,请查看Martin Fowler在Mocks Aren't Stubs上的帖子。