среда, 24 ноября 2010 г.

Там на неведомых дорожках. Небольшой привал.

Хотел рассказать про Fone Monkey, но после непродолжительных размышлений захотелось сделать небольшой привальчик и помедитировать на тему того, чего вообще хочется от хорошего инструмента для автоматизации тестирования UI приложений.

 
Понятно, что у всех есть свои предпочтения, есть какие-то "священные коровы", от которых сложно или вовсе невозможно отказаться, а еще есть такая штука, как специфика проекта, которая может диктовать такие условия, что хоть стой, хоть падай. Но все-таки я рискну составить небольшой "must have" список опций для такого инструмента, без которых жить становится не то чтобы трудно, но как-то некомфортно, что ли. А если нам (читай "клиенту разработчиков инструмента") некомфортно, то... Ну в общем, поехали.

 
Первое, чего хочется - это возможность записать (и естественно повторить) действия пользователя. Без этого конечно можно обойтись, но на дворе, как ни как, а XXI век и от хорошего инструмента ждешь упрощения своей работы. Вот представьте, сидите вы вечером у камина, пардон, это я размечтался... Итак, сидите вы у компьютера и творите скрипт. На улице зима, холодно, выходить совсем не хочется, монитор весело помигивает пикселями, а кулер тихо и умиротворяюще гудит, заглушая ваше бормотание: "кнопка, где эта кнопка, почему тут window, какой такой window, кнопка тут должна быть, ну что за... ах вот в чем дело, не то окошко смотрим, а какие еще у нас есть окошки..." Брр, увольте! Не знаю, кто как, а я предпочитаю в такой ситуации иметь возможность включить запись действий пользователя, записать небольшую последовательность действий над требуемым контролом и посмотреть созданный рекордером код, который при желании можно отредактировать и благополучно скопипастить в свой рабочий тест.
Да, немаловажная деталька, - рекордер должен генерировать код, который можно (и нужно) читать, редактировать, сохранять в конце концов. В противном случае этот рекордер превращается в лучшем случае в игрушку, которую можно использовать не более как утилиту для демонстрации, проведения демо или создания какого-нибудь продвинутого интерактивного хелпа. Но для тестирования приложений это будет уже бесполезно, хотя бы потому, что при малейшем изменении в интерфейсе или функционале вам придется перезаписать все заново. А это уже не тестирование и уж тем более - не регрессионное.

 
Вот так плавно мы перешли от редактирования скрипта к следующему пункту нашего меню - языку, использующемуся для написания тестов. Я видел много вариантов того, как может выглядеть код теста, от полноценной программы на собственном языке, специфичном для данного инструмента, до массива строк, в котором в текстовом виде записаны действия над элементами. На первый взгляд, идеальным вариантом была бы полная интеграция с разрабатываемым приложением. Т.е. если приложение у нас написано на .Net, было бы неплохо и тесты писать на каком-нибудь шарпе. В таком случае мы получаем максимально полный контроль над приложением, нам удобно использовать его внутренние библиотеки, не надо задумываться о развертывании тестов и т.д. Да?
Если так, то мы попали в главную ловушку автоматизатора. Во-первых, зачем нам внутренние библиотеки приложения? Да, это упрощает жизнь при обмене данными с приложением, например проверке его состояния в процессе выполнения теста. С другой стороны, это не задача UI тестирования. Такие вещи можно и нужно проверять юнит-тестами и не выносить такой сор из избы. Пользователю, которого мы в процессе тестирования интерфейса так рьяно изображаем, абсолютно фиолетово, поменялся ли флажок в методе какого-то там объекта, отнаследованного непонятно от кого, - у него вообще другое представление о том, что такое наследование. Вот ежли у него зеленая кнопка фиолетовая, то ему будет уже не фиолетово (пора отдыхать с такими фразами). Во-вторых, а зачем нам тот же .Net, вот честное слово, не могу представить, зачем в тест-скрипте может понадобиться что-то еще, кроме возможности оперировать элементами пользовательского интерфейса, считывания данных из какого-нибудь репозитария и записи в лог, или еще куда, результатов выполнения теста. Поэтому такая мощь языка и платформы нам не так уж и важна. Кроме уже сказанного, давайте не будем забывать еще и о том, что тестировщики - не программисты. Чем проще и примитивнее язык, тем проще и быстрее с ним разбираться. Но и уже не к ночи упомянутого массива строк-команд тоже "маловато будет".
Я думаю, что лучшим решением для любого инструмента тестирования UI является какой-нибудь скриптовый язык на основе одного из широко известных, например VBScript, JavaScript, Python или AppleScript, причем с минимумом отличий от оригинального, а то и вовсе без каких-либо изменений. Эти языки достаточно простые и достаточно мощные одновременно, для них понаписана прорва документации, библиотек и примеров, они уже известны многим (или очень многим), а это здорово сокращает расходы на обучение.
О, чуть не забыл! По поводу развертывания тестов. Что бы ни говорили, я считаю ,что эта возможность вообще зависит не от интеграции с базовой платформой приложения, а от продуманности и удобства работы самого инструмента тестирования. Так что этот вопрос пока отложим дабы обсудить отдельно.

 
Следующий пункт очень тесно связан с предыдущим. Каким бы ни был замечательным язык, который используется при написании тестов для какого-либо инструмента, им не будут пользоваться, если предоставляемый этим инструментом API будет бедным или неудобным. А что такое хороший API? Честно, не знаю, - у хорошего продукта всегда много качеств и граней, - намного проще рассказать, что я называю бедным API. Вот, скажем, если я вместо кнопки вижу объект Window, - это бедный API. Я понимаю, что кнопка - это, по сути, окно, но я хочу видеть кнопку. Итак, на первом месте у нас уже расположилась возможность работы с каждым конкретным компонентом в соответствии с его реальным типом. Если это сделано хотя бы для всех стандартных объектов пользовательского интерфейса, таких как (спасибо Википедии)
  • кнопка (button)
  • список (list box)
  • раскрывающийся список (combo box)
  • флажок/переключатель (check box)
  • радио-кнопка (radio button)
  • поле редактирования (textbox, edit field)
  • значок (icon)
  • панель инструментов (toolbar)
  • панель (строка) статуса (status bar)
  • всплывающая подсказка (tooltip, hint)
  • полоса прокрутки (scrollbar)
  • вкладка (tab)
  • элемент для отображения табличных данных (grid view)
  • меню (menu) (главное меню окна (main menu), контекстное меню (popup menu))
  • окно (window) (панель (panel), диалоговое окно (dialog box), модальное окно (modal window))
  • дерево — элемент для отображения иерархии (tree view)
