Вы здесь: Home > Статьи > Экстремальное тестирование
Что здесь происходит
Правила XP
Статьи по XP
Книги по XP
Ссылки по XP
Обсудить
Написать нам

Экстремальное тестирование

XP фактически предлагает два термина для функционального тестирования системы: Приемочные тесты (Acceptance Tests) и Функциональные тесты (Functional Tests). По последним данным,  терминология сменилась с Функциональных Тестов на Приемочные Тесты.

Рекомендую определиться с терминами заранее и далее их последовательно использовать, иначе может возникнуть путаница, которая в нашем случае буквально привела к параллельным наборам тестов - одни назывались приемочными, другие - функциональными. Поэтому мы все назвали Функциональными тестами. Я их так и буду называть, чтобы не путаться.

Итак, XP говорит, что Заказчик имеет право получать работающую систему в конце каждой итерации. Как Заказчик узнает, что система работает? Должны проходить функциональные тесты, предоставленные заказчиком. Поскольку в конце итерации должны проходить ВСЕ предоставленные заказчиком тесты (в том числе и тесты результатов предыдущих итераций), то, очевидно, что тесты должны быть автоматизированы. Иначе процесс приемки результатов итерации займет больше времени, чем сама итерация.

Это теория. А как это делать на практике? В этой статье я попытаюсь описать наш опыт функционального тестирования.

Что тестировать?

Назначение функционального теста - доказать Заказчику (и показать Разработчику) то, что система работает так, как необходимо. Следовательно, тестировать надо то, и только то, что должно быть в работающем виде.

У разработчика всегда есть соблазн написать функциональный тест в терминах внутренней структуры системы (по типу Unit Test-a). Например, если система должна рассчитывать комиссионные, то можно захотеть в коде создать обьект, который их считает, и протестировать правильность его работы. Это ошибка, так как корректность работы какой-то части системы не дает гарантии работы бизнес-функции. Может быть, этот обьект и не вызывается вовсе.

Функциональный тест должен работать в терминах пользователя. Если для пользователя расчет комиссионных выглядит как появление нужных сумм в окне просмотра, то и функциональный тест должен тестировать это.

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

Стоит заметить, что обычно User Story не оговаривает UI, который будет использоваться. Более того, очевидно ожидать, что со временем UI может изменяться в соответствии с пожеланиями пользователей и модой, тогда как связанная с ним функциональность останется неизменной. Поэтому, разумно создать уровень абстракции, чтобы отвязать тесты от интерфейса.

Как тестировать?

Здесь больше вопросов, чем ответов. С другой стороны, почти всегда находилась возможность протестировать функциональность:

Толстый Win32 клиент с GUI

Наиболее простой и очевидный способ тестирования таких приложений - это использование средств автоматизации тестирования. Их существует множество, мы используем AQTest компании AutomatedQA - недорогой и эффективный продукт. Он позволяет записать последовательность действий и позже воспроизвести ее. Последовательность действий представляется в виде тестового скрипта - программы на языке JScript, VBScript или DelphiScript. Также скрипт можно просто написать.

Мы уже не используем прямую запись действий, поскольку в результате рефакторинга тестовых скриптов у нас уже появилась библиотека методов, позволяющая тестам выглядеть так:

procedure ChangeSplit(const ASplit: string);
begin
  GotoTab('Comm Splits');
  GotoFirstGridRow;
  EditCell('Split %');
  InputNewText(ASplit);
  InputText('[Enter]');
end;

Другой, менее очевидный способ тестирования таких приложений - создать внутри подсистему доступа к UI. Эта подсистема должна предоставлять все необходимые функции для эмуляции действий пользователя. Сами функциональные тесты будут являться просто частью приложения, и иметь полный доступ к внутреннему состоянию системы (которым, впрочем, им запрещено пользоваться). Очевидно, что тестовый код не должен входить в продукционную версию. Преимуществами этого подхода является его простота и высокое быстродействие. С другой стороны, это не совсем чистый подход: Edit1.Text = "abc" может работать не совсем так, как ввод пользователя символов a,b,c в поле редактирования, соответствующего Edit1.

