Добрый день.
Модуль MiniMock – пожалуй, один из наиболее изящных способов создания mock-объектов в Python. История MiniMock началась с 25 строк кода, которые Ian Bicking набросал под впечатлением от модуля python-mock. Будучи приверженцем doctest, Ian решил задействовать его возможности для подготовки имитаторов. В результате, по наводке python-mock и с doctest наперевес, появился MiniMock, возможности которого охватывают базовые сценарии создания mock-объектов. За три года усилиями энтузиастов функциональность модуля MiniMock значительно расширилась, но он по-прежнему остается компактным и удобным в использовании.
Текущая версия модуля доступна на python.org. Ниже представлена работа с последней на сегодня версией 1.2.3. В качестве примера рассматривается максимально упрощенная система выдачи книг в библиотеке. Модуль library.py содержит два класса Books и Readers. Работа с классами Books и Readers иллюстрируется функцией simple_example. Файл library.py:
class Books:
def __init__ (self, title):
self.title = title
self.reader = "In place"
def current_reader (self, reader):
self.reader = reader
def get_reader (self):
return self.reader
class Readers:
def __init__ (self, name):
self.name = name
self.book = ""
def current_book (self, book):
self.book = book
def get_book (self):
return self.book
def simple_example():
reader = Readers ("Obama")
book = Books ("War and Peace")
reader.current_book (book.title)
book.current_reader (reader.name)
title = reader.get_book()
print title
reader_name = book.get_reader()
print reader_name
if __name__ == "__main__":
simple_example()
Запуск:
[capt@rh564 Work]# python library.py
War and Peace
Obama
Когда книга выдается читателю, должны быть выполнены два действия: название книги необходимо записать в формуляр читателя, а фамилию читателя – в формуляр книги. Этим занимается функция book_checkout, представленная в модуле main.py:
from library import Readers, Books
def book_checkout (reader, book):
reader.current_book (book.title)
book.current_reader (reader.name)
Подготовим тест для этой функции. Мы хотим быть уверенными в том, что при ее выполнении будут вызваны два метода: current_book для читателя и current_reader для книги. Дополним скрипт main.py:
from minimock import Mock, TraceTracker, assert_same_trace
from library import Readers, Books
def book_checkout (reader, book):
reader.current_book (book.title)
book.current_reader (reader.name)
def test_book_checkout():
tt = TraceTracker()
Readers.current_book = Mock ('Readers.current_book', tracker = tt)
Books.current_reader = Mock ('Books.current_reader', tracker = tt)
reader = Readers("Obama")
book = Books("War and Peace")
expect_mock_output = """\
Called Readers.current_book('War and Peace')
Called Books.current_reader('Obama')"""
book_checkout (reader, book)
assert_same_trace(tt, expect_mock_output)
test_book_checkout()
Результат запуска main.py более чем лаконичен:
[capt@rh564 Work]# python main.py
[capt@rh564 Work]#
Что произойдет, если мы изменим логику работы book_checkout? Скажем, уберем вызов book.current_reader (reader.name) и тем самым “забудем” прописать в формуляре книги ее нынешнего читателя? Вот что получится:
[capt@rh564 MiniMock]# python main.py
Traceback (most recent call last):
File "main.py", line 25, in ?
test_book_checkout()
File "main.py", line 23, in test_book_checkout
assert_same_trace(tt, expect_mock_output)
File "build/bdist.linux-x86_64/egg/minimock.py", line 213, in assert_same_trace
AssertionError: Expected:
Called Readers.current_book('War and Peace')
Called Books.current_reader('Obama')
Got:
Called Books.current_reader('Obama')
Причина появления AssertionError: при выполнении book_checkout вызов методов отличается от ожидаемого. Таким образом, unit-тест test_book_checkout отслеживает поведение функции book_checkout. Если логика ее работы изменится, мы будем предупреждены.
Более подробную информацию о MiniMock можно получить в документации к модулю. Помимо этого, будет полезно ознакомиться с кодом minimock.py (доступен в архиве MiniMock-1.2.3.tar.gz). Он небольшой, но достаточно информативный. Каждый класс снабжен описанием и примерами его использования. Счастливого взлета.
02.06.2009
Капитан Аляска |
Модульные тесты |
Комментарии (4)
Добрый день.
Чапаев: Петька, приборы! Петька: 300! Чапаев: Что 300? Петька: А что приборы?
Он пытался сопоставить скорость с потерей высоты, охваченный глубоким, вызывающим тошноту, ужасом от вида земли, неумолимо приближающейся с каждой секундой. Самолет переваливался с боку на бок, пропеллеры то замирали, то вновь начинали вращаться с бешеной скоростью. Через мгновение, показавшееся ему вечностью, колеса чиркнули по раскаленному асфальту, оттолкнулись, машина зависла в воздухе, но тут же, с ударом, опустилась на полосу… (по мотивам “Взлетно-посадочной полосы 08″ Артура Хейли).
Джордж Спенсер и все пассажиры самолета словно родились заново. Волею судьбы оказавшись в кресле первого пилота, Джордж сумел посадить лайнер, имея за плечали лишь опыт полетов на крошечном боевом истребителе во время войны. Кресло пилота гражданской авиации – одно из самых жестких. Множество датчиков, пультов, рычагов, мониторов, факторы внешней среды, взаимодействие с диспетчерскими службами, индивидуальные особенности воздушного судна – все не перечислишь. Подготовка пилота подразумевает отработку навыков поведения в стандартных и аварийных ситуациях. На помощь приходят тренажеры, которые воссоздают специфические рабочие нагрузки и состояния, возникающие в процессе управления самолетом.
Что представляет собой простейший тренажер? Некое подобие ответа Петьки из эпиграфа к данной статье. Прибор А – 300, прибор B – 500, и “ваше слово, товарищ маузер!”, как предлагал Маяковский. Иными словами, арию приборов исполняют заглушки, которые возвращают фиксированные значения. Есть буквы-цифры, и неважно, что за ними.
Что такое комплексный тренажер (Full Flight Simulator, FSS)? Максимально точное воссоздание стандартных процедур и внештатных ситуаций. От и до. Петька отдыхает, а Василий Иванович полностью погружается в состояние полета. Забыл про гидроусилитель? Не проверил удельный расход топлива? Не выпустил шасси? Авария, получите, распишитесь. Проанализируйте, что случилось, и начинайте заново.
Каждый компонент программной системы (возвращаемся к нашим баранам) подвергается интенсивным проверкам до сдачи в эксплуатацию. Отдельно взятый модуль зависит от других компонентов, и задействовать их не всегда представляется возможным. Вот несколько типичных ситуаций:
1. требуемый компонент еще не написан или же находится в неработоспособном состоянии. Скажем, если над ним работает другой программист.
2. искомый модуль недоступен. К примеру, сервлет, который находится на закрытом сайте.
3. необходимый интерфейс слишком “дорог”. Возьмем CУБД. Если тестируемый модуль обращается к базе данных, то хотелось бы избежать нагрузки на сервер.
Как поступать в таких случаях? Самый простой вариант – использовать заглушки (stubs). Мы рассматривали их реализации в Perl и Python. Они довольно удобны, если воспринимать мир вокруг нашего модуля как черный ящик. Неважно, что происходит внутри. Важно, что на выходе. “Петька, приборы!” – “300″, и вся недолга. Но такой вариант подходит не всегда. Предположим, наша программная система анализирует финансовое состояние компании. В простейшем виде, прибыль = доход – издержки. Если нашему компоненту нужно знать прибыль, то ему можно подложить 300 и сказать, что это прибыль. А можно убедиться в том, что величина прибыли не берется “с потолка”. Можно убедиться в том, что при запросе прибыли действительно вычисляются доход и издержки (вызываются соответствующие методы класса), и в случае получения 700 рублей дохода и 400 рублей издержек мы таки получим 300 рублей прибыли. Такая проверка позволит быть уверенным в том, что изменение/расширение программной системы не повлекло за собой несанкционированное изменение поведения системы, и алгоритм расчета прибыли остался именно таким, каким он предполагался быть. Именно этим и занимаются имитаторы (mocks).
Имитаторы позволяют воссоздавать поведение системы, не нарушая ее работы и даже не имея ее под рукой. Например, в клиент-серверном приложении можно имитировать работу сервера и полноценно тестировать клиент. Или выполнить псевдоконнект к базе, “записать” строку в таблицу, вернуть результат, и при этом даже не обращаться к серверу баз данных. Или имитировать ситуацию, трудно воспроизводимую в реальной среде, но реакция на которую нам важна – например, исключение (exception), генерируемое при высоких нагрузках. Здорово, правда?
Для современных языков программирования написано немало модулей, которые позволяют создавать mock-объекты “на лету” и “с пылу, с жару” использовать их в модульных тестах. В то же время, хочется отметить, что mock-объекты – это не серебряная пуля. В каких случаях применение имитаторов может оказаться неэффективным? Вот несколько сценариев:
1. мы используем сторонние библиотеки, не хотим в них разбираться и, соответственно, не можем воспроизвести их интерфейсы.
2. нас не интересует поведение системы. Мы считаем, что модульные тесты будут достаточно надежны с применением заглушек.
3. особенности нашего приложения делают использование имитаторов опасным или слишком трудоемким.
Каждая программная система уникальна. Архитектура приложения и сопутствующие обстоятельства диктуют выбор в пользу имитаторов, заглушек или даже отсутствия тех и других. Как бы то ни было, выбор будет тем надежнее, чем лучше мы знаем свое приложение и практики создания модульных тестов.
Примеры создания имитаторов мы рассмотрим в одной из следующих статей. Оставайтесь с нами и зовите друзей. До встречи.
13.05.2009
Капитан Аляска |
Модульные тесты |
Комментарии (1)