то это уже очень хорошо, а если еще есть возможность создавать дескрипторы кастомных элементов, то просто круто!
Если поиск поделемента мне надо реализовывать самому - это тоже достаточно бедный API. Это не сложно сделать самому, но возникает вопрос: "а чем, собственно, занимались разработчики инструмента". Мало сделать доступ ко всем элементам, надо еще и сделать удобной просмотр и навигацию по дереву графических объектов приложения. Кто пытался, тот знает, как противно вручную двигаться от верхнего элемента-окна к какому-нибудь флажку, в восемнадцатой группе третьей панели седьмой вкладки :)
Еще один пример недостаточной продуманности: очень часто есть необходимость сделать скриншот, скажем, для подтверждения того, что произошло ожидаемое/неожиданное событие, так вот в хорошем API предусмотрена возможность снять скриншот и сохранить его. В принципе, никто не помрет, если прийдется использваться стороннюю утилиту для таких вещей, и это не есть очень важный критерий, но согласитесь, приятно, если о тебе позаботились.
В заключение к вопросу об API, хотелось бы видеть еще одну штуковину, а именно - возможность расширения за счет написания новых и, особенно, использования существующих библиотек. Было бы просто глупостью не использовать всего того, что успели нам во благо насоздавать разработчики ПО.
Еще одна мелочь, хотя и достаточно важная, которую я по своей прихоти отнесу к API, хотя она может быть и сама по себе, - запись и сохранение результатов выполнения тестов. С одной стороны, это может как-то не очень сильно быть связано с API, с другой, если в инструмент встроен логгер, позволяющий создавать отчеты , да еще на уровне используемого скриптового языка и объектов инструмента, да еще с развитой дифференциацией сообщений по типам (issue, debug, error, message etc.) - то ему цены нет.

 
Следующий момент - наличие хорошей документации, описывающей фичи продукта, примеры b best-practices его использования. Нет никакого толку от сверхзамечательного инструмента, с которым непонятно как работать. Понятно, что в такой ситуации отлично работает метод изучения путем "средневзвешенного межпотолочного тыка", но это долго, сложно, да и есть ли гарантия, что инструмент в таком случае будет использоваться наилучшим образом, - к сожалению, из-за плохой документации (или из-за лени, мешающей в ней хорошо покопаться, но это уже другая история) мы очень часто изобретаем костыли для проведения операций, которые замечательно поддерживаются инструментом "в прямом направлении".

 
Следующий пункт нашей программы - это поддержка инструментом техники Data-Driven Testing. Тут даже не знаю как уговаривать, кроме как напомнить, что мы работаем с огромным числом данных, тьмой входов и выходов и тут автоматизация здорово помогает жить, так что DDT для нас просто безальтернативна.

 
Дальше по порядку, но не по значимости,  у нас идет управление тестами и тестовыми наборами. Опять же доводилось видеть всякое, но инструмент, который не позволяет использовать отдельные тесты, объединять их в классы или наборы, очень неудобен. Вот представьте, есть у вас тул, который не понямает что такое атомарные тесты. Для него существует только один тест, который он будет выполнять. Это классно для Smoke Test'a, вернее, не классно, а достаточно. Но что делать если у вас куча функционала? Проверять одним огромным тестом? А сопровождать его как, а модифицировать, а просто понять, что свалилось? Нет, можно, конечно, но меня на такое не зовите, не хочу. В принципе, должен сказать, что большинство инструментов, которые таки не умели работать с более чем одним тестом (остается только удивляться тому, насколько их много), позволяли это ограничение благополучно обходить. Два самых распространенных способа - это или дописать инструменту функционал для работы с тестами (это как раз к вопросу о расширяемости API) или запускать инструмент для каждого теста в отдельности.
Первый способ самый привлекательный, хотя и требует достаточно высокой квалификации тестировщика (а возможно, - привлечения разработчиков) и какого-никакого времени, да и не стоит забывать, что инструмент все-таки должен позволять такое расширение своего функционала. Так что этот способ применим далеко не всегда, а иногда бывает так, что если и применим,  то овчинка может не стоить выделки.
Второй способ проще, может быть реализован всегда, но он обычно требует наличия внешнего по отношению к инструменту управляющего скрипта (скажем bat-file с командами), который будет запускать инструмент для прогона теста и тушить его каждый раз, когда тест пройден. Второе неудобство состоит в том, что время прогона тестов увеличивается за счет того, что мы тратим время на перезапуск и инициализацию инструмента для каждого теста. Хотя это не так и долго на первый взгляд, но когда тестов много... Тут можно провести аналогию с инструментами для юнит тестинга - возьмите любой тул типа nUnit, поместите каждый тест в отдельный класс и прогоните, а потом перенесите все эти тесты в один тест-класс и посмотрите, как сильно будет отличаться время прогона. По собственному опыту, с 40 минут (это было не совсем юнит, а скорее системное тестирование - так сложилось) время запуска выросло одномоментно до 3 часов, и это на не самом большом тестовом наборе. Без комментариев :)

