Как я могу использовать Mock Objects в своих модульных тестах и ​​использовать Code Coverage?

В настоящее время я начинаю вводить концепцию объектов Mock в свои модульные тесты. В частности, я использую структуру Moq. Тем не менее, одна из вещей, которые я заметил, это то, что внезапно классы, которые я тестирую с использованием этой структуры, показывают покрытие кода 0%.

Теперь я понимаю, что, поскольку я просто издеваюсь над классом, он не запускает сам фактический класс.... но как я могу написать эти тесты и получить Code Coverage верные результаты? Должен ли я писать один набор тестов, которые используют Mocks и один набор для непосредственного создания класса.

Возможно, я что-то делаю неправильно, не осознавая этого?

Вот пример того, как я пытаюсь выполнить Unit Test класс под названием 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!";
        }
    }
}

Кто-нибудь знает, что я должен делать по-другому?

Ответ 1

Вы неправильно используете свои макетные объекты. Когда вы используете макеты, вы должны тестировать, как ваш код взаимодействует с другими объектами, фактически не используя реальные объекты. См. Код ниже:

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() использовали некоторую логику, которая может изменить результат выходной строки в зависимости от возврата из метода IDependdance. GiveMeAString(), тогда вы можете увидеть, как ваш метод обрабатывает плохие данные отправляется из интерфейса IDependdance.

Что-то вроде:

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

Теперь, если у вас есть эта строка в вашем тесте:

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

Что произойдет с вашим методом GetSomeString()?

Ответ 2

Большая ошибка издевается над System Under Test (SUT), вы проверяете что-то еще. Вы должны высмеивать только зависимости SUT.

Ответ 3

Я бы рекомендовал держаться подальше от насмешливых фреймворков, пока вы не поймете взаимодействия, которые здесь происходят.

ИМО лучше учиться с помощью вручную созданных тестовых удвоений, а затем заканчивать на насмешливую структуру. Мои рассуждения:

  • Издевательские рамки абстрагируются от того, что на самом деле происходит; проще понять взаимодействие, если вы должны явно создавать свои зависимости, а затем выполнить тесты в отладчике.

  • Легко злоупотреблять каркасами. Если вы катитесь самостоятельно, когда вы учитесь, вы с большей вероятностью понимаете различия между различными типами двойных тестов. Если вы переходите прямо к насмешливой структуре, легко использовать макеты, когда вы хотите заглушки, и наоборот - есть большая разница.

Подумайте об этом так: класс под тестом - это фокус. Вы создаете его экземпляр, вызываете его методы, а затем утверждаете, что результат верен. Если тестируемый класс имеет зависимости (например, что-то требуется в конструкторе), вы удовлетворяете этим зависимостям, используя либо A: реальные классы, либо B: test double.

Причина, по которой мы используем тестовые двойники, заключается в том, что она изолирует тестируемый класс, что означает, что вы можете осуществлять свой код более контролируемым образом.

например. если у вас есть класс, который содержит сетевой объект, вы не можете протестировать процедуры обработки ошибок собственного класса, которые обнаруживают мертвые соединения, если вы вынуждены использовать конкретный объект сетевого подключения. Вместо этого вы вводите объект поддельного подключения и указываете ему, чтобы он генерировал исключение, когда вызывается его метод "SendBytes".

т.е. В каждом тесте зависимости тестируемого класса создаются специально для реализации определенной части кода.

Ответ 4

Это имеет большой смысл. По сути, вы говорите, что мне нужно делать следующее:

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 и используя только mocks для классов, которые требует SUT?