Предлагаю поговорить сегодня о замечательном фреймворке для тестирования TextTest. Это кроссплатформенный инструмент для функционального тестирования с «record-replay» парадигмой. Как следует из названия TextTest пропагандирует довольно необычный на сегодняшний день подход текст-ориентированного тестирования, который позволяет легко и просто писать и читать тесты. Помимо TextTest мы поговорим о StoryText — являющимся строго говоря отдельном инструменте для тестирования GUI, но совместно с TextTest он позволяет делать это гораздо приятнее. Так же упомянем о третьем модуле от автора — CaptureMock.
Итак, с чего все начиналось: мне понадобился кроссплатформенная библиотека для тестирования GUI на Tkinter (стандартный модуль python для написания GUI) с теоретически возможным переходом в последствии на другой фреймворк. Покопавшись в google, я уж было совсем отчаялся найти нечто подходящее. Но встретил упоминание о TextTest, который умел не только тестировать логику работы интерфейса на Tkinter, но и еще предоставлял возможность работы с кучей других графических GUI-библиотек. Да еще и содержал такое кол-во разных других вкусностей, что я сходу в него влюбился. Итак, приступим.
Краткая сводка
Текущее название: TextTest +дополнительные модули StoryText и CaptureMock
Старое название: PyUseCase
Автор: Geoff Bache
Первый коммит: 04/02/2003
Сайт с документацией: sourceforge
Исходные коды можно найти тут: launchpad тут же расположен bug-tracker
Лицензия: GNU LGPL v3
Как видим, проект зрелый, ему стукнуло почти 10 лет и тем не менее он продолжает активно развиваться. Автор утверждает, что пишет его полный рабочий день и судя по активности на launchpad в несколько десятков коммитов в месяц, я склонен ему верить. Помимо него в проекте на постоянной основе участвует еще один человек. В статье я попытаюсь хотя бы в общих чертах рассказать, что же им удалось создать за эти 10 лет.
Основная идея
Фреймворк помогает вашему приложению тем или иным образом записать обычный текстовый файл, в котором отражены все важные действия, которые сделала программа. Запуская тест в первый раз, вы проверяете что вывод программы верен и помечаете этот набор выходных файлов как правильный «golden copy».
После внесения изменений в ваше приложение, тест снова запускается и сравнивается новый получившийся набор файлов с исходными. Если все совпало — тест считается пройденным. Не совпало — смотрим изменения, если они корректны, помечаем новые файлы как «golden copy». Все просто.
Итак, какие существуют способы формирования таких файлов с информацией о работе программы:
- TextTest умеет при запуске программы сохранять ее stdout, stderr в файлы.
- Можно указать TextTest какие файлы сгенерировала программа в процессе работы (например, логи).
- При помощи CaptureMock можно автоматически сгенерировать обертки для отдельных функций и даже модулей, в том числе и для стандартных библиотек python. Ну, к примеру, можно попросить логировать math.fabs, тогда любое обращение к этой функции (входные и выходные значения) будут писаться в лог.
- StoryText представляет собой набор классов-оберток, которые прозрачно для вашей программы замещают интерфейсы работы с GUI, что позволяет записывать все действия сделанные человеком на форме, с последующим воспроизведением, а так же логировать ответную реакцию приложения.
Если совсем упрощенно выглядит это так: запускаем программу, кликаем на кнопке, что вызывает изменение какого-то виджета и выходим. При воспроизведении теста StoryText запускает программу, сам нажимает ту же кнопку и проверяет, что виджет изменился таким же образом, как и при записи теста.
Такие обертки существуют для: PyGTK/Tkinter/wxPython/SWT/Eclipse RCP/GEF/Swing, причем список вполне реально расширить самостоятельно для других библиотек.
Как видите, существует богатый набор способов для сохранения информации о программе, не меняя ни единой строчки в исходниках. Далее я попытаюсь продемонстрировать в самых общих чертах как пользоваться этим богатством на примерах, заодно и станет понятнее. Несомненно в рамках одной статьи не возможно охватить все целиком и за кадром останется бОльшая часть фреймворка. А примеры вынужденно сделаны сильно упрощенными и короткими. Но я надеюсь, вам удастся получить хотя бы общее представление о возможностях TextTest и возможно заинтересоваться им.
Установка
Кратко опишу, что нужно установить для работы с фреймворком под Windows, останавливаясь на некоторых нюансах, надеюсь, что для других системах все так же не сложно.
Для тестов нам понадобиться установленный python 2.6.
Затем качаем и устанавливаем texttest 3.24, обратите внимание, что PyGtk нужен для работы GUI (я в первый раз галочку снял), так же не забудьте поставить по умолчанию снятую галочку для «StoryText for python».
В предыдущий набор почему-то не попал CaptureMock, поэтому поставим его отдельно командой «easy_install capturemock» (если у вас не установлен easy_install его можно взять по ссылке он вам пригодиться помимо этой статьи, кому интересно подробнее про него можно почитать тут)
Далее для корректной работы StoryText в переменных окружения («свойства системы»«дополнительно»«переменные среды») добавьте переменную TCL_LIBRARY со значением c:SoftPython26tcltcl8.5 (путь соответственно поменяйте на свой).
Потом нужно перезайти в систему, чтобы выставленные переменные окружения применились.
Создаем проект
Запускаем TextTest, он при первом запуске предлагает создать проект, никаких там особенных секретов нет. Нужно указать название для проектов, расширение для файлов конфигурации, название для папки с проектом, в выпадающем списке выбрать что тестировать мы будет не GUI программу и указать путь к тестируемому приложению (скрипт который описан далее в статье можно скачать отсюда), получаем примерно следующее:
Далее идем в папку с проектами для TextTest (по умолчанию она находится тут: c:Tests) и смотрим, что там получилось. Находим внутри файлик config.cfg, в котором описаны настройки проекта, он представляет из себя текстовый файл с ini-подобным синтаксисом: «name:value» + иногда там встречаются секции. Давайте сразу его немного поправим изменив значение executable на:
executable:${TEXTTEST_ROOT}/test.py
где TEXTTEST_ROOT — это переменная окружения, ссылающаяся на текущий каталог проекта.
Теперь можно test.py положить рядом с config.cfg, для большей мобильности так сказать. Полный список переменных окружения тут
test.py — содержит 3 функции и один GUI-класс на Tkinter, которые мы будет тестировать. Параметрами для функций и собственно выбором самой функции можно рулить, запуская скрипт с разными параметрами. Первый характеризует вызываемую функцию, остальные передаются в параметры этой функции. Чтобы не повторять вручную все, что будет описано ниже, можно сразу скачать итоговый проект тут, и положить папку simpletests в «c:Tests». И потом запускайте тесты из него по мере чтения.
Первый пример. Тестируем вывод в stdout
Тестировать будем функцию mul из test.py
def mul(a, b):
print 'params: %s, %s' % (a, b)
result = a * b
print 'result = %s' % result
if result > 0:
print 'positive'
elif result == 0:
print 'zero'
else:
print 'negative'
Как видите функция элементарна — она перемножает два числа и выводит в stdout значения параметров, результат и знак результата. Чтобы ее вызывать нужно запустить test.py с первым параметром «mul» и двумя другими, которые будут аргументами функции.
Создаем через меню новый test-suite и называем его к примеру «Suite_Mul», к нему создаем тест «Test_Negative», в качестве параметров командной строки указываем «mul 1 -2», запускаем.
Т.к. запуск теста производился в первый раз, TextTest резонно укажет вам на то, что значения stderr.cfg и stdout.cfg изменились, еще бы их вообще не существовало. Смотрим, что же в них вывелось, первый получился пустым, а во втором должен быть вот такой текст:
params: 1, -2
result = -2
negative
Что же, все верно, нажимаем на Save сохраняя результаты в качестве «golden copy». При повторном запуске теста он пройдет успешно.
Можно попробовать изменить программу, чтобы она выдавала другой результат и посмотреть как выглядит ошибка. Если дважды кликнуть по ошибочному, подсвеченному красным, файлу, можно наглядно увидеть что же изменилось
Думаю вы без труда допишите тесты на два остальных случая «Test_Positive» и «Test_Zero», чтобы потренироваться. Потом стоит посмотреть, какие файлы и папки добавились в проекте, чтобы убедиться, что структура проекта логична, а все файлы легко читаются человеком, нет ни бинарных данных, ни xml, только plain text.
Второй пример. Тестируем вывод в лог
Тестировать будет функцию file_write
def file_write(s):
f = open('log.txt', 'wt')
f.write('%s %s-%sn' % (time.strftime("%H:%M:%S"), s, s[::-1]))
Она выводит текущее время в log.txt а затем строчку из входного параметра + эту же строчку только инвертированную.
Добавляем новый test-suite «Suite_File» и тест к нему «Test_File» с параметрами «file HelloWord».
Чтобы указать TextTest'у, что программа будет генерировать файл «log.txt» в появившейся папке «Suite_File» добавляем (вручную, ибо убогий интерфейс мало что позволяет делать из него) файл config.cfg с текстом:
[collate_file]
logfile:log.txt
Запускаем, сохраняем результат. Запускам второй раз. Если вы не самый быстрый ковбой дикого запада, то увидите, что тест прошел с ошибкой, т.к. в log.txt на этот раз записано другое время. Подобная проблема возникает довольно часто, когда мы работаем с изменяющимися данными. Это могут быть записанные в лог: время, дата, ip, имя машины и т.п. К счастью, TextTest имеет средства изменения выходных файлов. Мы ограничимся тем, что заменим реальное время на 00:00:00 вот такой строчкой в созданном выше config.cfg:
[run_dependent_text]
logfile:[0-9][0-9]:[0-9][0-9]:[0-9][0-9]{REPLACE 00:00:00}
После этого любые изменения времени не повлияют на прохождение теста. Можете запустить и проверить.
Третий пример. Перехватываем вызов функции
Теперь тестировать будем функцию formula
def formula(val):
print math.floor(math.fabs(val))
И предположим, что по какой-то прихоти, мы хотим узнать какие же значения приходят в функцию math.fabs и что она возвращает. В этом нам поможет библиотека CaptureMock, она может «обернуть» math.fabs своим кодом, который добавит в лог входящие параметры, выполнить оригинальную math.fabs и результаты так же поместит в лог.
Добавляем новый test-suite «Suite_Formula» и тест к нему «Test_Formula» с параметрами «math -4.5». Затем подключим CaptureMock, для этого в файле в главный config.cfg (тот который лежит в корне simpletests) добавим строчку:
import_config_file:capturemock_config
А в папку Suite_Formula добавим файлик capturemockrc.cfg, в котором укажем, что для Test_Formula нам хочется логировать math.fabs:
[python]
intercepts = math.fabs
[general]
server_multithreaded = False
После этого, если перезайти в редактор, во вкладках Running, а внутри нее во вкладке Basic, мы обнаружим появившиеся настойки «CaptureMock». Переключаем галочку на «Record», что указывает на то, что мы хотим использовать CaptureMock при записи «golden copy» и запускаем тест. После запуска, мы увидим, что помимо стандартных stderr и stdout появился новый pythonmoks.cfg со следующим содержимым:
<-PYT:math.fabs(-4.5)
->RET:4.5
т.е. туда записались входные и выходные значения функции math.fabs.
После первого запуска галочку в блоке «CaptureMock» можно вернуть на Replay (что впрочем будет сделано автоматически при следующем перезапуске редактора).
Пример, конечно, получился несколько надуманным, но функциональность чрезвычайно полезна. Ну например, пишите вы чат, который по tcp/ip передает кучу служебных данных. Эти данные нужно как-то проверять. А как? Ну не логировать же каждый чих. При помощи CaptureMock можно легко переопределить функции передачи данных по сети и залогировать данные передаваемые через них.
CaptureMock может выполнять еще много чего интересного, но формат статьи не позволяет рассказать обо всем.
Чуть позже я опубликую завершающую часть статьи, где покажу, как можно тестировать GUI и формировать отчеты.
Автор: ReanGD