简体   繁体   English

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

[英]How can I use Mock Objects in my unit tests and still use Code Coverage?

Presently I'm starting to introduce the concept of Mock objects into my Unit Tests.目前我开始在我的单元测试中引入 Mock 对象的概念。 In particular I'm using the Moq framework.特别是我使用的是 Moq 框架。 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%.但是,我注意到的一件事是,我正在使用此框架测试的类突然显示代码覆盖率为 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.我是否必须编写一组使用 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?有谁知道我应该以不同的方式做什么?

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 .如果您的GetSomeString()方法执行了一些逻辑,这些逻辑可能会根据IDependdance的返回值更改输出字符串的结果,那么真正的力量就来了。 GiveMeAString() method, then you can see how your method handles bad data being sent from the IDependdance interface. GiveMeAString()方法,然后您可以看到您的方法如何处理从IDependdance接口发送的坏数据。

Something like:就像是:

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

Now if you have this line in your test:现在,如果您的测试中有这一行:

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

What will happen to your GetSomeString() method?你的GetSomeString()方法会发生什么?

Big mistake is mocking the System Under Test (SUT), you test something else.大错误是嘲笑被测系统(SUT),而您测试的是其他东西。 You should mock only SUT dependencies.您应该只模拟 SUT 依赖项。

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. IMO 最好使用手动创建的测试替身来学习,然后再升级到模拟框架。 My reasoning:我的推理:

  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 (eg something is required in the constructor), you satisfy those dependencies using either A: real classes or B: test doubles.如果被测类有依赖项(例如,构造函数中需要某些东西),您可以使用 A: real classes 或 B: test doubles 来满足这些依赖项。

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.我们使用测试替身的原因是它隔离了被测类,这意味着您可以以更可控的方式练习其代码。

Eg 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”方法时抛出异常。

Ie In each test, the dependencies of the class under test are created specifically to exercise a particular piece of code.即在每个测试中,被测类的依赖项是专门创建的,以执行特定的代码段。

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所需的类使用模拟吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM