Передача требований
Одним из самых распространенных аспектов общения между программистами и бизнесом является разработка требований. Бизнесмены описывают то, что по их мнению им нужно, а программисты создают то, что по их мнению им описали.
По крайней мере так должно быть. На практике информационный обмен проходит чрезвычайно сложно, с высоким риском ошибок.
В 1979 году во время работы в Teradyne меня посетил Том, руководитель отдела установки и обслуживания. Он попросил меня научить его работать в текстовом редакторе ED-402, чтобы он мог организовать простую систему обработки заявок на устранение неисправностей.
ED-402 был коммерческим редактором, написанным для компьютера M365 – клона PDP-8. Это был очень мощный текстовый редактор со встроенным языком сценариев, который мы использовали для разнообразных операций с текстом.
Том не был программистом, но приложение, которое он себе представлял, было довольно простым. Том полагал, что я быстро научу его, а он сам напишет приложение. Я по наивности думал то же самое. В конце концов, язык сценариев представлял собой обычный макроязык команд редактирования, с простейшими условными и циклическими конструкциями.
Итак, мы сели за компьютер, и я спросил, что должно делать его приложение. Том начал с экрана ввода данных. Я показал, как создать текстовый файл со сценарными командами и как ввести в сценарий символьное представление команд редактирования. Но когда я взглянул ему в глаза, ответный взгляд был совершенно пустым. Он не понял ни единого слова из моего объяснения.
Тогда я впервые встретился с этим явлением. Для меня символьное представление команд редактирования было простым и естественным: например, для представления команды Control+B (команда перемещения курсора в начало текущей строки) в файл сценария просто включались символы ^B. Но Тому это казалось бессмысленным: он не мог перейти от редактирования файла к редактированию файла, который редактировал файл.
Том не был глуп. Наверное, он просто понял, что задача намного более сложная, чем ему казалось изначально, и не захотел тратить свое время и умственные силы на изучение такой хитроумной концепции, как использование редактора для управления редактором.
Мало-помалу я начал сам реализовывать приложение Тома, пока он сидел и наблюдал. Через 20 минут стало ясно, что он уже думает не о том, как сделать все самому, а о том, как объяснить мне, что именно ему нужно.
На это ушел целый день. Том описывал некоторую возможность, а я реализовывал ее, пока он наблюдал. Рабочий цикл составлял не более 5 минут, поэтому ему не нужно было отходить и заниматься чем-то другим. Он просил меня сделать X, и через 5 минут реализация X работала.
Часто Том рисовал то, что он хотел, на листке бумаги. Некоторые из его требований было трудно реализовать средствами ED-402; тогда я предлагал что-то другое. В конечном итоге мы договаривались до чего-то, что могло работать, и я реализовывал согласованный вариант. Но потом мы опробовали его, и Том менял свое решение. Он говорил что-нибудь вроде: «Да, но это не совсем то, что мне нужно. Давай попробуем иначе». Час за часом мы пробовали, экспериментировали и придавали приложению нужную форму. Мы опробовали одно, потом другое, потом третье. Вскоре мне стало абсолютно ясно, что Том – скульптор, а я – инструмент в его руках.
В конечном итоге Том получил желаемое приложение, но не имел ни малейшего понятия о том, как построить следующее приложение самостоятельно. С другой стороны, я получил важный урок относительно того, как заказчик определяет свои потребности. Оказалось, что его представление о функциональности приложения часто не переживает контакта с компьютером.
Преждевременная точность
И бизнесмены, и программисты часто попадают в ловушку преждевременной точности. Бизнесмены хотят точно знать размер прибыли, прежде чем давать согласие на проект. Разработчики хотят точно знать, что им предстоит сделать, прежде чем давать прогнозы по проекту. Обе стороны желают иметь точную информацию, которой быть заведомо не может, причем часто они готовы потратить целое состояние на попытки ее получения.
Принцип неопределенности
К сожалению, на бумаге все выглядит совсем не так, как в рабочей системе. Когда бизнесмены видят, что получается из реализации их требований в системе, они понимают, что хотели совсем не этого. А иногда уже после знакомства с реализацией они начинают лучше понимать, что же им на самом деле нужно – чаще всего не то, что они видят.
В игру вступает «эффект наблюдателя», или принцип неопределенности. Когда вы демонстрируете новую возможность бизнес-участникам, у них появляется больше информации, чем было прежде, а эта новая информация влияет на их восприятие системы в целом. В конечном итоге чем точнее формулируете свои требования, тем быстрее они теряют актуальность в ходе реализации системы.
Стремление к точности оценки
Разработчики тоже попадают в ловушку точности. Они знают, что должны выдать оценку системы, и часто думают, что их оценка должна быть точной. Ничего подобного!
Во-первых, даже с идеальной информацией в ваших оценках будет наблюдаться огромный разброс. Во-вторых, принцип неопределенности разносит раннюю точность в пух и прах. Требования будут изменяться, а это приведет к тому, что точность потеряет смысл.
Профессиональные разработчики понимают, что оценки могут (и должны) базироваться на требованиях с низкой точностью. Они понимают, что оценка – это именно оценка. Они всегда включают в свои оценки диапазоны погрешности, чтобы бизнес-участники понимали наличие неопределенности (см. главу 10).
Поздняя неоднозначность
Как решить проблему преждевременной точности? Откладывая точные определения на как можно более поздний срок. Профессиональные разработчики не формулируют требования до того, как будут готовы к разработке. Однако такой подход может привести к другому несчастью: поздней неоднозначности.
Между ключевыми участниками проекта часто возникают разногласия. В таких случаях часто бывает проще «заговорить» проблему, чем решать ее. Они находят такую формулировку требования, с которой будут согласны все, без реального разрешения спора. Однажды я слышал, как Том Демарко сказал: «неоднозначность в документе с требованиями появляется из-за разногласий между участниками».
Конечно, неоднозначность не всегда появляется из-за споров или разногласий. Иногда ключевые участники проекта просто считают, что читатели требований знают, что они имеют в виду. Возможно, их формулировки абсолютно ясны им самим в их контексте, но означают нечто совершенно иное для программиста, читающего документ. Подобная контекстная неоднозначность также может возникать и при личном общении заказчиков с программистами.
Сэм (ключевой участник проекта): «Так, еще нужно организовать резервное копирование журнальных файлов».
Пола: «С какой частотой?»
Сэм: «Ежедневно».
Пола: «Понятно. И где будут храниться резервные копии?»
Сэм: «В смысле?»
Пола: «Наверное, они должны сохраняться в определенном подкаталоге?»
Сэм: «Да, пожалуй».
Пола: «И как он будет называться?»
Сэм: «Может, backup?»
Пола: «Да, нормально. Значит, журнал будет ежедневно сохраняться в каталоге backup. В какое время?»
Сэм: «Ежедневно».
Пола: «Нет, в какое именно время суток?»
Сэм: «В любое».
Пола: «В полдень?»
Сэм: «Нет, только не во время торгов. Лучше в полночь».
Пола: «Хорошо, пусть будет полночь».
Сэм: «Отлично, спасибо!»
Пола: «Всегда рада помочь».
Позднее Пола рассказывает о задаче своему коллеге Питеру.
Пола: «Значит, журнал должен копироваться в подкаталог с именем backup ежедневно в полночь».
Питер: «Понятно. А как будет называться файл?»
Пола: «Я думаю, log.backup подойдет».
Питер: «Хорошо».
В другом офисе Сэм общается по телефону с заказчиком.
Сэм: «Да, да, журнальные файлы будут сохраняться».
Карл: «Очень важно, чтобы ни один журнал не был потерян. Нельзя исключать, что нам придется когда-нибудь вернуться к любому из этих файлов, даже через месяцы или годы – в случае сбоя питания, какой-нибудь катастрофы, судебного спора и т. д.».
Сэм: «Не беспокойтесь, я только что говорил с Полой. Журналы будут сохраняться в каталоге с именем backup ежедневно в полночь».
Карл: «Хорошо, это подойдет».
Вы заметили неоднозначность? Заказчик ждет, что сохраняться будут все журналы, а Пола думает, что достаточно сохранить журнал за последний день. Если заказчик захочет вернуться к резервной копии месячной давности, он найдет только данные последнего дня.
В данном случае и Пола, и Сэм оказались не на высоте. Профессиональные разработчики (и менеджеры) должны следить за тем, чтобы из требований была исключена всякая неоднозначность.
Это сложная задача, и мне известен только один способ ее решения.
Приемочные тесты
Термин «приемочные тесты» перегружен множеством значений. Одни полагают, что речь идет о тестах, выполняемых пользователями перед приемкой очередной версии продукта. Другие понимают под этим термином контроль качества. В этой главе под термином «приемочные тесты» понимаются тесты, написанные при совместном участии заинтересованных сторон и программистов для проверки выполнения требований.
Что такое «выполнено»?
Одна из самых распространенных неоднозначностей, с которыми сталкиваются профессиональные разработчики, связана с самим понятием «выполнения». Когда разработчик говорит, что он выполнил свою задачу, что он имеет в виду? Что он готов передать свой код в эксплуатацию с полной уверенностью? Что его код готов к прохождению контроля качества? А может, что он дописал свой код и даже смог один раз выполнить его, но еще не подвергал тестированию?
Я работал с группами, имевшими разные представления о терминах «выполнено» и «готово». В одной группе использовались термины «сделано» и «совсем сделано». У профессиональных разработчиков есть только одно определение: выполнено – значит выполнено. Сделано – значит, что весь код написан, все тесты пройдены, служба контроля качества и ключевые участники приняли результат. Сделано.
Но как добиться такого уровня выполнения, не снижая темпа перехода между итерациями? Нужно создать набор автоматизированных тестов, прохождение которых означает выполнение всех перечисленных условий! Если приемочные тесты вашей подсистемы проходят успешно, значит, работа выполнена.
Профессиональные разработчики расширяют определение требований до автоматизированных приемочных тестов. Они общаются с ключевыми участниками и специалистами по контролю качества, стремясь к тому, чтобы автоматизированные тесты полностью отражали определение «выполнения».
Сэм: «Так, еще нужно организовать резервное копирование журнальных файлов».
Пола: «С какой частотой?»
Сэм: «Ежедневно».
Пола: «Понятно. И где будут храниться резервные копии?»
Сэм: «В смысле?»
Пола: «Наверное, они должны сохраняться в определенном подкаталоге?»
Сэм: «Да, пожалуй».
Пола: «И как он будет называться?»
Сэм: «Может, backup?»
Том (тестер): «Погодите, backup – слишком общее название. Что именно будет храниться в этом каталоге?»
Сэм: «Резервные копии».
Том: «Резервные копии чего?»
Сэм: «Журнальных файлов».
Пола: «Но ведь журнальный файл всего один?»
Сэм: «Нет, их много. По одному на каждый день».
Том: «То есть у нас будет один активный журнал и много резервных копий?»
Сэм: «Конечно».
Пола: «О! А я думала, копии будут постоянно замещаться».
Сэм: «Нет, заказчик хочет, чтобы они хранились неограниченно долго».
Пола: «Для меня это новость. Хорошо, что вовремя разобрались».
Том: «Имя подкаталога должно сообщать, что именно в нем хранится».
Сэм: «Там будут храниться старые неактивные журналы».
Том: «Тогда мы назовем его old_inactive_logs».
Сэм: «Отлично».
Том: «И когда будет создаваться этот каталог?»
Сэм: «В смысле?»
Пола: «Каталог будет создаваться при запуске системы, но только в том случае, если он не существует».
Том: «Понятно, это наш первый тест. Я запускаю систему и проверяю, создан ли каталог old_inactive_logs. Затем я добавляю файл в этот каталог, завершаю работу, снова запускаю систему и проверяю, что каталог и файл находятся на положенном месте».
Пола: «На выполнение этого теста уйдет много времени. Инициализация системы уже занимает около 20 секунд, и время только растет. К тому же я не хочу собирать систему заново при каждом запуске приемочных тестов».
Том: «Что ты предлагаешь?»
Пола: «Создадим класс SystemStarter. Основная программа будет загружать его с группой объектов StartupCommand, построенных на базе паттерна «Команда». Затем во время запуска системы SystemStarter просто приказывает всем объектам StartupCommand выполнить свои команды. Один из этих объектов StartupCommand создает старый каталог old_inactive_logs, но только в том случае, если он не существует».
Том: «Ладно, тогда мне остается только протестировать этого потомка StartupCommand. Я напишу для этого простой тест FitNesse».
(Том идет к доске.)
«Первая часть будет выглядеть примерно так»:
given the command LogFileDirectoryStartupCommand
given that the old_inactive_logs directory does not exist
when the command is executed
then the old_inactive_logs directory should exist and it should be empty
«А вторая часть будет примерно такой»:
given the command LogFileDirectoryStartupCommand
given that the old_inactive_logs directory exists and that it contains a file named x
When the command is executed
Then the old_inactive_logs directory should still exist and it should still contain a file named x
Пола: «Да, этого должно хватить».
Сэм: «А это все действительно необходимо?»
Пола: «Сэм, какое из этих двух утверждений недостаточно важно для проверки?»
Сэм: «Просто я хочу сказать, что для написания всех этих тестов потребуется немало лишней работы».
Том: «Да, но не больше, чем для написания плана ручного тестирования. И намного меньше, чем для многократного выполнения тестов вручную».
Взаимодействие сторон
Основные цели приемочных тестов – взаимодействие сторон, ясность и точность требований. В результате согласования приемочных тестов разработчики, ключевые участники и тестеры достигают понимания планируемого поведения системы. Достижение такой ясности – обязанность всех сторон. Профессиональные разработчики считают своей обязанностью работать с ключевыми участниками и тестерами и принимать меры к тому, чтобы все стороны знали, что именно они собираются построить.
Автоматизация
Приемочные тесты всегда должны быть автоматизированными. В других моментах жизненного цикла программных продуктов находится место для ручного тестирования, но такие тесты никогда не должны выполняться вручную. Причина проста: затраты.
Взгляните на рис. 7.1. Руки, которые вы на нем видите, принадлежат менеджеру по контролю качества крупной интернет-компании. В документе, который он держит, содержится оглавление его плана ручного тестирования. Он оплачивает целую армию тестеров из других стран, которые выполняют этот план каждые шесть недель. Каждое тестирование обходится примерно в миллион долларов. Он только что вернулся с собрания, на котором руководитель сообщил, что бюджет тестирования будет урезан примерно на 50 %, а теперь спрашивает меня: «Какую половину этих тестов не нужно выполнять?»
Назвать происходящее катастрофой значило бы не сказать ничего. Затраты на ручное тестирование настолько велики, что фирма решила отказаться от него – и просто жить дальше, не зная, работает ли половина ее продукта!
Рис. 7.1. План ручного тестирования
Профессиональные разработчики не допускают возникновения подобных ситуаций. Затраты на проведение автоматизированных тестов настолько малы по сравнению с затратами на ручное тестирование, что написание сценариев, запускаемых вручную, не имеет никакого экономического смысла. Профессиональные разработчики считают своей обязанностью проследить за тем, чтобы приемочные тесты проводились в автоматизированном режиме.
Существует множество программных инструментов (как коммерческих, так и с открытым кодом), автоматизирующих приемочные тесты. FitNesse, Cucumber, cuke4duke, robot framework и Selenium – этот список далеко не полон. Во всех этих инструментах автоматизированные тесты определяются в такой форме, что даже не-программисты могут читать их, понимать и даже создавать.
Дополнительная работа
Замечание Сэма насчет лишней работы понятно. На первый взгляд кажется, что написание подобных приемочных тестов потребует значительных усилий. Но взглянув на рис. 7.1, мы видим, что эту работу неправильно называть «лишней». Написание тестов всего лишь является работой по определению спецификации системы. Только на таком уровне детализации мы, программисты, понимаем, что означает «выполненная работа». Только на таком уровне детализации ключевые участники проекта могут убедиться в том, что система, за которую они платят, делает то, что требуется. И только на таком уровне детализации возможна успешная автоматизация тестирования. Так что не стоит рассматривать эти тесты как лишнюю работу – лучше рассматривайте их как значительную экономию времени и денег. Тесты предотвратят ошибки в реализации системы и помогут узнать, когда ваша работа закончена.
Кто и когда пишет приемочные тесты?
В идеальном мире ключевые участники проекта и служба контроля качества сотрудничают в написании этих тестов, а разработчики проверяют их на логическую непротиворечивость. В реальном мире ключевые участники редко находят время или желание погружаться на нужный уровень детализации, поэтому они перепоручают эту обязанность бизнес-аналитикам, специалистам по контролю качества или даже разработчикам. Если окажется, что тесты должны писать разработчики, по крайней мере проследите за тем, чтобы это были не те разработчики, которые занимаются реализацией тестируемой функциональности.
Бизнес-аналитики обычно пишут «оптимистичные» версии тестов, потому что эти тесты описывают аспекты, обладающие коммерческой ценностью. Служба контроля качества обычно пишет «пессимистичные» тесты с проверкой всевозможных граничных условий, исключений и аномальных случаев. И это понятно, потому что задача контроля качества – думать о том, что может пойти не так.
Согласно принципу «поздней точности» приемочные тесты следует писать как можно позднее, обычно за несколько дней до реализации. В проектах на базе гибких методологий тесты пишутся после выбора функций для следующей итерации или спринта.
Первые приемочные тесты должны быть готовы к первому дню итерации. Новые тесты должны появляться ежедневно вплоть до середины итерации, когда готовы должны быть все тесты. Если к середине итерации некоторые приемочные тесты еще не готовы, переведите нескольких разработчиков на их срочную доработку. Если это происходит часто, включите в команду дополнительных бизнес-аналитиков и/или специалистов по контролю качества.
Роль разработчика
Работа по реализации некоторого аспекта системы начинается тогда, когда для этого аспекта готовы все приемочные тесты. Разработчики выполняют приемочные тесты и убеждаются в том, что те не проходят. Затем они связывают приемочные тесты с системой и начинают работать над реализацией нужного аспекта, обеспечивая прохождение приемочных тестов.
Пола: «Питер, поможешь мне?»
Питер: «Разумеется, а что случилось?»
Пола: «Вот наш приемочный тест. Как видишь, он не проходит».
given the command LogFileDirectoryStartupCommand
given that the old_inactive_logs directory does not exist
when the command is executed
then the old_inactive_logs directory should exist and it should be empty
Питер: «Да, все результаты красные. Ни один сценарий еще не написан. Давай я напишу первый»:
|scenario|given the command _|cmd|
|create command|@cmd|
Пола: «А у нас уже есть операция createCommand»?
Питер: «Да, в пакете CommandUtilitiesFixture, который я написал на прошлой неделе».
Пола: «Хорошо, давай запустим тест».
Питер: (запускает тест) «Первая строка стала зеленой, переходим к следу ющей».
Не обращайте внимания на все эти сценарии (scenarios) и оснастки (fixtures) – это всего лишь служебный код, который необходимо написать для связывания тестов с тестируемой системой. Достаточно сказать, что все перечисленные инструменты предоставляют те или иные средства поиска по шаблону для распознавания и разбора инструкций теста и последующего вызова функций, передающих данные тестируемой системе. Все это делается достаточно просто, а полученные сценарии и оснастки могут использоваться в разных тестах.
Суть в том, что разработчик должен связать приемочные тесты с системой, а затем обеспечить их прохождение.
Обсуждение тестов и пассивно-агрессивная позиция
Авторы тестов – люди, и они тоже допускают ошибки. Иногда при переходе к реализации становится очевидно, что тест выглядит бессмысленно. Тесты бывают слишком запутанными или громоздкими.
Они могут базироваться на нелепых предпосылках. А иногда они попросту неверны. Все это может быть весьма неприятно, если вы – разработчик, который должен обеспечить прохождение теста.
Ваша задача как профессионального разработчика – обсудить ситуацию с автором теста для его улучшения. Никогда не выбирайте пассивно-агрессивную позицию, когда вы говорите себе: «Как написано в тесте, так я и сделаю».
Помните: профессионал обязан помочь своей группе создать программу настолько качественную, насколько это возможно. А это означает, что все должны уделять внимание возможным ошибкам и промахам коллег и совместно работать над их исправлением.
Пола: «Том, с этим тестом что-то не то».
ensure that the post operation finishes in 2 seconds.
Том: «По-моему, все нормально. Наше требование гласит, что пользователи не должны ждать больше двух секунд. В чем проблема?»
Пола: «Проблема в том, что мы можем гарантировать выполнение требования только в статистическом смысле».
Том: «По-моему, это только слова. В требованиях ясно сказано: две секунды».
Пола: «Верно, и мы можем обеспечить этот результат в 99,5 % случаев».
Том: «Пола, в требовании этого нет».
Пола: «Мы живем в реальном мире. Я не могу предоставить других гарантий».
Том: «Сэм будет в бешенстве».
Пола: «Вообще-то я с ним уже пару раз обсуждала эту тему. Он согласен, если типичная продолжительность операции будет не более двух секунд».
Том: «Ну и как мне писать этот тест? Я же не могу сказать: „операция обычно заканчивается за две секунды“».
Пола: «Сформулируй на статистическом уровне».
Том: «Предлагаешь выполнить операцию тысячу раз и проверить, что она заняла более двух секунд в пяти и менее случаях? Абсурд».
Пола: «Нет, это займет слишком много времени. А как насчет этого?»
execute 15 post transactions and accumulate times.
ensure that the Z score for 2 seconds is at least 2.57
Том: «А что еще за z-показатель?»
Пола: «Так, статистика. А как тебе такая формулировка?»
execute 15 post transactions and accumulate times.
ensure odds are 99.5 % that time will be less than 2 seconds
Том: «Да, это уже понятнее, но можно ли доверять математике?»
Пола: «Я обязательно включу все промежуточные вычисления в отчет по тестированию, чтобы ты мог проверить вычисления, если у тебя останутся сомнения».
Том: «Хорошо, меня это устроит».
Приемочные тесты и модульные тесты
Не путайте приемочные тесты с модульными (unit tests). Модульные тесты пишутся программистами для программистов. Они представляют собой формальные архитектурные документы с описанием нижнего уровня структуры и поведения кода. Их читателями являются не бизнесмены, а программисты.
Приемочные тесты создаются бизнесменами для бизнесменов (даже если в конечном итоге их пишете вы, разработчик). Они представляют собой формальные описания требований, определяющие поведение системы с точки зрения бизнеса. Их читателями являются бизнесмены и программисты.
Возможно, у кого-то возникнет соблазн избавиться от лишней работы, предположив, что тесты двух видов избыточны. Но хотя модульные и приемочные тесты часто тестируют одно и то же, никакой избыточности в них нет.
Во-первых, даже если они тестируют одно и то же, при этом используются разные пути и механизмы. Модульные тесты углубляются во внутреннюю реализацию системы и вызывают методы конкретных классов. Приемочные тесты обращаются к системе на значительно более высоком уровне – уровне API или даже уровне пользовательского интерфейса. Таким образом, пути выполнения этих тестов сильно различаются.
Но настоящая причина, по которой эти тесты нельзя назвать избыточными, заключается в том, что тестирование не является их главной функцией. Тот факт, что они что-то проверяют, вторичен. Модульные и приемочные тесты прежде всего являются документами и лишь потом – тестами. Их главная цель – формальное документирование архитектуры, структуры и поведения системы. Автоматическая проверка архитектуры, структуры и поведения чрезвычайно полезна, но истинной целью является именно документирование.
Графические интерфейсы и другие сложности
Графический интерфейс трудно определить заранее. Теоретически это возможно, но редко удается сделать хорошо. Дело в том, что эстетика – предмет субъективный, а следовательно, переменчивый. Разработчики любят повозиться со своими графическими интерфейсами, доводить их до ума и шлифовать. Они хотят использовать разные шрифты, цвета, макеты и схемы выполнения операций. Графические интерфейсы находятся в постоянном развитии.
Это обстоятельство усложняет написание приемочных тестов для графических интерфейсов. Задача решается таким проектированием системы, при котором графический интерфейс можно было бы рассматривать как API, а не как набор кнопок, ползунков, таблиц и меню. На первый взгляд кажется странно, но в действительности речь идет просто о качественном проектировании.
В области проектирования программных систем существует принцип, называемый принципом единственной обязанности (SRP, Single Responsibility Principle). Он гласит, что при проектировании следует разделять аспекты системы, которые могут изменяться по разным причинам, и группировать вместе те аспекты, которые изменяются по одним и тем же причинам. Графические интерфейсы не являются исключением.
Макет, формат и схема выполнения операций в графическом интерфейсе могут меняться по эстетическим причинам и по соображениям эффективности, но базовая функциональность графического интерфейса остается неизменной. Таким образом, при написании приемочных тестов для графического интерфейса следует пользоваться базовыми абстракциями, которые изменяются относительно редко.
Например, на странице могут находиться несколько кнопок. Вместо создания тестов, имитирующих нажатия кнопок по их положению на странице, следует предусмотреть возможность имитации нажатий с идентификацией по именам. Еще лучше, если у каждой кнопки будет уникальный идентификатор. При написании теста намного приятнее выбирать кнопку с идентификатором ok_button, чем кнопку в столбце 3 строки 4 таблицы элементов.
Выбор интерфейса для тестирования
И все же лучше писать тесты, которые активизируют функции тестируемой системы через API, а не через графический интерфейс. При этом должен использоваться тот же API, который используется графическим интерфейсом. В этом нет ничего нового: специалисты в области проектирования десятилетиями говорили нам, что графический интерфейс нужно отделять от бизнес-логики.
Тестирование через графический интерфейс всегда создает проблемы (если вы не ограничиваетесь тестированием одного лишь графического интерфейса). Дело в том, что графический интерфейс с большой вероятностью изменится, а это делает тесты весьма непрочными. Когда каждое изменение интерфейса нарушает работу тысячи тестов, вы либо начинаете отказываться от тестов, либо перестаете изменять графический интерфейс. Ни один из этих вариантов хорошим не назовешь. Итак, пишите тесты бизнес-логики так, чтобы они проходили через API, находящийся за графическим интерфейсом.
Некоторые приемочные тесты определяют поведение самого графического интерфейса. Естественно, такие тесты должны проходить через графический интерфейс. Однако они не тестируют бизнес-логику, а следовательно, не требуют ее связывания с графическим интерфейсом. По этой причине на время тестирования самого графического интерфейса лучше изолировать его от бизнес-логики и заменить последнюю заглушками.
Ограничьтесь минимальным объемом тестов графического интерфейса. Они слишком непрочны из-за изменчивости графического интерфейса. Чем больше у вас тестов графического интерфейса, тем меньше вероятность того, что они останутся неизменными.
Непрерывная интеграция
Проследите за тем, чтобы все модульные и приемочные тесты запускались несколько раз в день в механизме непрерывной интеграции. Этот механизм должен инициироваться вашей системой управления исходным кодом. Каждый раз, когда кто-то вносит в базу новый модуль, механизм непрерывной интеграции должен инициировать сборку с последующим запуском всех тестов в системе. Результаты запуска должны рассылаться всем участникам группы.
Стоп-сигнал
Очень важно, чтобы тесты непрерывной интеграции все время проходили успешно. Они никогда не должны завершаться отказом. В случае отказа вся группа прекращает заниматься текущими делами и направляет все усилия на то, чтобы обеспечить успешное прохождение всех тестов. Сборка в системе с непрерывной интеграцией должна рассматриваться как экстренное событие, своего рода «стоп-сигнал».
Я общался с группами, которые недостаточно серьезно относились к отказам в тестах. Такие группы обычно были «слишком заняты», чтобы решать проблему немедленно, поэтому тесты откладывались в сторону до лучших времен. В одном случае нерабочие тесты были попросту исключены из сборки, потому что программистов раздражали сообщения об отказах. Позднее, уже после сдачи продукта заказчику, они вдруг вспомнили, что забыли вернуть тесты в сборку. Это выяснилось уже после того, как разгневанный заказчик забросал их сообщениями об ошибках.