Web приложения

Здесь также можно воспользоваться средствами автоматизации тестирования. Существуют специализированные средства тестирования для Web приложений. Многие средства, работающие c Win32 приложениями, могут также тестировать и Web приложения (например, новая версия TestComplete от AutomatedQA). Способ работы абсолютно такой же, как и с Win32 приложениями (включая возможность записи последовательности действий).

Другой способ - тестирование через DOM или JDOM документа. Например, положить IE control на форму в VB, загрузить нужную страницу и работать через ее DOM представление.

В силу исторических причин мы используем второй вариант. Специальное приложение на Delphi загружает Web страницу в ActiveX IE контрол и тестирует работу приложения. Результаты пишутся в общий лог через интерфейсы AQTest-а.

Инсталляция

Собственно тестирование процесса инсталляции не отличается от тестирования любого Win32 приложения. Проблема в том, чтобы воспроизвести исходное состояние системы до инсталляции. Можно каждый раз перед запуском теста вычищать все и перезагружать машину, но все равно нет гарантии, что она чиста. Автоматизировать восстановление чистой системы с помощью Ghost, например, тоже будет достаточно сложно.

Нам пришла на помощь замечательная программа VMWare - эмулятор отдельного компьютера в окне. Собственно жесткий диск виртуального компьютера выглядит как файл. Таким образом, можно подготовить чистую систему с QTest в виде виртуальной машины, автоматически запустить ее, дождаться загрузки, связаться через DCOM с QTest на виртуальной машине, и запустить там инсталляцию. В процессе инсталляции виртуальная машина даже может перезагружаться. Мы тестируем таким образом инсталляции в Win95, Win98, WinME, WinNT, W2K.

VMWare также может быть полезной, если ваше приложение должно тестироваться под различными операционными системами или в различных локализованных версиях. Просто запускайте ваш пакет функциональных тестов в различных профилях VMWare.

Воспроизведение исходного состояния

Большинство функциональных тестов будут описывать исходное состояние системы, действие пользователя и проверку нового состояния. Существует два способа задать исходное состояние:

Сам тест может создавать начальное состояние системы. Этот способ прост в сопровождении, но может быть сложен в создании. Например, для расчета комиссионных агенту придется создать 10-20 обьектов в системе.

Начальное состояние системы может быть задано вручную и записано в файл или базу данных. Этот способ проще в создании, но сложнее в сопровождении, поскольку каждый тест перестает быть самодостаточным.

Мы используем второй способ. У нас существует эталонная БД, содержимое которой соответствует исходным состояниям для всех тестов. При каждом прогоне пакета функциональных тестов эта база восстанавливается заново из резервной копии.

Тестирование результата

В практике встречается два способа тестирования результата - сравнение с эталоном и анализ.

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

При анализе код функционального теста получает необходимые данные и программным способом анализирует их. Этот способ проще в сопровождении и более избирателен.

Мы обычно используем первый способ, хотя в последнее время стараемся переходить на второй. Сравнение с эталоном приносит много проблем в сопровождении. Во-первых, трудно сделать эталон сфокусированным, то есть, нередко различия между результатом и эталонным результатом вызваны побочными эффектами, например, изменением системного формата даты. Во-вторых, при любом изменении поведения системы, ведущей к изменению эталонов, или при изменении механизма выгрузки придется заново генерировать эталоны, и проверять их.

В некоторых ситуациях анализ провести трудно, и сравнение с эталоном остается единственным методом. Пример такой ситуации - вывод на печать. Мы в своей системе используем встроенную возможность экспорта в RTF, и затем сравниваем эталонный и полученный RTF файлы.

Когда писать тесты

Поскольку Заказчик узнает о том, что User Story реализована путем выполнения тестов, то, стало быть, они должны быть готовы к концу итерации. В течение итерации (у нас обычно в первой половине) Заказчик предоставляет словесное описание тестов. Вместе с самой User Story, разработчики пишут функциональные тесты для нее. Как только тесты выполняются, Заказчик может пометить User Story как выполненную.

