如何在C#中使用NUnit编写存根方法

9

我有两个类:

  • FirstDeep.cs
  • SecondDeep.cs

    我举了一个简单的例子:


class FirstDeep
    {
        public FirstDeep() { }
public string AddA(string str) { SecondDeep sd = new SecondDeep(); bool flag = sd.SomethingToDo(str);
if (flag == true) str = string.Concat(str, "AAA"); else str = string.Concat(str, "BBB");
return str; } }

还有:

class SecondDeep
    {
        public bool SomethingToDo(string str)
        {
            bool flag = false;
            if (str.Length < 10)
            {
                //todo something in DB, and after that flag should be TRUE
            }
            return flag;
        }
    }

然后我想为方法“AddA”编写单元测试:
class Tests
    {
        [Test]
        public void AddATest()
        {
            string expected = "ABCAAA";

            FirstDeep fd = new FirstDeep();
            string res = fd.AddA("ABC");

            Assert.AreEqual(expected, res);
        }
    }

在那之后我遇到了麻烦,我不知道如何正确编写Test类中SomethingToDo方法的存根。我经常得到false的返回值,但我只想要返回TRUE。但是该怎么做呢?


你仍然可以使用你的模式:引入 bool expected = false; SecondDeep sd = new SecondDeep(); bool actualResult = sd.SomethingToDo("ABC"); Assert.AreEqual(excpected, actualResult); ...!?如果这不能满足你的需求,你应该考虑详细阐述和改进你的问题! - user57508
你调试过你的代码吗?如果问题出现在数据库中,我们无法帮助你,因为我们没有关于那里发生了什么的详细信息。 - Jon
是的,我调试了我的代码,在我写的地方:“//在数据库中执行某些操作,之后标志应该为TRUE”,我使用了.NET中的MembershipUser类,但这个方法无法连接到数据库,所以在这种情况下我只能返回true。 - Smit
2个回答

12

让你编写桩件的好方法是使用依赖注入。在您的测试中,FirstDeep 依赖于 SecondDeep,您希望将 SecondDeep 替换为一个存根。

首先更改现有代码,提取一个 SecondDeep 的接口,然后在构造函数中将其注入到 FirstDeep 中:

interface ISecondDeep {

  Boolean SomethingToDo(String str);

}

class SecondDeep : ISecondDeep { ... }

class FirstDeep {

  readonly ISecondDeep secondDeep;

  public FirstDeep(ISecondDeep secondDeep) {
    this.secondDeep = secondDeep;
  }

  public String AddA(String str) {   
    var flag = this.secondDeep.SomethingToDo(str);
    ...
  }

}
请注意,FirstDeep不再创建SecondDeep实例。相反,在构造函数中注入一个实例。
在您的测试中,可以为ISecondDeep创建一个存根,其中SomethingToDo始终返回true:
class SecondDeepStub : ISecondDeep {

  public Boolean SomethingToDo(String str) {
    return true;
  }

}

在测试中,您使用存根:

var firstDeep = new FirstDeep(new SecondDeepStub());

在生产代码中,您将使用“真正的”SecondDeep

var firstDeep = new FirstDeep(new SecondDeep());

使用依赖注入容器和桩(mock)测试框架可以让很多事情变得更加容易。

如果您不想重写代码,可以使用一个拦截调用的框架,比如Microsoft Moles。在Visual Studio的下一个版本中,类似的技术将会在Fakes Framework中提供。


4
为了使您的代码可测试,不要在类内部实例化依赖项。使用依赖注入(通过构造函数、属性或参数)。还要使用抽象类或接口来允许模拟依赖项:
class FirstDeep
{
    private ISecondDeep oa;

    public FirstDeep(ISecondDeep oa) 
    { 
        this.oa = oa;
    }

    public string AddA(string str)
    {
       return String.Concat(str, oa.SomethingToDo(str) ? "AAA" : "BBB");
    }
}

依赖抽象使您能够单独测试类。
interface ISecondDeep
{
   bool SomethingToDo(string str);
}

class SecondDeep : ISecondDeep
{
    public bool SomethingToDo(string str)
    {
       bool flag = false;
       if (str.Length < 10)
       {
           // without abstraction your test will require database
       }
       return flag;
    }
}

这是一个测试示例(使用Moq)。它向您展示了如何从对模拟依赖项的调用中返回true
[TestFixture]
class Tests
{
    [Test]
    public void AddAAATest()
    {
        // Arrange
        Mock<ISecondDeep> secondDeep = new Mock<ISecondDeep>();
        secondDeep.Setup(x => x.SomethingToDo(It.IsAny<string>())).Returns(true);
        // Act
        FirstDeep fd = new FirstDeep(secondDeep.Object);
        // Assert
        Assert.That(fd.AddA("ABD"), Is.EqualTo("ABCAAA"));
     }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接