Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
ГЛАВА 22 Тестирование, выполняемое разработчиками 511 работке требований или проектировании. Возможно, исправить отдельную ошиб- ку конструирования и дешевле, но при учете всех ошибок ситуация меняется. Ниже приведены некоторые мои выводы. 쐽 В небольших проектах дефекты конструирования составляют боль- шинство всех ошибок. В одном исследовании ошибок кодирования, до- пущенных в небольшом проекте (1000 строк кода), было обнаружено, что 75% дефектов пришлось на этап кодирования, тогда как на этап выработки тре- бований — 10%, а на этап проектирования — 15% (Jones, 1986a). По-видимому, такое распределение ошибок характерно для многих небольших проектов. 쐽 Дефекты конструирования составляют минимум 35% всех дефектов независи- мо от размера проекта. В крупных проектах доля дефектов конструирования не так велика, как в небольших, но и тогда она равна минимум 35% (Beizer, 1990; Jones, 2000). Некоторые ученые сообщают, что даже в очень крупных проек- тах дефектами конструирования являются около 75% всех ошибок (Grady, 1987). Обычно чем лучше разработчики знают прикладную область, тем лучше они проектируют общую архитектуру системы. В этом случае на детальное проек- тирование и кодирование приходится еще больший процент ошибок (Basili and Perricone, 1984). 쐽 Хотя ошибки конструирования и дешевле исправлять, чем ошибки выработки требований и проектирования, это все равно дорого. Исследование двух очень крупных проектов, проведенное в Hewlett-Packard, показало, что стоимость исправления среднего дефекта конструирования составляла 25–50% от стоимо- сти исправления средней ошибки проектирования (Grady, 1987). При учете большего числа дефектов конструирования общая стоимость их исправления могла вдвое превышать стоимость исправления дефектов проектирования. Вот примерное отношение между объемом проекта и распределением ошибок (рис. 22-2): Рис. 22-2. По мере увеличения объема проекта доля ошибок, допускаемых во время конструирования, снижается. Тем не менее ошибки конструирования составляют 45–75% всех ошибок даже в самых крупных проектах 512 ЧАСТЬ V Усовершенствование кода Сколько ошибок можно ожидать? Ожидаемое число ошибок зависит от качества вашего процесса разработки. Вот некоторые соображения по этому поводу. 쐽 Средний для отрасли показатель таков: примерно 1–25 ошибок на 1000 строк кода в готовом ПО, разрабатывавшегося с использованием разных методик (Boehm, 1981; Gremillion, 1984; Yourdon, 1989a; Jones, 1998; Jones, 2000; Weber, 2003). Проекты, в которых обнаруживается 0,1 от указанного числа ошибок, редки; о случаях, когда ошибок в 10 раз больше, предпочитают не сообщать (вероятно, такие проекты даже не доводят до конца!). 쐽 Отделение прикладного ПО компании Microsoft сообщает о таких показате- лях, как 10–20 дефектов на 1000 строк кода во время внутреннего тестирова- ния и 0,5 дефекта на 1000 строк кода в готовой продукции (Moore, 1992). Для достижения этого уровня применяется комбинация методик чтения кода, опи- санных в разделе 21.4, и независимого тестирования. 쐽 Харлан Миллз придумал «разработку методом чистой комнаты» — методику, позволяющую достигнуть всего лишь 3 дефектов на 1000 строк кода во время внутреннего тестирования и 0,1 дефекта на 1000 строк кода в готовой системе (Cobb and Mills, 1990). В нескольких проектах — например, в проекте разработ- ки ПО для космических кораблей — благодаря сочетанию методик формальной разработки, обзоров кода коллегами и статистического тестирования был до- стигнут такой уровень, как 0 дефектов на 500 000 строк кода (Fishman, 1996). 쐽 Уоттс Хамфри (Watts Humphrey) сообщает, что группы, применяющие мето- дику Team Software Process (TSP), достигают уровня 0,06 дефекта на 1000 строк кода. Главный аспект TSP — обучение разработчиков изначальному предот- вращению дефектов (Weber, 2003). Результаты проектов TSP и проектов «чистой комнаты» подтверждают другую версию Главного Закона Контроля Качества ПО: дешевле сразу создать высококачественную программу, чем создать низкокачественную программу и исправлять ее. Производительность труда в случае одного полнос- тью проверенного проекта из 80 000 строк, разработанного методом «чистой комнаты», оказалась равной 740 строкам кода на человеко-месяц. В то же время средняя для отрасли скорость разработки полностью проверенного кода, учиты- вающая все затраты, не связанные с кодированием, близка к 250–300 строкам на человеко-месяц (Cusumano et al., 2003). Экономия затрат и повышение произво- дительности труда при использовании методик TSP или «чистой комнаты» объяс- няются тем, что эти методики почти исключают затраты времени на отладку. Что? Исключают затраты времени на отладку? Это действительно стоящая цель! Ошибки самого тестирования Вероятно, вам знакома следующая ситуация. В программе имеется ошибка. У вас сразу же возникают некоторые предположения о ее причинах, но весь код кажется правильным. Пытаясь изолировать ошибку, вы выпол- няете еще несколько тестов, но все они дают правильные результаты. Вы прово- дите несколько часов за чтением кода и пересчетом результатов вручную, но без- результатно. Еще через несколько часов что-то заставляет вас проверить тесто- ГЛАВА 22 Тестирование, выполняемое разработчиками 513 вые данные. Эврика! Ошибка в тестовых данных! Как глупо тратить часы на по- иск ошибки, если она кроется в тестовых данных, а не в коде! Это довольно распространенная ситуация. Зачастую сами тесты содер- жат не меньше, а то и больше ошибок, чем тестируемый код (Weiland, 1983; Jones, 1986a; Johnson, 1994). Объяснить это легко, особенно если разра- ботчики сами пишут тесты. Тесты обычно создают на ходу, не уделяя должного внимания проектированию и конструированию. Их часто считают одноразовы- ми и разрабатывают с соответствующей тщательностью. Ниже дано несколько советов по снижению числа ошибок в тестах. Проверяйте свою работу Разрабатывайте тесты так же тщательно, как и код, и внимательно проверяйте их. Анализируйте код каждого теста строка за стро- кой при помощи отладчика, как вы проверяли бы код готового приложения. Про- водите анализ и инспекцию тестовых данных. Планируйте тестирование программы так же, как и ее разработку Планирование тестирования следует начинать на этапе выработки требований или сразу после получения задания. Это поможет избавиться от тестов, основанных на неверных предположениях. Храните тесты Посвятите немного времени проверке качества тестов. Сохра- ните их для регрессивного тестирования и для работы над будущими версиями программы. Связанные с этим проблемы легко оправдать, если вы знаете, что собираетесь сохранить тесты, а не выбросить. Встраивайте блочные тесты в среду тестирования Пишите сначала код блочных тестов, но затем интегрируйте их в системную среду тестирования (та- кую как JUnit). Использование интегрированной среды тестирования предотвра- щает только что упомянутую тенденцию выбрасывать тесты. 22.5. Инструменты тестирования В этом разделе рассматриваются типы инструментов тестирования, которые вы можете купить или создать. Конкретные инструменты названы здесь не будут, потому что ко времени издания книги они вполне могут устареть. Для получения свежих сведений обратитесь к своему любимому журналу по программированию. Создание лесов для тестирования отдельных классов Термин «леса» (scaffolding) пришел в программирование из строительства. Стро- ительные леса создаются для того, чтобы рабочие получили доступ к определен- ным частям здания. В программировании леса создаются исключительно для упрощения тестирования кода. Одним из типов лесов является объект, используемый дру- гим, тестируемым объектом. Такой объект называется «под- дельным объектом» (mock object) или «объектом-заглушкой» (stub object) (Mackinnon, Freemand, and Craig, 2000; Thomas and Hunt, 2002). С аналогичной целью можно создавать и низкоуровневые методы, называемые «заглушками». Поддель- ные объекты или методы-заглушки можно делать более или Дополнительные сведения Не- сколько хороших примеров ле- сов можно найти в эссе Джона Бентли «A Small Matter of Prog- ramming» в книге «Programming Pearls, 2d ed.» (Bentley, 2000). 514 ЧАСТЬ V Усовершенствование кода менее реалистичными в зависимости от требуемой степени достоверности. Такие леса могут: 쐽 немедленно возвращать управление, не выполнив никаких действий; 쐽 тестировать полученные данные; 쐽 выводить диагностическое сообщение (например, значения входных парамет- ров) на экран или записывать в файл; 쐽 запрашивать возвращаемые значения у программиста; 쐽 возвращать стандартный ответ независимо от полученных данных; 쐽 впустую тратить ресурсы процессора, выделенные реальному объекту или методу; 쐽 играть роль более медленной, объемной, простой или менее точной версии реального объекта или метода. Другим типом лесов является поддельный метод, вызывающий реальный тести- руемый метод. Такой метод, называемый «драйвером» или иногда «тестовой сбруей» (test harness), может: 쐽 вызывать объект, передавая ему некоторые данные; 쐽 запрашивать информацию у программиста и передавать ее объекту; 쐽 принимать параметры командной строки (если ОС это поддерживает) и пе- редавать их объекту; 쐽 читать аргументы из файла и передавать их объекту; 쐽 вызывать объект несколько раз, передавая ему при каждом вызове новый пред- определенный набор входных данных. Последний тип лесов, фиктивный файл (упрощенная вер- сия реального полноразмерного файла), включает такие же элементы. Небольшой фиктивный файл обладает двумя до- стоинствами. Его небольшой объем позволяет держать в уме все его содержимое и облегчает проверку правильности фай- ла. А так как файл создается специально для тестирования, вы можете спроектировать его так, чтобы любая ошибка в его использовании бро- салась в глаза. Очевидно, что создание лесов требует некоторой работы, но вы сможете повторно использовать их даже при обнаружении ошибки в классе. Кроме того, существуют многие инструмен- ты, упрощающие создание поддельных объектов и других видов лесов. При тести- ровании класса леса позволяют исключить его взаимодействие с другими классами. Леса особенно полезны, если в программе применяются утонченные алгоритмы. Так, если тестируемый код встроен в другой блок кода, тесты могут выполняться непри- емлемо медленно. Леса позволяют тестировать код непосредственно. Несколько минут, потраченных на создание лесов для тестирования кода, скрытого в самых глубинах программы, могут сэкономить много часов на отладке. Для создания лесов годится любая из многих доступных сред тестирования (JUnit, CppUnit, NUnit и т. д.). Если ваша среда разработки не поддерживается ни одной из существующих сред тестирования, вы можете написать несколько методов класса и включить в файл метод main() для их тестирования, пусть даже эти методы не Перекрестная ссылка Грань между инструментами тестиро- вания и инструментами отлад- ки размыта. Об инструментах отладки см. раздел 23.5. http://cc2e.com/2268 ГЛАВА 22 Тестирование, выполняемое разработчиками 515 будут в итоговой программе работать сами по себе. Метод main() может читать параметры командной строки и передавать их в тестируемый метод, позволяя проверить его до интеграции с остальной частью программы. Выполняя инте- грацию, оставьте методы и предназначенные для их тестирования леса в файле, и заблокируйте леса при помощи директив препроцессора или комментариев. В итоге код лесов будет проигнорирован при компиляции, а если вы разместите его в конце файла, он и на глаза не будет попадаться. Оставленный в файле, он не причинит никакого вреда. Вам не придется тратить время на его удаление и ар- хивирование, а в случае чего он всегда будет под рукой. Инструменты сравнения файлов Регрессивное (или повторное) тестирование значительно облегчают автоматизированные инструменты сравнения действительных выходных данных с ожидаемыми. Данные, выводимые на печать, легко проверить, перенаправив их в файл и сравнив при помощи diff или другой утилиты срав- нения файлов с другим файлом, в который ранее были записаны ожидаемые дан- ные. Если файлы различаются, радуйтесь: вы обнаружили регрессивную ошибку. Генераторы тестовых данных Вы также можете написать код для систематичного тести- рования выбранных фрагментов программы. Несколько лет назад я разработал собственный алгоритм шифрования и написал на его основе программу шифрования файлов. Программа должна была так кодировать файл, чтобы его можно было декодировать, только введя правиль- ный пароль. При шифровании содержимое файла изменялось самым радикаль- ным образом. Было очень важно, чтобы программа декодировала файл правиль- но, потому что в противном случае он был бы испорчен. Я настроил генератор тестовых данных, который полностью тестировал шифро- вальную и дешифровальную части программы. Он генерировал файлы, состоящие из случайных символов и имеющие случайный объем в пределах от 0 до 500 кб. Он генерировал случайные пароли случайной длины в диапазоне от 1 до 255 симво- лов. Для каждого случая он генерировал две копии случайного файла, шифровал одну копию, заново инициализировался, дешифровал копию и затем сравнивал дешифрованную копию с первоначальной. Если какие-нибудь байты различались, генератор печатал всю информацию, нужную мне для воспроизведения ошибки. Я настроил генератор так, чтобы средний объем тестируемых файлов равнялся 30 кб. Если бы я этого не сделал, файлы были бы равномерно распределены меж- ду 0 кб и 500 кб и имели средний объем 250 кб. Сокращение среднего объема позволило мне быстрее тестировать файлы, пароли, признаки конца файла, не- обычные объемы файлов и прочие возможные причины ошибок. Результаты не заставили себя ждать. Выполнив лишь около 100 тестов, я нашел две ошибки. Обе возникали в специфических ситуациях, которые могли ни разу не случиться на практике, однако это все же были ошибки, и я был рад их обна- ружить. После их исправления я тестировал программу несколько недель, зашиф- Перекрестная ссылка О регрес- сивном тестировании см. подраз- дел «Повторное (регрессивное) тестирование» раздела 22.6. http://cc2e.com/2275 516 ЧАСТЬ V Усовершенствование кода ровав и дешифровав около 100 000 файлов без каких бы то ни было ошибок. Учитывая то, что я протестировал файлы и пароли самого разного объема и со- держания, я мог быть уверен, что с программой все в порядке. Из этой истории можно извлечь ряд уроков, в том числе следующие. 쐽 Правильно спроектированные генераторы случайных данных способны гене- рировать комбинации тестовых данных, о которых вы могли бы не подумать. 쐽 Генераторы случайных данных могут протестировать программу тщательнее, чем вы сами. 쐽 Со временем вы можете улучшить случайные тесты, обратив повышенное вни- мание на реалистичный диапазон входных значений. Это позволяет сконцен- трировать тестирование на тех областях, которые будут использоваться чаще всего, и максимально повысить их надежность. 쐽 Модульное проектирование сполна окупается во время тестирования. Я смог отделить код шифрования и дешифрования и задействовать его независимо от кода пользовательского интерфейса, что упростило задачу написания тес- тового драйвера. 쐽 Вы можете повторно использовать тестовый драйвер даже после изменения тестируемого им кода. Как только я исправил две ошибки, я смог сразу же на- чать повторное тестирование. Мониторы покрытия кода тестами Карл Вигерс сообщает, что тестирование, выполняемое без измерения покрытия кода тестами, обычно охватывает только 50–60% кода (Wiegers, 2002). Монитор покрытия — это инструмент, который следит за тем, какой код тестировался, а какой нет. Монитор покрытия особенно полезен для систе- матичного тестирования, потому что он говорит вам, полностью ли код покры- вается конкретным набором тестов. Если вы выполнили полный набор тестов, а монитор покрытия сообщает, что какой-то код все еще не протестирован, значит, нужны дополнительные тесты. Регистраторы данных Некоторые инструменты могут следить за программой и собирать информацию о ее состоянии в случае краха по- добно «черному ящику», устанавливаемому в самолетах для определения причин крушения. Эффективная регистрация данных облегчает ди- агностику ошибок и сопровождение программы после ее выпуска. Вы можете создать собственный регистратор данных, записывающий сведения о важных событиях в файл. Записывайте в файл состояние системы до ошибки и подробные сведения об условиях возникновения ошибки. Можете использовать эту функциональность в предварительных версиях программ и блокировать в окончательных версиях. Если вы используете для хранения зарегистрированных данных самоочищающееся хранилище и тщательно продумали размещение и содержание сообщений об ошибках, можете оставлять функции регистрации дан- ных и в итоговых версиях программ. http://cc2e.com/2282 ГЛАВА 22 Тестирование, выполняемое разработчиками 517 Символические отладчики Символический отладчик — это технологическое дополне- ние анализа и инспекций кода. Отладчик может выполнять код строка за строкой, позволяет следить за значениями переменных и всегда интерпретирует код так же, как ком- пьютер. Возможность пошагового выполнения кода и наблю- дения за его работой трудно переоценить. Анализ кода при помощи отладчика во многом напоминает процесс обзора ва- шего кода другими программистами. Ни ваши коллеги, ни отладчик не имеют тех же слабых сторон, которые имеете вы. Дополнительное преимущество отладчика в том, что анализ кода с его помощью менее трудоемок, чем групповой обзор. Наблюдение за выполнением кода при разных комбинациях входных данных позволяет убедиться в том, что вы написали то, что хотели. Кроме того, использование хорошего отладчика является эффективным способом изучения языка, поскольку вы можете получить точную информацию о выполне- нии кода. Переключаясь между высокоуровневым языком и ассемблером, вы мо- жете изучить соответствие высокоуровневых и низкоуровневых конструкций. Наблюдение за регистрами и стеком позволяет лучше разобраться в передаче аргументов в методы. Вы можете увидеть, как компилятор оптимизирует код. Ни- какое из этих преимуществ не имеет прямого отношения к главной роли отлад- чика — диагностике уже обнаруженных ошибок, но при творческом подходе из отладчика можно извлечь гораздо большую выгоду. Инструменты возмущения состояния системы Другой класс инструментов тестирования предназначен для приведения системы в возмущенное состояние. Многие программисты могут рассказать истории о программах, ко- торые работают 99 раз из 100, но при сотом запуске с теми же данными терпят крах. Почти всегда причиной этого является невыполнение инициализации ка- кой-то переменной, и обычно эту ошибку очень трудно воспроизвести, потому что в 99 случаях из 100 неинициализированная переменная получает нулевое значение. Инструменты тестирования из этого класса могут иметь самые разнообразные возможности. 쐽 Заполнение памяти Эта функция помогает находить неинициализирован- ные переменные. Некоторые инструменты перед запуском программы запол- няют память произвольными значениями, чтобы неинициализированные пе- ременные не получили случайно значение 0. Иногда память целесообразно за- полнять конкретным значением. Например, в случае процессоров с архитек- турой x86 значение 0xCC соответствует машинному коду команды прерыва- ния. Если вы заполните память значением 0xCC и программа попробует вы- полнить что-то, чего выполнять не следует, вы натолкнетесь в отладчике на точку прерывания и обнаружите ошибку. 쐽 «Встряхивание» памяти В многозадачных средах некоторые инструменты могут переупорядочивать выделенную программе память, что позволяет гаран- Перекрестная ссылка Наличие отладчиков зависит от зрелос- ти технологической среды (см. раздел 4.3). http://cc2e.com/2289 518 ЧАСТЬ V Усовершенствование кода тировать отсутствие кода, требующего, чтобы данные находились по абсолют- ным, а не относительным адресам. 쐽 Селективный сбой памяти Драйвер памяти позволяет имитировать недо- статок или неудачный запрос памяти, может отказывать в запросе памяти после произвольного числа успешных запросов или предоставлять запрошенную память после произвольного числа неудовлетворенных запросов. Это особен- но полезно при тестирования сложных программ, выделяющих память дина- мически. 쐽 Проверка доступа к памяти (проверка границ) Инструменты проверки границ следят за операциями над указателями, гарантируя их корректность. Такие инструменты полезны при поиске неинициализированных или «вися- чих» указателей. Базы данных для хранения информации об ошибках БД, содержащая информацию об обнаруженных ошибках, — тоже мощный инструмент тестирования. Такую базу мож- но рассматривать и как инструмент управления, и как тех- нический инструмент. Она позволяет узнавать о появлении старых ошибок, сле- дить за частотой обнаружения и исправления новых ошибок, а также за статусом и тяжестью исправленных и неисправленных ошибок. О том, какую информацию об ошибках следует хранить в БД, вы узнаете в разделе 22.7. 22.6. Оптимизация процесса тестирования Способы улучшения тестирования аналогичны способам улучшения любого дру- гого процесса. Опираясь на точные знания о процессе, вы немного изменяете его и смотрите, к каким результатам это приводит. Если результат изменения поло- жителен, значит, процесс стал несколько лучше. В следующих подразделах опти- мизация процессов рассматривается в контексте тестирования. Планирование тестирования Эффективность тестирования заметно повышается, если его спланировать в самом начале работы над проектом. При- своение тестированию той же степени важности, что и про- ектированию с кодированием, подразумевает, что на тес- тирование будет выделено время, что оно будет считаться не менее важным и что процесс тестирования будет высо- кокачественным. Планирование тестирования нужно и для обеспечения повторяемости процесса тестирования. Если процесс нельзя повторить, его нельзя улучшить. Повторное (регрессивное) тестирование Допустим, вы тщательно протестировали систему и не обнаружили ошибок. Пред- положим далее, что вы изменяете какой-то фрагмент системы и хотите убедить- ся в том, что она по-прежнему успешно проходит все тесты, т. е. что изменение Перекрестная ссылка Одним из элементов планирования тести- рования является формализа- ция планов в письменной фор- ме. О документировании тести- рования см. работы, указанных в разделе «Дополнительные ресурсы» главы 32. http://cc2e.com/2296 |