Раз уж мы заговорили о тестах и тестовых наборах, давайте обсудим еще одну фишку, которая  настолько облегчает жизнь, что промолчать о ней сложно. Речь идет о восстановлении состояния приложения после запуска теста. Вообще говоря, есть два подхода в организации тестовых наборов для регрессионного тестирования - последовательный и параллельный.
Последовательный подход заключается в том, что конец одного теста является началом следующего, таким образом, это - зависимые друг от друга тесты и каждый предыдущий тест фактичестки готовит исходные данные для последующего. Этот подход имеет пару преимуществ, заключающихся в том что:
  • время прохождения тестового набора сокращается за счет экономии времени на подготовке тестовых данных и реинициализации приложения;
  • последовательный тестовый набор, по сути своей, ближе к реальному сценарию действий пользователя, т.к. пользователь обычно работает в "непрерывном потоке сознания" и очень редко заботится о том, чтобы убрать из системы весь созданный им ранее "мусор";
 но этот подход имеет крайне весомые недостатки, а именно:
  • крайне низкая дефектоустойчивость тестового набора, т.к. "слетание" одного теста в результате возникшей ошибки (что само по себе и неплохо, в виду того, что для поиска дефектов тесты и пишутся) практически гарантированно приведет к ошибкам в последующих тестах набора из-за изменившихся для них начальных данных, хотя с точки зрения тестируемого функционала система в рамках этих тестов работала вполне корректно;
  • в случае последовательного набора определение конкретной причины фейла, и как следствие, места пояления дефекта, может быть очень даже нетривиальной задачей.
Тестовый набор, построенный по параллельному (независимому) принципу, заключается в том, что каждый тест работает со строго фиксированным набором начальных условий и в идеале на него не должны влиять результаты предыдущих тестов. Вот в этом-то случае и понадобится восстанавливать состояние системы, о котором мы вспомнили в начале. Есть множество подходов и стратегий. Ниже привдятся два основных подхода, комбинацией которых получается почти все остальные:
  • инструмент рестартует тестируемое приложение, удаляя/заменяя/перезаписывая данные и настройки (подходит для приложений с четко определенным расположением данных и настроек, например в конфигурационных файлах или базе данных с несложной структурой)
  • инструмент запускает промежуточный скрипт, убирающий сделанные изменения (по сути, это такой же скрипт, что и в тестах, просто с утилитарным назначением).
Понятно, что оба этих способа не абсолютно надежны, но, тем не менее, они значительно повышают надежность тестового набора.