По-определению, Заказчик не может заранее предугадать все возможные ситуации, так что по мере реализации функции, разработчики будут задавать Заказчику вопросы о поведении системы в той или иной ситуации. Тут Заказчику представляется отличный шанс зафиксировать эти дополнительные требования в виде нового теста. При наличии недисциплинированного Заказчика можно принудительно внедрить протокол, обеспечивающий это: Вопрос об уточнении поведения системы задается только в виде теста с неопределенным результатом, и Заказчик отвечает на такой вопрос только в виде готового теста.

Следующий момент, когда надо писать функциональный тест - обнаружение ошибки. Обнаруженная ошибка должна быть воспроизведена в тесте. Действительно, тесты тестируют все, что может не работать. Если найдена ошибка, то значит, тесты тестировали не все. Естественно, они должны быть дополнены.

Тут возникает проблема. Не всякую ошибку стоит исправлять. Ошибки обычно имеют Severity (степень серьезности) и Priority (приоритет). Мы воспроизводим в тестах только те ошибки, которые по нашим правилам мы должны исправить немедленно.

В нашей команде сейчас есть проблема: не всегда ошибки воспроизводятся в тестах. Сказываются проблемы переходного периода. Раньше у нас было правило: разработчики смотрят в БД багов и исправляют открытые ошибки. Таким образом, тестировщику не обязательно делать тест для того, чтобы ошибку исправили. Как только это будет возможно, мы собираемся внедрить правило: разработчик не смотрит в БД ошибок и исправляет только неработающие тесты.

Кто пишет тесты

Теория говорит: тесты пишутся разработчиками или отдельной командой QA. В нашем случае, не все так однозначно. Если пытаться минимизировать эффект сломанного телефона (потери информации), то ситуация выглядит так:

Функциональные тесты, которые пишутся для сдачи User Story, должны писаться в той же команде, что и сама User Story.

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

Исторически у нас была отдельная команда тестировщиков. Я не думаю, что эту команду надо расформировывать с внедрением XP. С другой стороны, их уже надо называть программистами отдела QA, поскольку они большую часть своего времени программируют. Это имеет положительный эффект, поскольку ранее у нас были проблемы с тем, что никто не хотел быть тестировщиком.

Что делать с тестами

Функциональные тесты предназначены для того, чтобы показать Заказчику, что система работает, то есть все реализованные User Story выполняются правильно. И наоборот, функциональные тесты нужны для того, чтобы показать разработчику, что что-то не работает.

Все мы знаем, что чем раньше известно о проблеме, тем дешевле ее исправить. Значит, о неработающем функциональном тесте разработчик должен узнать как можно раньше. А, следовательно, прогонять тесты  надо как можно чаще, настолько часто, насколько это возможно. Можно прогонять их после каждой сборки билда. Если тесты  интенсивно добавляются, то можно прогонять их несколько раз на одном и том же билде. У нас весь набор тестов прогоняется после сборки билда - каждую ночь, иногда чаще.

Вы должны запускать ВСЕ имеющиеся тесты и получить два числа - общее число тестов и число прошедших. Это и есть состояние вашей системы. Представьте историческую последовательность этих чисел в виде графика и публикуйте его:

[Image]

Тесты должны адекватно показывать состояние проекта. Это, казалось бы, очевидно. Но посмотрите на реальный график полученный нами:

[Image]

Во-первых, система тестирования выдавала не общее число тестов, а число запускавшихся тестов. А это не всегда одно и то же. Из-за ошибок некоторые тесты могут вообще не запуститься. Они должны рассматриваться как не прошедшие.

Во-вторых, привязка тестирования к сборке билда сыграла с нами злую шутку - если билд не собрался, тесты все равно прогонялись и показывали непредсказуемый результат, который имеет мало общего с состоянием системы. Скорее всего, надо всегда тестировать последний хороший билд.

