Тестирование систем с графическим интерфейсом
Тестирование систем, насыщенных графическими интерфейсами, часто требует наличия специализированных инструментальных средств. Эти средства могут основываться на простой модели захвата/воспроизведения данных или могут потребовать специально для этой цели написанных сценариев для управления графическим интерфейсом. Некоторые системы объединяют элементы обеих моделей.
Менее сложные инструментальные средства предписывают высокую степень связывания тестируемой версии программы и самого тестового сценария: если вы перемещаете диалоговое окно или уменьшаете размер экранной кнопки, процедура тестирования может не найти всего этого и оказаться неудачной. Большинство современных инструментальных средств тестирования графических интерфейсов используют ряд методик, чтобы обойти эту проблему и попытаться приспособиться к незначительным различиям в компоновке.
Однако вы не можете автоматизировать все. Энди работал над графической системой, которая позволяла пользователю создавать и отображать недетерминированные визуальные эффекты, моделирующие различные природные явления. К сожалению, в ходе тестирования нельзя просто захватить растровое изображение и сравнить с предыдущим прогоном, потому что приложение было спроектировано так, что каждый раз оно выполнялось по-разному. В подобных ситуациях у вас может не быть выбора, кроме как положиться на ручную интерпретацию результатов теста.
Одним из преимуществ, возникающих при написании несвязанной программы (см. "Несвязанность и закон Деметера") является большая доля модульного тестирования. Например, для приложений, занимающихся обработкой данных, которые имеют внешний графический интерфейс, конструкция должна быть несвязанной в достаточной степени, чтобы можно было тестировать логику приложения в отсутствии графического интерфейса. Эта идея аналогична необходимости тестировать компоненты в числе первых. Как только достоверность логики приложения подтверждается, задача по поиску дефектов, которые выявляются при наличии пользовательского интерфейса, не представляет труда (скорее всего, эти дефекты были созданы программой интерфейса пользователя).
Тестирование самих тестов
Поскольку мы не можем писать совершенные программы, то из этого следует, что мы не можем написать и совершенные программы для тестирования. Нам необходимо тестировать сами тесты.
Рассматривайте набор тестовых пакетов как сложную систему безопасности, предназначенную для подачи звукового сигнала тревоги при выявлении дефекта. Ведь нет лучшего способа проверки безопасности системы, как попытаться вломиться в нее?
После того как вы написали тест для обнаружения конкретного дефекта, вызовите этот дефект преднамеренно и удостоверьтесь, что тест его обнаружил. Это гарантия того, что тест обязательно выловит этот дефект в реальных условиях.
Подсказка 64: Используйте диверсантов для тестирования самих тестов
Если вы серьезно относитесь к тестированию, то вы должны нанять диверсанта проекта, чья роль состоит в том, чтобы воспользоваться отдельной копией исходного дерева, преднамеренно внести дефекты и проверить, что при тестировании они будут выловлены.
При написании тестов убедитесь, что сигналы тревоги раздаются тогда, когда они обязаны раздаваться.
Исчерпывающее тестирование
Вы уверены в том, что ваши тесты являются корректными и обнаруживают созданные вами дефекты. Но как вы узнаете о том, насколько исчерпывающе вы провели тестирование ядра программы?
Ответ здесь краток: «никак», вы никогда это не узнаете. Но на программном рынке есть продукты, которые могут вам помочь. Эти средства анализа степени покрытия отслеживают программу при тестировании и регистрируют, какие строки были выполнены, а какие нет. Эти средства дают общее представление о том, насколько исчерпывающей является процедура тестирования, но не стоит ожидать, что степень покрытия составит 100 %.
Даже если выполненными окажутся все строки программы, это еще не все. Важно то число состояний, в которых может находиться программа. Состояния не являются эквивалентом строк программы. Предположим, что есть функция, обрабатывающая два целых числа, каждое из которых может принимать значения от 0 до 999.
int test(int a, int b) {
return a / (a + b)
}
Теоретически эта функция, состоящая из трех строк, имеет 1000000 логических состояний, 999999 из которых будут работать правильно, а одно – неправильно (когда а + b равно нулю). Если вам известно лишь то, что данная строка программы выполнена, то вам придется идентифицировать все возможные состояния программы. К сожалению, это очень сложная проблема. Настолько сложная, что "пока вы ее решите, солнце превратится в холодную глыбу".
Подсказка 65: Тестируйте степень покрытия состояний, а не строк текста программы
Даже при высокой степени покрытия программы данные, используемые вами в процедуре тестирования, все еще оказывают огромное влияние, и, что более важно, порядок, в котором вы выполняете программу, может оказать самое сильное воздействие.
Когда тестировать
Многие проекты стремятся отложить процедуру тестирование на последний момент – тот, где оно будет срезано в преддверии контрольного срока [51]. Нужно начать тестирование намного раньше наступления этого срока. Как только появится какая-либо рабочая программа, ее необходимо протестировать.
Большинство процедур тестирования должно выполняться автоматически. Важно заметить, что под термином «автоматически» мы имеем в виду и автоматическую интерпретацию результатов теста. Более подробно этот аспект рассматривается в разделе "Вездесущая автоматизация".
Мы предпочитаем проводить тестирование как можно чаще и всегда делаем это перед возвращением исходного текста в библиотеку. Некоторые системы управления исходным текстом, наподобие Aegis, могут осуществлять это автоматически. В других случаях мы просто набираем
% make test
Обычно не представляет труда запускать регрессии на всех отдельных модульных и комплексных тестах и проделывать это так часто, как это необходимо.
Но для ряда тестов частый прогон может представлять сложность. Для проведения нагрузочного тестирования могут потребоваться специальные настройки или оборудование и некоторая часть ручной работы. Эти тесты могут проводиться с меньшей частотой – возможно, еженедельно или ежемесячно. Но важно то, что они прогоняются на регулярной, запланированной основе. Если это нельзя сделать автоматически, то удостоверьтесь, что тесты включены в план вместе со всеми ресурсами, назначенными для данной задачи.
Кольцо сжимается
И наконец, мы хотели бы раскрыть единственный и самый важный принцип тестирования. Он очевиден, и практически в каждом учебнике говорится о том, что это нужно делать именно так. Но в силу некоторых причин в большинстве проектов этого все еще не делается.
Если дефект проскальзывает через сеть существующих тестов, вам необходимо добавить новый тест, чтобы поймать его в следующий раз.
Подсказка 66: Дефект должен обнаруживаться единожды
Если тестировщик обнаруживает дефект, это должно быть в первый и последний раз – обнаружение дефекта человеком. Автоматизированные тесты должны быть модифицированы для проверки наличия этого дефекта, начиная с момента его первоначального обнаружения, всякий раз, без каких-либо исключений, не обращая внимания на степень тривиальности, жалобы разработчика и его фразу "Этого больше не случится".
Потому что это снова случится. А у нас просто нет времени гоняться за дефектами, которые автоматизированные тесты не могли обнаружить. И нам придется тратить время на написание новой программы – с новыми дефектами.
Другие разделы, относящиеся к данной теме:
• Мой исходный текст съел кот Мурзик
• Отладка
• Несвязанность и закон Деметера
• Реорганизация
• Программа, которую легко тестировать
• Вездесущая автоматизация
Вопросы для обсуждения
• Можете ли вы осуществить автоматическое тестирование вашего проекта? Многие команды вынуждены дать отрицательный ответ. Почему? Слишком сложно определить приемлемые результаты? Не приведет ли к затруднениям попытка доказать спонсорам, что проект "сделан"?
Сложно ли проверить логику приложения независимо от графического интерфейса? Что можно сказать о графическом интерфейсе? О связывании?
Все эти сочинения
Лучше выцветшие чернила, чем отличная память.
Китайская пословица
Как правило, разработчики не размышляют над документацией слишком долго. В лучшем случае она является для них досадной необходимостью; в худшем случае она считается задачей с низким приоритетом в надежде на то, что руководство забудет о ней в конце работы над проектом.
Прагматики воспринимают документацию как неотъемлемую часть общего процесса разработки. Написание документации может быть облегчено, если вы не дублируете усилия, не теряете времени попусту и держите документацию под рукой, а если это возможно, – то в самой программе.
Эти мысли не отличаются оригинальностью и новизной; идея о брачном союзе программы и документации к ней появляется уже в работе Доналда Кнута о грамотном программировании и в утилите JavaDoc фирмы Sun. Мы хотим уменьшить противоречие между программой и документацией и вместо этого считать их двумя визуальными представлениями одной и той же модели (см. "Всего лишь визуальное представление"). На самом деле мы хотим пойти немножко дальше и применить все наши прагматические принципы к документации так, как мы применяем их к программам.
Подсказка 67: Считайте естественный язык одним из языков программирования
Существует два основных вида документации, которая готовится для проекта: внутренняя и внешняя. Внутренняя документация включает комментарии исходных текстов, документы, касающиеся проектирования и тестирования, и т. д. Внешняя документация – это то, что отправляется заказчику или публикуется для внешнего мира, например, руководство пользователя. Но вне зависимости от целевой аудитории или роли автора (разработчик он или технический писатель), вся документация является отражением программы. При наличии несоответствий программа – это то, что имеет значение.
Подсказка 68: Встраивайте документацию в проект, а не накручивайте ее сверху
Начнем с внутренней документации.
Комментарии в программе
Создать форматированные документы из комментариев и объявлений в исходном тексте довольно просто, но вначале нужно убедиться, в тексте программы действительно есть комментарии. Программа должна иметь комментарии, но слишком большое их количество может быть так же плохо, как и малое.
В общем, комментарии должны обсуждать, почему выполняется та или иная операция, ее задачу и ее цель. Программа всегда демонстрирует, как это делается, поэтому комментирование – избыточная информация и нарушение принципа DRY.
Создание комментариев в тексте исходной программы дает отличную возможность документировать неуловимые фрагменты проекта, которые не могут документироваться где-либо еще: технические компромиссы, почему было принято то или иное решение, какие альтернативные варианты были отвергнуты и т. д.
Мы предпочитаем увидеть простой комментарий в заголовке (на уровне модуля), комментарии к существенным данным и объявлениям типов и краткие заголовки для каждого из классов и методов, описывающие, как используется именно эта функция и все ее неочевидные действия.
Имена переменных должны выбираться четко и со смыслом. Например, имя foo, не имеет смысла, так же как doit или manager, или stuff. «Венгерский» стиль именования (в котором вы кодируете информацию о типе переменной в самом ее имени) крайне нежелателен в объектно-ориентированных системах. Не забывайте, что вы (и те, кто идет за вами) будут читать текст программы много сотен раз, но писать ее будут лишь несколько раз. Не торопитесь, и напишите connectionPool вместо ср.
Имена, вводящие в заблуждение, еще хуже, чем бессмысленные. Приходилось ли вам слышать, как кто-нибудь объясняет несоответствия в унаследованном тексте программы типа: "Подпрограмма с именем getData на самом деле записывает данные на диск"? Человеческий мозг будет периодически все путать – это называется эффектом Струпа [Str35]. Вы можете поставить на себе следующий эксперимент, чтобы увидеть эффект подобных помех. Возьмите несколько цветных ручек и напишите ими названия цветов спектра. Но при этом название цвета должно быть написано только ручкой другого цвета. Вы может написать слово «синий» зеленым цветом, слово «коричневый» – красным и т. д. (В качестве альтернативы имеется набор цветов спектра, уже помещенный на наш web-сайт www.pragmaticprogrammer.com.) Как только вы написали названия цветов, постарайтесь как можно быстрее произнести вслух название цвета, которым написано каждое слово. В определенный момент вы собьетесь и станете читать названия цветов, а не сами цвета. Имена очень важны для восприятия, а имена, вводящие в заблуждение, вносят беспорядок в программу.
Вы можете документировать параметры, но задайте себе вопрос, а нужно ли это делать во всех случаях. Уровень комментариев, предлагаемый средством JavaDoc, кажется весьма приемлемым:
/**
* Найти пиковое (наивысшее) значение в указанном интервале дат
* @param aRange Range of dates to search for data.
* @param aThreshold Minimum value to consider.
* @return the value, or <code>null<code> if no value found
* greater than or equal to the threshold.
*/
public Sample findPeak(Date Range aRange, double aThreshold);
Вот перечень того, чего не должно быть в комментариях к исходному тексту программы.
• Перечень функций, экспортируемых программой в файл. Существуют программы, которые анализируют исходный текст. Воспользуйтесь ими, и этот перечень никогда не устареет.
• Хронология изменений. Для этого предназначены системы управления исходным текстом программы (см. "Управление исходным текстом"). Однако, будет полезно включить информацию о дате последнего изменения и сотруднике, который внес это изменение [52].
• Список файлов, используемых данным файлом. Это можно более точно определить при помощи автоматических инструментальных средств.
• Имя файла. Если оно должно указываться в файле, не поддерживайте его вручную. Система RCS и ей подобные могут обновлять эту информацию автоматически. При перемещении и удалении файла вам не хочется вспоминать о необходимости редактирования заголовка.
Одним из наиболее важных фрагментов информации, который обязан появиться в исходном файле, – это имя автора, не обязательно того, кто осуществлял последнюю редакцию, но имя владельца. Приложение обязательств и ответственности к исходному тексту программы творит чудеса, сохраняя людей честными (см. "Гордость и предубеждение").
Проект также может потребовать наличия определенных ссылок на авторские права или других юридических стереотипов в каждом исходном файле. Сделайте так, чтобы программа редактирования вставляла эти элементы автоматически.
При наличии обширных комментариев инструментальные средства, подобные JavaDoc [URL 7] и DOC++ [URL 21], могут извлекать и форматировать их для автоматического создания документации на уровне API. Это является одним из конкретных примеров более универсальной методики, которой мы пользуемся, – исполняемые документы.
Исполняемые документы
Предположим, что есть спецификация, которая перечисляет столбцы в таблице базы данных. Тогда мы получим отдельный набор команд SQL для создания реальной таблицы в базе данных и, по всей вероятности, некую структуру записи на языке программирования для хранения содержимого строки в таблице. Одна и та же информация повторяется три раза. Стоит изменить один из этих трех источников – и два других немедленно устареют. Это явное нарушение принципа DRY.
Для решения этой проблемы необходимо выбрать авторитетный источник информации. Это может быть спецификация, инструментальное средство для построения схем баз данных или некий третий источник. Выберем в качестве источника спецификацию. Теперь она является моделью нашего процесса. Нам необходим способ экспортирования информации, содержащейся в ней, в виде различных визуальных представлений, например, в виде схемы базы данных и записи на языке программирования высокого уровня [53].
Если документ хранится в виде простого текста вместе с командами описания документов (например, в виде HTML, LATeX. или troff), то в этом случае можно использовать такие инструментальные средства, как Perl, для извлечения схемы и ее автоматического переформатирования. Если документ хранится в двоичном формате текстового процессора, то ознакомьтесь с некоторыми вариантами действий, приведенных во врезке, данной ниже.
Теперь документ – неотъемлемая часть разработки проекта. Единственным способом изменения схемы является изменение документа. Вы гарантируете, что спецификация, схема и программа находятся в согласии. Вы сводите к минимум работу, которую необходимо выполнить для внесения каждого изменения, и можете обновлять визуальные представления изменений автоматически.
Как быть, если мой документ не хранится в формате простого текста!
К сожалению, в настоящее время все больше проектной документации составляется с помощью текстовых процессоров, сохраняющих файл на диске в некоем определенном формате. Мы говорим "к сожалению", потому что это существенно ограничивает возможности автоматической обработки документа. Но у вас в запасе имеется еще два варианта:
• Создавайте макрокоманды. Сейчас большинство многофункциональных текстовых процессоров содержит встроенные макроязыки. Затратив некоторое усилие, вы можете запрограммировать их таким образом, чтобы экспортировать отмеченные разделы документов в альтернативные формы, которые вам необходимы. Если программирование на таком уровне является для вас болезненной процедурой, вы всегда можете экспортировать соответствующий раздел в файл, имеющий стандартный формат простого текста, а затем воспользоваться инструментальным средством наподобие Perl для преобразования его в окончательную форму.
• Сделайте документ подчиненным. Вместо того, чтобы использовать документ в качестве определяющего источника, возьмите другое представление. В примере с базой данных вы хотели бы использовать схему в качестве авторитетной информации. Тогда создайте средство, которое экспортирует эту информацию в ту форму, которую документ может импортировать. Однако при этом будьте внимательны. Вы должны быть уверены, что эта информация импортируется всякий раз, когда документ выводится на печать, а не единожды, при создании этого документа.
Аналогичным образом можно генерировать документацию на уровне API из исходного текста программы, пользуясь инструментальными средствами, такими как JavaDoc и DOC++. Моделью является исходный текст программы: компилироваться может одно визуальное представление модели; другие представления предназначены для вывода на печать или просмотра на web-странице. Наша цель – работа над моделью (неважно, является ли эта модель самой программой или же каким-либо иным документом), и мы должны добиться того, чтобы все эти представления обновлялись автоматически (см. "Вездесущая автоматизация").
Внезапно документация оказывается не столь уж плохой.
Технические писатели
До этого момента мы говорили лишь о внутренней документации, той которую составляют сами программисты. Но что происходит, если в вашем проекте участвуют профессиональные технические писатели? Слишком часто программисты просто «перекидывают» материал техническим писателям и дают им возможность заработать себе на жизнь, создавая руководства пользователей, рекламные материалы и т. д.
Это является ошибкой. То, что программисты не составляют такие документы, вовсе не означает, что мы можем поступиться прагматическими принципами. Мы хотим, чтобы писатели восприняли те же основные принципы, что и прагматики, – соблюдали принципы DRY, ортогональности, а также концепцию "модель-визуальное представление", применяли автоматизацию и сценарии.