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

时间:2022-02-11 08:22:41

Presently I'm starting to introduce the concept of Mock objects into my Unit Tests. In particular I'm using the Moq framework. However, one of the things I've noticed is that suddenly the classes I'm testing using this framework are showing code coverage of 0%.

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

Now I understand that since I'm just mocking the class, its not running the actual class itself....but how do I write these tests and have Code Coverage return accurate results? Do I have to write one set of tests that use Mocks and one set to instantiate the class directly.

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

Perhaps I am doing something wrong without realizing it?

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

Here is an example of me trying to Unit Test a class called "MyClass":

这是我尝试单元测试一个名为“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!";
        }
    }
}

Does anyone know what I should be doing differently?

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

4 个解决方案

#1


16  

You are not using your mock objects correctly. When you are using mock objects you meant to be testing how your code interacts with other objects without actually using the real objects. See the code below:

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

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

It doesn't look like it is doing anything useful when your code is just returning a string without any logic behind it.

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

The real power comes if you GetSomeString() method did some logic that may change the result of the output string depending on the return from the IDependdance .GiveMeAString() method, then you can see how your method handles bad data being sent from the IDependdance interface.

如果你的GetSomeString()方法做了一些逻辑可能会改变输出字符串的结果取决于IDependdance .GiveMeAString()方法的返回,那么你可以看到你的方法如何处理从IDependdance发送的错误数据,真正的力量来了接口。

Something like:

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

Now if you have this line in your test:

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

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

What will happen to your GetSomeString() method?

你的GetSomeString()方法会发生什么?

#2


7  

Big mistake is mocking the System Under Test (SUT), you test something else. You should mock only SUT dependencies.

最大的错误是嘲笑被测系统(SUT),你测试别的东西。你应该只模拟SUT依赖项。

#3


2  

I would recommend staying away from mocking frameworks until you understand the interactions that are going on here.

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

IMO it's better to learn with manually created test doubles, then graduate to a mocking framework afterwards. My reasoning:

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

  1. Mocking frameworks abstract away what's actually happening; it's easier to grasp the interactions if you have to create your dependencies explicitly, then follow the tests in the debugger.

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

  2. It's easy to misuse frameworks. If you roll your own when you're learning, you are more likely to understand the differences between different type of test doubles. If you go straight to a mocking framework, it's easy to use mocks when you wanted stubs and vice versa -- there is a big difference.

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

Think of it this way: The class under test is the focus. You create an instance of it, call its methods and then assert that the result is correct. If the class under test has dependencies (e.g. something is required in the constructor), you satisfy those dependencies using either A: real classes or B: test doubles.

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

The reason we use test doubles is that it isolates the class under test, meaning that you can exercise its code in a more controlled fashion.

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

E.g. if you have a class that contains a network object, you cannot test the owning class's error handling routines that detect dead connections if you're forced to use a concrete network connection object. Instead, you inject a fake connection object and tell it to throw an exception when its "SendBytes" method is called.

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

I.e. In each test, the dependencies of the class under test are created specifically to exercise a particular piece of code.

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

#4


0  

That makes a lot of sense. Essentially you're saying that I need to be doing the following:

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

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

Essentially instantiating the SUT and only using mocks for the classes the SUT requires?

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

#1


16  

You are not using your mock objects correctly. When you are using mock objects you meant to be testing how your code interacts with other objects without actually using the real objects. See the code below:

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

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

It doesn't look like it is doing anything useful when your code is just returning a string without any logic behind it.

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

The real power comes if you GetSomeString() method did some logic that may change the result of the output string depending on the return from the IDependdance .GiveMeAString() method, then you can see how your method handles bad data being sent from the IDependdance interface.

如果你的GetSomeString()方法做了一些逻辑可能会改变输出字符串的结果取决于IDependdance .GiveMeAString()方法的返回,那么你可以看到你的方法如何处理从IDependdance发送的错误数据,真正的力量来了接口。

Something like:

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

Now if you have this line in your test:

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

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

What will happen to your GetSomeString() method?

你的GetSomeString()方法会发生什么?

#2


7  

Big mistake is mocking the System Under Test (SUT), you test something else. You should mock only SUT dependencies.

最大的错误是嘲笑被测系统(SUT),你测试别的东西。你应该只模拟SUT依赖项。

#3


2  

I would recommend staying away from mocking frameworks until you understand the interactions that are going on here.

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

IMO it's better to learn with manually created test doubles, then graduate to a mocking framework afterwards. My reasoning:

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

  1. Mocking frameworks abstract away what's actually happening; it's easier to grasp the interactions if you have to create your dependencies explicitly, then follow the tests in the debugger.

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

  2. It's easy to misuse frameworks. If you roll your own when you're learning, you are more likely to understand the differences between different type of test doubles. If you go straight to a mocking framework, it's easy to use mocks when you wanted stubs and vice versa -- there is a big difference.

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

Think of it this way: The class under test is the focus. You create an instance of it, call its methods and then assert that the result is correct. If the class under test has dependencies (e.g. something is required in the constructor), you satisfy those dependencies using either A: real classes or B: test doubles.

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

The reason we use test doubles is that it isolates the class under test, meaning that you can exercise its code in a more controlled fashion.

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

E.g. if you have a class that contains a network object, you cannot test the owning class's error handling routines that detect dead connections if you're forced to use a concrete network connection object. Instead, you inject a fake connection object and tell it to throw an exception when its "SendBytes" method is called.

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

I.e. In each test, the dependencies of the class under test are created specifically to exercise a particular piece of code.

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

#4


0  

That makes a lot of sense. Essentially you're saying that I need to be doing the following:

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

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

Essentially instantiating the SUT and only using mocks for the classes the SUT requires?

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