Под тестированием с использованием моков понимается модульное тестирование с использованием моков в качестве заменителей реальных объектов. Под реальными объектами я подразумеваю объекты, которые тестируемый модуль (класс) будет использовать в реальном приложении. Если у вас есть класс
Calculator
, которому для загрузки данных из базы данных требуется объект dao (Data Access Object), то объект dao — это «реальный объект». Чтобы протестировать класс Calculator
, необходимо предоставить ему объект dao, имеющий корректное соединение с базой данных. Кроме того, нужно добавить в базу данные, необходимые для тестирования.Установка соединения и вставка данных в базу может оказаться очень трудоемкой задачей. Вместо этого можно предоставить экземпляру
Calculator
поддельный класс dao, который просто возвращает данные, необходимые для теста. На самом деле поддельный класс dao не будет считывать данные из базы данных. Поддельный класс dao — это мок-объект. Это замена реального объекта, которая облегчает тестирование класса Calculator
. Истинные ценители моков назвали бы такой поддельный dao стабом. К этому различию в терминах я вернусь позже.Ситуацию с использованием классом
Calculator
объекта dao можно обобщить до «модуля, использующего коллаборатор (пояснение: любой объект, используемый целевым тестируемым объектом)». Модулем является Calculator
, а коллаборатором — объект dao. Я выражу это следующим образом:unit --> collaborator
Стрелка означает «использует». Когда коллаборатор работает с моком или стабом, это будет выражено следующим образом:
unit --> mock
В ситуации с модульным тестом это будет выглядеть так:
unit test --> unit --> collaborator
… или…
unit test --> unit --> mock
Стабы, моки и прокси
Существует три типа фейковых объектов, используемых для тестирования: стабы, моки и прокси. Помните, что стаб, мок или прокси заменяет одного из коллабораторов тестируемого модуля во время модульного тестирования. Стабы и моки соответствуют определению Мартина Фаулера.
Стаб — это объект, реализующий интерфейс компонента, но вместо того, чтобы возвращать то, что компонент вернет при вызове, стаб может быть настроен на возврат значения, соответствующего тесту. В случае применения стабов модульный тест проверит, может ли модуль обрабатывать различные возвращаемые значения от своего коллаборатора. Использование стаба вместо реального коллаборатора в модульном тесте может быть выражено вот так:
- модульный тест --> стаб
- модульный тест --> модуль --> стаб
- модульный тест подтверждает результаты и состояние модуля
Сначала в модульном тесте создается стаб и настраиваются его возвращаемые значения. Затем модульный тест создает модуль и устанавливает на него стаб. Теперь модульный тест вызывает модуль, который, в свою очередь, вызывает стаб. Наконец, модульный тест делает проверки результатов вызова метода на модуле.
Мок похож на стаб, только у него есть методы, позволяющие определить, какие методы были вызваны на моке. Таким образом, с помощью мока можно протестировать как корректность работы модуля с различными возвращаемыми значениями, так и корректность использования им коллаборатора. Например, по значению, возвращаемому объектом dao, нельзя определить, были ли данные прочитаны из базы данных с помощью
Statement
или PreparedStatement
. Также нельзя понять, был ли вызван метод connection.close()
перед возвратом значения. Это возможно в случае применения моков. Другими словами, моки позволяют полностью протестировать взаимодействие модуля с коллаборатором. А не только те методы коллаборатора, которые возвращают значения, используемые модулем. Использование мока в модульном тесте может быть выражено следующим образом:- модульный тест --> мок
- модульный тест --> модуль --> мок
- модульный тест подтверждает результаты и состояние модуля
- модульный тест подтверждает методы, вызываемые на моке
Сначала в модульном тесте создается мок и настраиваются его возвращаемые значения. Затем модульный тест создает модуль и на него устанавливается мок. Теперь модульный тест вызывает модуль, который, в свою очередь, вызывает мок. Наконец, модульный тест проверяет результаты вызова метода на модуле. Также в модульном тесте проверяется, какие методы были вызваны на моке.
Прокси в мок-тестировании — это мок-объекты, которые делегируют вызовы методов реальным объектам-коллабораторам, но при этом записывают, какие методы были вызваны на прокси. Таким образом, прокси позволяет проводить мок-тестирование с реальными коллабораторами. Использование прокси в модульном тесте может быть выражено следующим образом:
- модульный тест --> коллаборатор
- модульный тест --> прокси
- модульный тест --> модуль --> прокси --> коллаборатор
- модульный тест подтверждает результат и состояние юнита
- модульный тест делает ассерты для методов, вызываемых на прокси
Сначала в модульном тесте создается коллаборатор. Затем в модульном тесте создается прокси для коллаборатора. Далее в модульном тесте создается модуль и на него устанавливается прокси. Теперь модульный тест вызывает модуль, который, в свою очередь, вызывает прокси. Наконец, модульный тест делает утверждения о результатах вызова методов на модуле. Также в модульном тесте делаются утверждения о том, какие методы были вызваны на прокси.
Тестирование с применением стабов, моков и прокси с помощью Testimonial
Существует несколько популярных API для мок-тестирования. Среди них JMock и EasyMock. На момент написания статьи эти два API не поддерживают прокси, как описано выше. Примечание: Они *используют* экземпляры
java.lang.reflect.Proxy
для реализации своих динамических моков. Но это не то же самое, что тестовые прокси, описанные выше. Эти API могут использоваться только со стабами и моками, а не с прокси для реальных коллабораторов. Для примеров кода в этом тексте я буду использовать свой собственный API для тестирования — Butterfly Testing Tools. Я разработал его, потому что мне нужна была возможность тестирования прокси, которой нет ни у JMock, ни у EasyMock. Кроме того, на мой взгляд, API можно было бы сделать более простым и гибким (это, я думаю, вопрос личного стиля).
Сначала рассмотрим, как создать стаб для интерфейса:
Connection connection =
(Connection) MockFactory.createProxy(Connection.class);
Переменная connection представляет собой стаб интерфейса
Connection
. Теперь я могу вызывать методы экземпляра соединения так же, как если бы это было реальное соединение с базой данных. Однако методы будут возвращать только null, поскольку заглушка не настроена на возврат каких-либо специальных значений. Для того чтобы настроить заглушку на возврат значений, подходящих для вашего теста, необходимо получить мок для стаба. Вот как это делается: IMock mock = MockFactory.getMock(connection);
Теперь у вас есть мок, связанный со стабом. Одним из методов интерфейса IMock является
addReturnValue(Object returnValue);
С помощью метода
addReturnValue
можно добавить возвращаемые значения в стаб. Их можно добавлять сколько угодно. Возвращаемые значения будут возвращаться из стаба в той же последовательности, в которой они были добавлены. После возврата возвращаемого значения оно удаляется из стаба, как и в очереди. Примечание: Последовательность добавления возвращаемых значений должна совпадать с последовательностью вызова методов в стабе! Если в стаб добавить в качестве возвращаемого значения String «myReturnValue», а затем вызвать connection.prepareStatement("select * from houses")
, который возвращает PreparedStatement
, то будет выброшено исключение. Возвращаемое значение String не может быть возвращено из connection.prepareStatement("...")
; Вам придется самостоятельно убедиться в том, что возвращаемые значения и вызываемые методы в стабе совпадают.Получив экземпляр IMock для стаба, можно также проверить, какие методы были вызваны в стабе. Для этого в интерфейсе IMock имеется ряд методов
assertCalled(MethodInvocation)
. Приведем пример: mock.assertCalled(new
MethodInvocation("close"));
Если метод
connection.close()
не был вызван, то будет выброшено исключение java.lang.AssertionError
. Если вы используете для модульного тестирования JUnit, он перехватит ошибку AssertionError
и сообщит, что тест не прошел. В интерфейсе IMock есть и другие методы assertCalled()
и т.д. Более подробную информацию можно найти в проекте Testimonial.Последнее, что я покажу, — это как создать прокси для реального соединения и мок, связанный с прокси:
//opens a real database connection.
Connection realConnection = getConnection();
Connection proxyConnection = MockFactory.createProxy(realConnection);
IMock mock = MockFactory.getMock(proxyConnection);
Просто, да? Все точно так же, как и при создании стабов для интерфейсов. Просто вместо интерфейса (объекта класса) вы предоставляете
MockFactory
реальный коллаборатор. proxyConnection
будет записывать все методы, вызываемые на нем, прежде чем передать вызов метода экземпляру realConnection
. Таким образом, вы можете использовать proxyConnection
так же, как и realConnection
, и в то же время проверять, какие методы были вызваны.Можно даже временно превратить
proxyConnection
в стаб, добавив возвращаемое значение в прокси с помощью mock.addReturnValue(...)
. Когда proxyConnection
увидит возвращаемое значение, он вернет его вместо того, чтобы переадресовать вызов realConnection
. После того как все возвращаемые значения будут возвращены, proxyConnection
продолжит передавать вызовы методов экземпляру realConnection
. Таким образом, можно переключаться между использованием proxyConnection
в качестве реального соединения или стаба. Умно, правда?Имитировать можно не только соединения с базой данных. Подменить в процессе тестирования можно потенциально любой коллаборатор тестируемого модуля. Тестировать ли модуль с использованием мока или реального коллаборатора — зависит от конкретной ситуации. Прокси позволяют делать и то, и другое одновременно, и даже имитировать стабом некоторые вызовы методов. Более подробную информацию о тестировании с использованием моков в Testimonial ищите на странице проекта Testimonial.
В заключение приглашаем всех начинающих тестировщиков на открытый урок, посвященный документации. Мы научимся правильно составлять тест-кейсы, чек листы и баг-репорты и поймем, когда какой документ нужно использовать. Записаться на открытый урок можно на странице курса «QA Automation Engineer».
Нашла и перевела: Ксения Мосеенкова