Наконец, последнее обязательное для современных инструментов автотестирования свойство - возможность использования в процессе Continuous Integration (CI). Действительно, сами по себе автоматические (и не только) регрессионные тесты не нужны никому, их задача - своевременное выявление дефектов, вносимых в процессе разработки. Для того, чтобы эти тесты были эффективными, они должны запускаться достаточно часто. Есть правило, говорящее о том, что лучший способ добиться частого и обязательного прогона тестов - включить этот самый прогон в процесс сборки продукта (build). Это не я придумал, это старый и проверенный способ всех гибких методологий, начиная с XP. Вот только работает он для юнит-тестов, а в случае с GUI тестингом есть маленькая поправка-заковыка: т.к. тесты выполняются сравнительно долго (а иногда и совсем не сравнительно :), никто не будет их включать в каждый билд и ждать, пока они все пройдут, - гораздо логичнее создать отдельный билд-план, в который и включить все требуемые тесты. Запускается на исполнение этот тест-план автоматически, обычно по наступлению какого-то заданного времени, скажем, раз в сутки.
На данный момент, единственным, по сути, требованием для использования инструмента в CI является возможность запуска из командной строки с указанием тестируемого приложения и путей к тестам, логам и т.д.

На этом список, пожалуй, можно и закончить. Разве что вспомнить о том, что желательно, чтобы инструмент был совместим с другими, уже используемыми инструментами тестирования и разработки, а также о такой классной штуке, как распределенный запуск тестовых наборов, которая позволяет существенно сократить время выполнения тестов за счет включения в работу нескольких компьютеров, на каждом из которых выполняется часть тестов.
Но без этого, в принципе, можно и обойтись, хотя как знать, когда-то и те вещи, которые я для себя отношу к обязательным, воспринимались не более как просто удобные дополнения :)

В общем, теперь я наконец выдохнул из себя этот список, надеюсь он поможет понять (и мне в том числе) как же именно я выбирал, выбираю и буду выбирать себе тул по душе :)
Следующий пост я снова планирую про Fone Monkey, а там посмотрим...

понедельник, 8 ноября 2010 г.

Там на неведомых дорожках. Автоматизация тестирования под iOS

Стоит передо мной задачка: развернуть автоматизированное тестирование для довольно большого проекта под iOS. Ну и стоит себе, ну и что такого? Автоматизация тестирования, регрессионного, для приложения с пользовательским интерфейсом, под довольно-таки стабильной и известной платформой - классика, хожено-перехожено. Ан поди ж ты!

Вообще говоря, с Apple и ее мирком я лично познакомился не так давно, а именно - в мае сего года, и, когда только приступал к реализации этого проекта, все казалось красивым как макось и простым как сибирский валенок. Действительно, как это все выглядит с точки зрения автоматизатора, избалованного одной большой компанией по производству окон? А вот как: есть у нас приложение под iOS, давно известную платформу от солидной компании. Функционал приложения большой, стабильный (что странно для мобильной платформы, но тем не менее), дополняется часто, а меняется редко. В общем, на первый взгляд ситуация почти идеальная. Всё, что нужно сделать - это провести небольшое исследование с целью подбора инструмента автоматизации, составить план с объемом работ и эстимейтами да согласовать его с менеджментом - и вперед в светлое будущее!

Я, оказывается, большой оптимист. В принципе, проблем с выбором инструмента для автоматизации, можно и нужно было ожидать, но почему-то я понадеялся на Apple. Вот как-то верилось мне, что компания, продвигающая очень даже неплохую (а на мой взгляд, - и лучшую на данный момент) платформу могла бы позаботиться о создании инструментария. Честно говоря, она и позаботилась, но поздно и как-то неуверенно, что-ли. В любом случае, до SDK 4.0 родного инструмента от Apple попросту не было. А поиск тула для автоматизации я начал в мае, когда о идее Apple создать-таки свой инструмент уже слышали, но что это будет и с чем его есть - еще нет. Поэтому, рассматривались сторонние инструменты, коих тоже было не много. Сразу оговорюсь, что рассматривались только open-soource или, как минимум, просто бесплатные утилиты. В конце концов из всего малообразия инструментов было найдено всего несколько диковин, достойных внимания:
- Fone Monkey
- UISpec
- iCuke
плюс UIAutomation - тот самый долгожданный сюрприз от Apple, но о нем, даст Бог, напишу попозже, а в ближайших постах кратко пробегусь по первым трем, авось кому пригодится. Сразу оговорюсь, мои впечатления майско-июньской давности, ибо для себя я выбрал-таки UIAutomation, и о судьбе прочих особо не беспокоился. В общем, следующий пост будет про Fone Monkey, постараюсь особо не затягивать.