OpenQuality.ru

Качество программного обеспечения

Python: модульное тестирование с MiniMock

Добрый день.

Модуль 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)

Взлетная полоса, или введение в mock-объекты

Добрый день.

Чапаев: Петька, приборы! Петька: 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)