Как я могу использовать Mock Objects в своих модульных тестах и ​​по-прежнему использовать покрытие кода? - PullRequest
16 голосов
/ 03 декабря 2008

В настоящее время я начинаю вводить концепцию объектов 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 ]

17 голосов
/ 03 декабря 2008

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

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() методом?

8 голосов
/ 03 декабря 2008

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

2 голосов
/ 06 июня 2009

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

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

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

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

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

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

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

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

0 голосов
/ 03 декабря 2008

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

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?

...