如何在unit testing中使用模拟对象并仍使用代码覆盖率?

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

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

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

这是我尝试unit testing一个名为“MyClass”的类的示例:

using Moq; using NUnitFramework; namespace MyNameSpace { [TestFixture] public class MyClassTests { [Test] public void TestGetSomeString() { const string EXPECTED_STRING = "Some String!"; Mock myMock = new Mock(); 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!"; } } } 

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

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

 using Moq; using NUnitFramework; namespace MyNameSpace { [TestFixture] public class MyClassTests { [Test] public void TestGetSomeString() { const string EXPECTED_STRING = "Some String!"; Mock myMock = new Mock(); 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()方法做了一些逻辑,可能会改变输出字符串的结果,取决于IDependdance的返回,真正的力量来了。 GiveMeAString()方法,然后您可以看到您的方法如何处理从IDependdance接口发送的错误数据。

就像是:

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

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

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

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

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

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

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

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

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

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

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

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

即在每个测试中,被测试的类的依赖性是专门为了执行特定的代码而创建的。

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

 public class MyClass { public virtual string GetSomeString(MyOtherClass moc) { return moc.ToString(); } } ..... Mock myMock = new Mock(); MyClass mc = new MyClass(); string someString = mc.GetSomeString(myMock.Object); Assert.AreEqual(EXPECTED_STRING, someString); 

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