9

我有 2 节课:

  • 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);
        }
    }

在那之后我遇到了麻烦,我不知道在我的测试类中为方法SomethingToDo 编写存根有多正确。我总是假的。我应该返回 TRUE。但是怎么做?

4

2 回答 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在您的测试中,您可以为whereSomethingToDo始终返回 true创建一个存根:

class SecondDeepStub : ISecondDeep {

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

}

在测试中,您使用存根:

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

在生产代码中,您使用“真实” SecondDeep

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

使用依赖注入容器和存根框架可以使很多事情变得更容易。

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

于 2012-05-08T09:01:40.683 回答
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"));
     }
}
于 2012-05-08T09:02:44.537 回答