В-третьих, необходимо по возможности делать тесты независимыми. У нас некоторые тесты полагались на результаты работы предыдущих тестов. В результате, глядя на график, менеджер или Заказчик может подумать, что проект лихорадит, в то время как просто один не прошедший тест повлек за собой остановку десяти других.

Зачастую тест не проходит из-за ошибок в коде самого теста. К тому же, поскольку код тестов тоже надо будет рефакторить, через некоторое время у вас будет библиотеки тестовых классов и утилит. Поскольку сбой в подобной библиотеке приводит к тому, что результатам тестов становится нельзя доверять, то необходимо предпринимать дополнительные меры по обеспечению адекватности тестов.

Мы используем для этого такую систему: существуют две копии набора функциональных тестов - Золотая и Серебряная. Золотая копия прогоняется каждую ночь, и именно ее результаты являются окончательными. В Золотую копию запрещено добавлять тесты вручную. Она может быть создана только копированием Серебряной копии набора тестов. Серебряная копия может редактироваться, затем запускаются все тесты из нее, проверяются на адекватность, и если все хорошо, то она копируется в Золотую. Такой двухфазный подход заметно увеличивает адекватность тестов.

Что еще надо тестировать

Строго говоря, набор функциональных тестов не полностью гарантирует работоспособность системы. За рамками остаются поведение системы под нагрузкой, использование ресурсов, стабильность и отказоустойчивость.

Нагрузочное тестирование. Вообще говоря, даже если функциональный тест не оговаривает временные параметры работы системы, вы смело можете закладывать очевидные значения и в спорных случаях спрашивать Заказчика. Например, в интерактивной системе для всех тестов, связанных с действиями оператора, смело можно закладывать максимальное время реакции системы в 3 сек, или даже в 2 сек. Время работы теста проконтролировать очень легко, так же, как и рассчитать приемлемое время выполнения всего теста. Имея подобный контроль временных параметров в системе тестирования, несложно проводить нагрузочное тестирование в конце итерации - когда автоматизированные тесты прогоняются с большого числа машин. Мы пока не проводим таких нагрузочных тестов, но время выполнения функциональных тестов часто контролируем.

Использование ресурсов. Расход памяти или утечки памяти могут существенно влиять на фактическую работоспособность функционально безупречной системы. Поэтому, необходимо тестировать использование ресурсов. В нашем опыте не было случая, чтобы Заказчик мог внятно оговорить требования к использованию ресурсов системой. Обычно он ограничивается формулировкой вида: "Это должно сносно работать на <описание системы, которую можно купить в этот момент за $200>". Конечно, в случае разработки программы контроллера двигателя, эта ситуация будет совсем другой. В любом случае, если функциональные тесты не оговаривают требований к использованию ресурсов, имеет смысл тестировать хотя бы разумность их потребления. С утечками памяти все проще - просто запускайте функциональные тесты под системой контроля ресурсов (мы используем MemProof все той же AutomatedQA).

Отказоустойчивость может являться как подразумеваемым требованием: "система не должна падать при работе", так и более специфичным. Например, как должна вести себя система при пропаже связи с сервером БД. Второй случай должен описываться Заказчиком в тестах. Первый случай сложно проверить автоматически. Мы только могли воспроизводить известные сценарии, приводящие к падению системы.

Что нельзя протестировать

При всем оптимизме этой статьи и XP вообще, приходится признать, что все-таки существуют области, где автоматизированное тестирование невозможно, или точнее говоря, не стоит тех усилий, которые необходимо затратить. Вот некоторые примеры:
- правильность отрисовки графических элементов
- правильная смена курсоров, особенно при drag and drop
- моргание экрана при навигации и обновлении
- работа с внешними устройствами (например, синхронизация с PalmPilot)
- работа с нестандартными устройствами (у нас был такой LabelWriter), или в нестандартных режимах (640x480  16 цветов)

Другими словами, когда хорошо бы, чтобы на продукт перед сдачей кто-то просто смотрел глазами. Лучше всего, если вы найдете возможность самим использовать свой продукт (eat your own dog food).