如何在单元测试中使用Mock对象并仍使用代码覆盖率?

时间:2008-12-03 05:20:10

标签: c# .net unit-testing moq code-coverage

现在我开始将Mock对象的概念引入我的单元测试中。特别是我正在使用Moq框架。但是,我注意到的一件事是突然我使用这个框架测试的类显示代码覆盖率为0%。

现在我明白了,因为我只是在嘲笑这个类,它没有运行实际的类本身....但是我如何编写这些测试并让Code Coverage返回准确的结果呢?我是否必须编写一组使用Mocks的测试和一组直接实例化该类的测试。

也许我在没有意识到的情况下做错了什么?

以下是我尝试单元测试名为“MyClass”的类的示例:

using Moq;
using NUnitFramework;

namespace MyNameSpace
{
    [TestFixture]
    public class MyClassTests
    {

        [Test]
        public void TestGetSomeString()
        {
            const string EXPECTED_STRING = "Some String!";

            Mock<MyClass> myMock = new Mock<MyClass>();
            myMock.Expect(m => m.GetSomeString()).Returns(EXPECTED_STRING);

            string someString = myMock.Object.GetSomeString();

            Assert.AreEqual(EXPECTED_STRING, someString);
            myMock.VerifyAll();

        }

    }

    public class MyClass
    {
        public virtual string GetSomeString()
        {
            return "Hello World!";
        }
    }
}

有谁知道我应该采取哪些不同的做法?

4 个答案:

答案 0 :(得分:17)

您没有正确使用模拟对象。当您使用模拟对象时,您打算测试代码如何与其他对象交互而不实际使用真实对象。请参阅以下代码:

using Moq;
using NUnitFramework;

namespace MyNameSpace
    {
        [TestFixture]
        public class MyClassTests
        {

            [Test]
            public void TestGetSomeString()
            {
                const string EXPECTED_STRING = "Some String!";

                Mock<IDependance> myMock = new Mock<IDependance>();
                myMock.Expect(m => m.GiveMeAString()).Returns("Hello World");

                MyClass myobject = new MyClass();

                string someString = myobject.GetSomeString(myMock.Object);

                Assert.AreEqual(EXPECTED_STRING, someString);
                myMock.VerifyAll();

            }

        }

        public class MyClass
        {

            public virtual string GetSomeString(IDependance objectThatITalkTo)
            {
                return objectThatITalkTo.GiveMeAString();
            }
        }

        public interface IDependance
        {
            string GiveMeAString();
        }
    }

当你的代码只返回一个没有任何逻辑的字符串时,它看起来并没有做任何有用的事情。

如果你GetSomeString()方法根据IDependdanceGiveMeAString()方法的回报改变了输出字符串的结果,那么真正的力量就来了,那么你可以看到您的方法如何处理从IDependdance接口发送的错误数据。

类似的东西:

 public virtual string GetSomeString(IDependance objectThatITalkTo {
     if (objectThatITalkTo.GiveMeAString() == "Hello World")
     return "Hi";
 }

现在,如果你的测试中有这一行:

myMock.Expect(m => m.GiveMeAString()).Returns(null);

您的GetSomeString()方法会怎样?

答案 1 :(得分:8)

大错误是嘲笑System Under Test(SUT),你测试别的东西。你应该只模拟SUT依赖。

答案 2 :(得分:2)

我建议远离模拟框架,直到你理解这里发生的交互为止。

IMO最好通过手动创建的测试双打来学习,然后再毕业到模拟框架。我的推理:

  1. 模拟框架抽象出实际发生的事情;如果必须明确创建依赖项,则更容易掌握交互,然后按照调试器中的测试进行操作。

  2. 滥用框架很容易。如果你在学习时自己动手,你更有可能理解不同类型的测试双打之间的差异。如果你直接进入一个模拟框架,当你需要存根时很容易使用模拟,反之亦然 - 这有很大的不同。

  3. 以这种方式思考:被测试的课程是焦点。您创建它的实例,调用其方法,然后断言结果是正确的。如果被测试的类具有依赖性(例如构造函数中需要某些东西),则使用A:real类或B:test double,来满足这些依赖性。

    我们使用测试双精度的原因是它隔离了被测试的类,这意味着您可以以更加可控的方式运用其代码。

    E.g。如果您有一个包含网络对象的类,则如果您被迫使用具体的网络连接对象,则无法测试拥有类的错误处理例程来检测死连接。相反,您注入一个伪连接对象并告诉它在调用其“SendBytes”方法时抛出异常。

    即。在每个测试中,专门创建被测试类的依赖关系以执行特定的代码片段。

答案 3 :(得分:0)

这很有道理。基本上你说我需要做以下事情:

public class MyClass
{
    public virtual string GetSomeString(MyOtherClass moc)
    {
        return moc.ToString();
    }
}

.....

Mock<MyOtherClass> myMock = new Mock<MyOtherClass>();

MyClass mc = new MyClass();

string someString = mc.GetSomeString(myMock.Object);
Assert.AreEqual(EXPECTED_STRING, someString);

基本上实例化SUT并且仅对SUT所需的类使用模拟?