Mock-объект предоставляет фиктивную реализацию, другими словами, Mock-объект лишь имитирует поведение. Приведу пару ситуаций, когда имитирование поведения будет кстати:
- Параллельная разработка, заглушка.
Представьте ситуацию, вы работаете в паре параллельно над одной задачей, причем Ваша задача напрямую зависит от задачи напарника. Например, Ваш напарник реализует Repository-класс для предоставления доступа к данным, в то время как Вы реализуете Service-класс, содержащий основную логику приложения и использующий данный Repository-класс. Оптимальное решение - реализовать Mock-repository, и отлаживать свой код не дожидаясь напарника.
- Тестирование.
Часто бывает, что во время тестирования возникает проблема воспроизведения конкретной тестовой ситуации: Вы тестируете модуль, который зависит от другого стороннего модуля, повлиять на работу которого Вы никак не можете, например, Ваше приложение и сторонний WCF-сервис. И в этом случае, имитацию WCF-прокси может произвести наш Mock-объект.
Вы спросите: "Как же получить этот Mock-объект? И как он тогда наделяется поведением?". Отвечу, что все дело в Mock-фреймворке. Как следует из названия, именно он выполняет роль контейнера для Mock-объектов. На текущий момент существует длинный список Mock-фреймвоков: Moq, NMock, Rhino Mocks и другие, но в этой статье в качестве примера рассмотрим только один из них Moq. Следует отметить, что Moq адаптирован под .NET 3.5, поддерживает лямбда выражения и является строго типизированным. Эти особенности делают его простым, продуктивным и удобным в использовании. Помимо этого, Moq позволяет имитировать работу методов(methods), свойств(properties), событий(events). Поддерживает out/ref параметры, валидацию процесса имитирования Mock-объекта и управление поведением(Mock Behavior).
Для того чтобы начать работу необходимо скачать и добавить ссылку в проекте на Moq.dll. В качестве простого(подробно здесь) примера создадим Mock-объект для следующего интерфейса:
public interface IPing
{
bool Ping(string host);
}
Со следующим тестовым поведением: метод Ping возвращает true только для google.com без учета регистра, для остальных доменов-false, но при пустом аргументе метод должен генерировать исключение. Видно, что метод Ping описывает простейшую модель типа вход(имя хоста)-обработка(отправка/получение ICMP Echo Request/Reply запроса/ответа)-выход(результат, прошел ли запрос или нет). Поэтому, для таких типов моделей, у которых известен вход и выход, мы можем просто выполнить имитацию процесса обработки, при этом потратив минимум времени, не усложняя код:
// Mock-фреймворк для интерфейса IPing
Mock<IPing> mock = new Mock<IPing>();
// Настроим IPing.Ping(host)
mock
.Setup
(
mockPing => mockPing
.Ping
(
It.Is<string>
(
host => host
.ToLower()
.Equals("google.com")
)
)
)
.Returns(true);
// При пустом аргументе выбрасываем исключение.
mock
.Setup
(
mockPing => mockPing.Ping(string.Empty)
)
.Throws<ArgumentNullException>();
// ping - mock объект.
// Далее используем его, как искомый объект.
IPing ping = mock.Object;
Стоит отметить, что большинство Mock-фреймворков (в том числе и Moq), являются контейнерами для Mock-объектов, что позволяет им следить за всеми вызовами/обращениями к методам/свойствам соответствующего Mock-объекта и с какими параметрами. К примеру:
// Если метод Ping ни разу не вызывался,
// здесь будет выброшено исключение MockException
mock.Verify(mockPing => mockPing.Ping(It.IsAny<string>()), "Метод Ping не вызывался.");
// Если метод Ping вызывался хоть раз с пустым параметром и
// исключение ArgumentNullException было обработано,
// здесь будет выброшено исключение MockException
mock.Verify(mockPing => mockPing.Ping(string.Empty), Times.Never());
Многие могут заметить, что в методе Setup получось много кода, который можно было бы вынести, для этого Moq позволяет создать custom-предикат:
mock
.Setup
(
mockPing => mockPing.Ping(IsGoogle())
)
.Returns(true);
// Предикат.
static string IsGoogle()
{
return Match<string>
.Create
(
host => host
.ToLower()
.Equals("google.com")
);
}
Вывод: если рассматривается модель типа вход-обработка-выход, у которой известен вход, а так же выход(реакция на вход) системы, то использование Mock-объектов будет оптимальным решением, а задание поведения Mock-объекта сведется к простому сопоставлению входа и выхода системы без применения сложной логики. Следует помнить тривиальное правило, что усилия, потраченные на реализацию Mock-функционала должны быть намного меньше, чем усилия, потраченные на реализацию искомого функционала. Mock-решение - простое, быстрое и временное решение.
Как раз недавно изучил Moq.
ОтветитьУдалитьОчень уж простое описание. Примеров бы побольше. Но за труды все равно спасибо.
@M
ОтветитьУдалитьСпасибо за отзыв. Но я стремился передать читателю что такое Mocking и главное, где это можно применять, а чтобы не быть слишком абстрактным привел простой пример на Moq.
Насколько я понял цель статьи не подбор примеров, а скорее описание проблемы, решение которой подводит нас к использованию Mocking framework'а.
ОтветитьУдалитьБыло бы неплохо привести еще подборку хороших ссылок по этому фреймверку, чтобы читатель мог дальше знакомиться с subj.
Если будет желание, то я периодически сбрасывал ссылки с комментариями касательно mocking'а в свой твиттер: http://twitter.com/alexey_diyan
На всякий случай отмечу, что я предпочитаю все же Rhino.Mock (несмотря на его перегруженный API), поэтому мои твитты нельзя считать нейтральными :)
@All
ОтветитьУдалитьДобавил простой пример, демонстрирующий работу Verify-методов Moq.
Добавил простой пример, демонстрирующий работу Match-методов Moq.
Вообще, идея работы с Mock простая, мы описываем множество входных параметров (Moq.It.*) которому сопоставляем множество выходов (Moq.Language.IReturns).
Доброго дня! А можно выложить проект с примером Moq
ОтветитьУдалитьhttp://moqexamples.codeplex.com/
ОтветитьУдалитьСпасибо за ссылку пример.
ОтветитьУдалитьMock - объекты всегда используются для поведенческого тестирования. Для простых unit тестов они применимы?
Да, например, мы хотим протестировать парсер SomeParser, который имеет зависимость на объект типа IPacket, и чтобы не имплементировать IPacket мы построим для него mock-объект:
ОтветитьУдалитьMock<IPacket> packet= ...;
IParser parser = new SomeParser();
var expected = ....
var actual = parser.Parse(packet.Object);
Assert.Equals(expected, actual)