Руководство по стилю программирования и конструированию по
Скачать 7.6 Mb.
|
ГЛАВА 22 Тестирование, выполняемое разработчиками 503 x = a y = x + 1 и x = b y = x - 1 Но у нас есть еще две комбинации «определение — использование»: (1) x = a, после чего y = x - 1 и (2) x = b, после чего y = x + 1. В данном примере эти комбинации можно покрыть, добавив еще два теста: (Condition 1=True, Condition 2=False) и (Condition 1=False, Condition 2=True). Можно порекомендовать следующую стратегию разработки тестов: начните со структурированного базисного тестирования, которое охватит некоторые, если не все потоки данных «определение — использование». После этого добавьте те- сты, нужные для полного охвата комбинаций «определение — использование». Структурированное базисное тестирование указало нам на шесть тестов метода, для которого мы подсчитывали число тестов. Тестирование каждой пары «опре- деление — использование» требует нескольких дополнительных тестов. Вот пары, которые не охватываются уже созданными тестами: Номер теста Описание теста 7 Определение переменной companyRetirement в строке 12 и использо- вание в строке 26. Этот случай может не покрываться имеющимися тестами. 8 Определение переменной companyRetirement в строке 12 и использо- вание в строке 31. Этот случай может не покрываться имеющимися тестами. 9 Определение переменной companyRetirement в строке 17 и использо- вание в строке 31. Этот случай может не покрываться имеющимися тестами. Выполнив тестирование, основанное на потоках данных, несколько раз, вы нач- нете чувствовать, какие тесты нужны, а какие уже реализованы. В случае сомне- ний ищите все комбинации «определение — использование». Это может показаться слишком объемной работой, но так вы точно найдете все случаи, не охваченные базисным тестированием. Разделение на классы эквивалентности Хороший тест должен покрывать широкий диапазон вход- ных данных Если два теста приводят к обнаружению одних и тех же ошибок, вам нужен лишь один из них. Понятие «разделения на классы эквивалентности» формализует эту идею и помогает уменьшить число нужных тестов. Подходящее место для разделения на классы эквивалентно- сти — строка 7 уже известного нам листинга, в которой проверяется условие m_employee[ ID ].governmentRetirementWithheld < MAX_GOVT_RETIREMENT. Это условие Перекрестная ссылка Разделе- ние на классы эквивалентнос- ти гораздо подробнее обсужда- ется в книгах, указанных в раз- деле «Дополнительные ресур- сы» в конце этой главы. 504 ЧАСТЬ V Усовершенствование кода делит все значения m_employee[ ID ]. governmentRetirement Withheld на два класса эквивалентности: значения, которые меньше константы MAX_GOVT_RETIREMENT, и значения, которые больше или равны ей. В других частях программы могут использоваться другие классы эквивалентности, что может потребовать тестиро- вания более двух значений m_employee[ ID ].governmentRetirementWithheld, но в этой части программы нужно проверить только два значения. Размышление о разделении на классы эквивалентности не скажет вам много но- вого о программе, если вы уже покрыли ее базисным тестированием и тестиро- ванием, основанным на потоках данных. Однако оно очень полезно, если вы смот- рите на программу «извне» (с точки зрения спецификации, а не исходного кода) или если данные сложны, а эта сложность плохо отражена в логике программы. Угадывание ошибок Формальные методики тестирования хорошие программи- сты дополняют менее формальными эвристическими мето- диками. Одна из них — угадывание ошибок (error guessing). Термин «угадывание ошибок» — довольно примитивное название вполне разум- ной идеи, подразумевающей создание тестов на основе обдуманных предположе- ний о вероятных источниках ошибок. Предположения можно выдвигать, опираясь на интуицию или накопленный опыт. Так, в главе 21 в числе прочих достоинств инспекций были указаны создание и обновление списка частых ошибок, используемого при проверке нового кода. Поддерживая списки ранее допущенных ошибок, вы повысите эффективность своих догадок. Ниже рассматриваются конкретные виды ошибок, угадать которые легче всего. Анализ граничных условий Одной из самых плодотворных областей тестирования являются граничные ус- ловия — ошибки занижения или завышения на 1. Действительно, разработчики очень часто используют num – 1 вместо num или >= вместо >. Идея анализа граничных условий состоит в написании тестов, позволяющих про- верить эти условия. Так, при тестировании диапазона значений, которые меньше max, возможны три условия: Как видите, в этой ситуации мы имеем три граничных случая: максимальное зна- чение, которое меньше max, само значение max и минимальное значение, пре- вышающее max. Для исключения распространенных ошибок нужны три теста. Фрагмент кода, для которого мы подсчитывали число тестов, содержит проверку m_employee[ ID ].governmentRetirementWithheld < MAX_GOVT_RETIREMENT. Согласно принципам анализа граничных условий следует изучить три случая: Перекрестная ссылка Об эвристи- ческих методиках см. раздел 2.2. ГЛАВА 22 Тестирование, выполняемое разработчиками 505 Номер теста Описание теста 1 Тест требует, чтобы истинное значение выражения m_employee[ ID ]. governmentRetirementWithheld < MAX_GOVT_RETIREMENT было первым с истинной стороны границы. Иначе говоря, мы должны присвоить элементу m_employee[ ID ].governmentRetirementWithheld значение MAX_GOVT_RETIREMENT - 1. Для этого годится уже имеющийся тест 1. 3 Тест требует, чтобы ложное значение выражения m_employee[ ID ]. go vernmentRetirementWithheld < MAX_GOVT_RETIREMENT было первым с ложной стороны границы. Таким образом, элементу m_employee[ ID ].governmentRetirementWithheld нужно присвоить значение MAX_GOVT_ RETIREMENT + 1. Для этого вполне подойдет тест 3. 10 Дополнительный тест нужен для самой границы, когда m_employee [ ID ].governmentRetirementWithheld = MAX_GOVT_RETIREMENT. Сложные граничные условия Анализ граничных условий можно проводить также в отношении минимального и максимального допустимых значений. В нашем примере ими могли бы быть минимальные или максимальные значения переменных grossPay, companyRetirement или personalRetirement, но из-за того, что эти значения вычисляются вне области видимости метода, их тестирование мы обсуждать не будем. Более тонкий вид граничного условия имеет место, когда оно зависит от комби- нации переменных. Например, что произойдет при умножении двух переменных, если обе являются большими положительными числами? Большими отрицатель- ными числами? Если хотя бы одна из переменных равна 0? Что, если все пере- данные в метод строки имеют необычно большую длину? В текущем примере вы могли бы проверить, что происходит с денежными сум- мами, которые представлены переменными totalWithholdings, totalGovernment- Retirement и totalRetirement, если каждый член большой группы имеет крупную зарплату — скажем, каждый из программистов зарабатывает по 250 000 долларов (надежда умирает последней!). Для этого нужен еще один тест: Номер теста Описание теста 11 Большая группа высокооплачиваемых сотрудников (конкретные пока- затели зависят от конкретной системы — скажем, 1000 сотрудников, каждый из которых зарабатывает по 250 000 долларов в год), не вы- плачивающих взносы в фонд социального страхования, но отчисляю- щих средства в пенсионный фонд компании. Противоположным тестом из этой же категории было бы вычисление всех пока- зателей для небольшой группы сотрудников, не получающих зарплату: Номер теста Описание теста 12 Группа из 10 сотрудников, не получающих зарплату. Классы плохих данных Кроме граничных условий, программу можно тестировать на предмет несколь- ких других классов плохих данных. Типичными примерами плохих данных мож- но считать: 506 ЧАСТЬ V Усовершенствование кода 쐽 недостаток данных (или их отсутствие); 쐽 избыток данных; 쐽 неверный вид данных (некорректные данные); 쐽 неверный размер данных; 쐽 неинициализированные данные. Некоторые из этих случаев уже покрыты имеющимися тестами. Так, «недостаток данных» охватывается тестами 2 и 12, а для «неверного размера данных» тесты придумать трудно. И все же рассмотрение классов плохих данных позволяет со- здать еще несколько тестов: Номер теста Описание теста 13 Массив из 100 000 000 сотрудников. Тестирование на предмет избытка данных. Конечно, объем данных, который следует считать избыточ- ным, зависит от конкретной системы. 14 Отрицательная зарплата. Неверный вид данных. 15 Отрицательное число сотрудников. Неверный вид данных. Классы хороших данных При поиске ошибок легко упустить из виду тот факт, что номинальный случай так- же может содержать ошибку. Примерами хороших данных могут служить номиналь- ные случаи, описанные в разделе, посвященном базисному тестированию. Ниже ука- заны другие виды хороших данных, которые стоит подвергать проверке; проверка каждого из этих видов данных также может приводить к обнаружению ошибок: 쐽 номинальные случаи: средние, ожидаемые значения; 쐽 минимальная нормальная конфигурация; 쐽 максимальная нормальная конфигурация; 쐽 совместимость со старыми данными. Минимальную нормальную конфигурацию полезно применять для тестирования не только одного элемента, но и группы элементов. Минимальная нормальная конфигурация аналогична граничному условию, составленному из нескольких минимальных значений, но отличается от него тем, что она включает набор ми- нимальных значений из диапазона нормально ожидаемых значений. В качестве примера можно привести сохранение пустой электронной таблицы. При тести- ровании текстового редактора примером могло бы быть сохранение пустого до- кумента. В нашем примере тестирование минимальной нормальной конфигура- ции добавило бы в набор следующий тест: Номер теста Описание теста 16 Группа из 1 сотрудника. Тестирование минимальной нормальной конфигурации. Максимальная нормальная конфигурация противоположна минимальной. Она также аналогична граничному условию, но опять-таки включает набор максималь- ных значений из диапазона ожидаемых значений. Пример — сохранение или ГЛАВА 22 Тестирование, выполняемое разработчиками 507 печать электронной таблицы, имеющей «максимальный размер», заявленный в рекламных материалах. В случае текстового процессора — сохранение докумен- та максимального рекомендованного размера. У нас максимальная нормальная конфигурация определяется максимальным нормальным числом сотрудников. Если бы оно равнялось 500, вы добавили бы в набор такой тест: Номер теста Описание теста 17 Группа из 500 сотрудников. Тестирование максимальной нормальной конфигурации. Последний вид тестирования нормальных данных — тестирование совместимо- сти со старыми данными — вступает в игру при замене старого приложения или метода на новый вариант. При вводе старых данных новый метод должен выда- вать те же результаты, что и старый метод, за исключением тех ситуаций, в кото- рых старый метод работал ошибочно. Этот вид преемственности версий лежит в основе регрессивного тестирования, призванного гарантировать, что исправле- ния и улучшения поддерживают достигнутый уровень качества, не вызывая про- блем. В нашем примере критерий совместимости не добавил бы никаких тестов. Используйте тесты, позволяющие легко проверить результаты вручную Допустим, вы пишете тест для проверки расчета зарплаты; вам нужно ввести зарпла- ту, и один из способов сделать это — ввести числа, которые попадаются под руку. Попробуем: 1239078382346 Отлично. Это довольно большая зарплата — более триллиона долларов, но если ее «обрезать», можно получить что-то более реалистичное: скажем, 90 783,82 доллара. Теперь предположим, что этот тест успешен, т. е. указывает на ошибку. Как узнать, что вы обнаружили ошибку? Ну, вы можете вычислить правильный результат вруч- ную и сравнить его с результатом, полученным на самом деле. Однако если в вы- числениях фигурируют такие неприятные числа, как 90 783,82 доллара, вероят- ность допустить ошибку в вычислениях не менее высока, чем вероятность обна- ружения ошибки в программе. С другой стороны, удобные круглые числа вроде 20 000 долларов делают вычисления сущим пустяком. Нули легко набирать, а ум- ножение на 2 большинство программистов способны выполнять в уме. Возможно, вы считаете, что неудобные числа чаще приводят к обнаружению ошибок, но это не так: при использовании любого числа из одного и того же класса эквивалентности вероятность нахождения ошибки одинакова. 22.4. Типичные ошибки Главная идея этого раздела в том, что для достижения максимальной эффективно- сти тестирования мы должны как можно больше знать о нашем враге — ошибках. 508 ЧАСТЬ V Усовершенствование кода Какие классы содержат наибольшее число ошибок? Естественно предположить, что дефекты распределяются по коду равно- мерно. Если код содержит в среднем 10 дефектов на 1000 строк, вы мог- ли бы ожидать, что класс из 100 строк будет иметь один дефект. Это ес- тественное предположение, но оно ошибочно. Кейперс Джонс сообщает, что в результате принятой в IBM программы повыше- ния качества 31 из 425 классов системы IMS получил статус «подверженный ошиб- кам». Эти классы были исправлены или полностью разработаны заново, благода- ря чему менее чем через год частота обнаружения дефектов в IMS клиентами сни- зилась в 10 раз. Общие расходы на сопровождение системы снизились пример- но на 45%. Мнения клиентов о качестве системы изменились с «неприемлемо» на «хорошо» (Jones, 2000). Большинство ошибок обычно концентрируется в нескольких особенно дефект- ных методах. Типичные отношения между ошибками и кодом таковы: 쐽 80% ошибок содержится в 20% классов или методов проекта (Endres, 1975; Gremillion, 1984; Boehm, 1987b; Shull et al., 2002); 쐽 50% ошибок содержится в 5% классов проекта (Jones, 2000). Эти отношения могут казаться не такими уж и важными, пока вы не узнаете не- сколько следствий. Во-первых, 20% методов проекта обусловливают 80% затрат на разработку (Boehm, 1987b). Это не значит, что 20% самых дорогих методов явля- ются одновременно и самыми дефектными, но такое совпадение настораживает. Во-вторых, какой бы ни была точная доля расходов, приходящихся на разработку дефектных методов, эти методы очень дороги. В классичес- ком исследовании, проведенном в 1960-х, специалисты IBM проанали- зировали операционную систему OS/360 и обнаружили, что ошибки не были рас- пределены равномерно между всеми методами, а были сконцентрированы в не- скольких методах. Был сделан вывод, что эти методы — «самые дорогие сущнос- ти в программировании» (Jones, 1986a). Они содержали целых 50 дефектов на 1000 строк кода, а их исправление часто оказывалось в 10 раз дороже разработки всей системы (затраты включали поддержку пользователей и сопровождение системы на месте). В-третьих, дорогие методы оказывают очевидное влияние на процесс разработки. Как гласит старая пословица, «вре- мя — деньги». Справедливо и обратное: «деньги — время», и, если вы можете исключить почти 80% затрат, избежав проблемных методов, вы можете сэкономить и много вре- мени. Это наглядно иллюстрирует Главный Закон Контро- ля Качества ПО: повышение качества сокращает сроки и снижает общую стоимость разработки системы. В-четвертых, проблемные методы оказывают не менее очевидное влияние на со- провождение программы. При сопровождении программистам приходится сосре- доточиваться на идентификации методов, подверженных ошибкам, их перепро- ектировании и переписывании с нуля. В вышеупомянутом проекте IMS после за- мены дефектных классов производительность труда при разработке новых вер- сий IMS повысилась примерно на 15% (Jones, 2000). Перекрестная ссылка Другим типом методов, часто содержа- щих много ошибок, являются слишком сложные методы. Об идентификации таких методов и их упрощении см. подраздел «Общие принципы уменьшения сложности» раздела 19.6. ГЛАВА 22 Тестирование, выполняемое разработчиками 509 Классификация ошибок Некоторые ученые попытались классифицировать ошибки по типу и определить распространенность ошибок каждо- го типа. У каждого программиста есть свой список наибо- лее неприятных ошибок: ошибки занижения или завыше- ния на 1, невыполнение повторной инициализации переменной цикла и т. д. В контрольных списках я указал и многие другие типы ошибок. Борис Бейзер объединил данные нескольких исследований и пришел к исключи- тельно подробной классификации ошибок по распространенности (Beizer, 1990). Вот резюме его результатов: 25,18% Структурные ошибки 22,44% Ошибки в данных 16,19% Ошибки в реализации функциональности 9,88% Ошибки конструирования 8,98% Ошибки интеграции 8,12% Ошибки в функциональных требованиях 2,76% Ошибки в определении или выполнении тестов 1,74% Системные ошибки, ошибки в архитектуре ПО 4,71% Другие ошибки Бейзер сообщил свои результаты с точностью до двух разрядов после запятой, но исследования типов ошибок в целом не позволяют сделать окончательный вывод. Разные ученые сообщают о разных ошибках, а результаты исследований похожих типов ошибок иногда различаются на 50%, а не на сотые доли процента. Из-за таких больших различий выполненное Бейзером объединение результатов ряда исследований, вероятно, не способно дать нам точной информации. Но даже если имеющиеся данные неокончательны, на их основе мы можем сделать несколь- ко общих выводов. Большинство ошибок имеет довольно ограниченную область ви- димости Одно исследование показало, что в 85% случаев исправление ошибки требовало изменения только одного метода (Endres, 1975). Многие ошибки не связаны с конструированием Опрос 97 разработчиков позволил выяснить, что тремя наиболее частыми причинами ошибок были пло- хое знание прикладной области, изменения или конфликты требований, а также неэффективность общения и плохая координация действий разработчиков (Curtis, Krasner, and Iscoe, 1988). Как правило, ошибки конструирования лежат на со- вести программистов В двух давнишних исследовани- ях было установлено, что из всех ошибок 95% были допу- щены программистами, причиной 2% было системное ПО (компилятор и ОС), еще 2% — другое ПО, а оставшегося 1% — оборудование (Brown and Sampson, 1973; Ostrand and Weyuker, 1984). Системное ПО и инструменты разработки ис- пользуются сегодня гораздо шире, чем в 1970-х и 1980-х, Перекрестная ссылка Список всех контрольных списков при- веден после содержания книги. Если вы видите следы копыт, думайте о лошадях, а не о зеб- рах. Скорее всего ОС работает нормально. С базой данных тоже, наверное, все в порядке. Энди Хант и Дэйв Томас (Andy Hunt and Dave Thomas) 510 ЧАСТЬ V Усовершенствование кода поэтому я думаю, что сегодня программисты несут ответственность за еще боль- ший процент ошибок. На удивление распространенной причиной проблем являются опечатки В одном исследовании было обнаружено, что 36% всех оши- бок конструирования были опечатками (Weiss, 1975). Исследование по- чти 3 000 000 строк приложения для расчета динамики полета, проведенное в 1987 г., показало, что опечатками были 18% всех ошибок (Card, 1987). В другом исследо- вании было установлено, что 4% всех ошибок были орфографическими ошибка- ми в сообщениях (Endres, 1975). В одной из моих программ коллега обнаружил ряд орфографических ошибок, просто проанализировав все строки исполняемо- го файла утилитой проверки орфографии. Внимание к деталям на самом деле важно. Если вы сомневаетесь в этом, учтите, что три самых дорогостоящих ошибки всех времен, приведших к убыткам объемом 1,6 миллиарда, 900 миллионов и 245 миллионов долларов, были вызваны изменением одного символа в ранее коррек- тных программах (Weinberg, 1983). Довольно часто причиной ошибок является неправильное понимание проекта Компилятивное исследование Бейзера показало, что 16% ошибок было обусловлено неправильной интерпретацией проекта (Beizer, 1990). В другом ис- следовании было обнаружено, что неправильным пониманием проекта объясня- лось 19% ошибок (Weiss, 1975). Посвящайте анализу проекта столько времени, сколько нужно. Это не обеспечивает немедленную выгоду (кому-то даже может показаться, что вы не работаете!), но в итоге окупается с избытком. Большинство ошибок легко исправить Примерно в 85% случаев на исправ- ление ошибки требуется менее нескольких часов. Где-то в 15% случаев — от не- скольких часов до нескольких дней. И только около 1% ошибок требует больше- го времени (Weiss, 1975; Ostrand and Weyuker, 1984; Grady, 1992). Это подтвержда- ет и Барри Бом, заметивший, что на исправление около 20% ошибок уходит око- ло 80% ресурсов (Boehm, 1987b). Изо всех сил старайтесь избегать трудных оши- бок, выполняя предварительные обзоры требований и проектов. Исправляйте многочисленные мелкие ошибки так эффективно, как только можете. Оценивайте опыт борьбы с ошибками в своей организации Разнообразие результатов, приведенных в этом подразделе, говорит о том, что каждая органи- зация имеет собственный, уникальный опыт борьбы с ошибками. Это осложняет использование опыта других организаций в ваших условиях. Некоторые резуль- таты противоречат интуиции; возможно, вам следует дополнить свои интуитив- ные представления другими средствами. Начните оценивать процесс разработки в своей организации, чтобы знать причины проблем. Доля ошибок, обусловленных неграмотным конструированием Как и классификация ошибок, данные, соотносящие ошибки с разными этапами разработки, не окончательны. Но то, что с конструированием всегда связано боль- шое число ошибок, сомнений не вызывает. Иногда программисты утверждают, что ошибки конструирования дешевле исправлять, чем ошибки, допущенные при вы- |