Тестирование-книга. Ю. Н. Артеменко Научный редактор
Скачать 6.27 Mb.
|
Глава 3: Типы тестов ... 73 Для выполнения всех трех строк кода достаточно проверить вариант (а). При более основательном способе тестирования — по критерию охва та ветвлений — программист проверяет вариант (а) и еще один из трех остальных вариантов. Смысл этого способа в том, что проверяются дей ствия программы при выполненном условии оператора IF и при невыпол ненном. Таким образом программа проходит не только все строки кода, но и все возможные ветви. Иногда охват ветвлений называют полным охватом кода. Но термин этот некорректен: как показал Бейзер (Beizer, 1984), охват ветвлений не может претендовать на полноту тестирования, поскольку в лучшем случае позво ляет обнаружить половину имеющихся в программе ошибок. Еще более строгим является критерий охвата условий. По этому крите рию следует проверить все составляющие каждого логического условия. В нашем примере это означает проверку всех четырех перечисленных вари антов. Тестирование путей программы считается завершенным, когда выбран ный критерий охвата полностью выполнен. Для автоматизации этого про цесса разработаны программы, анализирующие программный код и вычисляющие количество подлежащих тестированию путей, а затем под считывающие, сколько их уже проверено. Такие программы называют сред ствами мониторинга охвата (execution coverage monitors). Классически при тестировании путей не поощряется проверка одного и того же пути на различных данных. Хотя варианты (б), (в) и (г) нашего примера могут оказаться важными, большинство средств мониторинга ох- вата посчитают их проверку пустой тратой времени. Все три начинаются с одного и того же оператора и приводят к выполнению одной и той же последовательности команд — проверять их все означает трижды проверять один и тот же путь. Хотя критерии охвата очень полезны, одного только тестирования пу- тей недостаточно для эффективного выявления ошибок. Гуденаф и Герхарт 74 Часть I: Основы (Goodenough & Gerhart, 1975) привели классический пример того, что проход строки кода еще не означает выявления имеющейся в ней ошибки. Рассмотрим такую строку программы. SET A = В/С Она вполне успешно выполняется, если С не равно нулю. Но если С равно нулю, программа или сообщит об ошибке и прекратит работу, или "зависнет". А разница между этими двумя вариантами не в пути, а в дан ных. Де Милло (DeMillo, 1987) называет программный путь чувствительным к ошибкам, если при его прохождении ошибки могут проявиться. Если же ошибки обязательно проявятся при прохождении данного пути, то такой путь называется обнаруживающим ошибки. Любой путь, проходящий через приведенную строку программы чувствителен к ошибкам. Ошибка прояв ляется только при нулевом значении переменной С. Для выявления подоб ных ошибок технология тестирования "черного ящика" подходит лучше, чем анализ программного кода. Формальное описание технологии тестирования программных путей можно найти у Реппса и Вейакера (Rapps & Weyuker, 1985), более подроб ное исследование проблемы — у Бейзера (Beizer, 1984,1990), а особенно, детальное обсуждение критериев охвата — у Майерса (Myers, 1979). | | Тестирование частей против тестирования целого Любая система разрабатывается по частям — как набор процессов или модулей. Можно ее так и тестировать — сначала отдельные части, а потом уже их взаимодействие. Такая стратегия называется восходящей (bottom-up). Выяснив, что отдельные элементы программы в порядке, специалист приступает к тестированию их совместной работы. И тут может оказаться, что вместе они работать отказываются. Например, если программист слу чайно поменяет местами параметры вызываемой функции, при выполне нии вызова произойдет ошибка. И выявлена она будет только при проверке совместной работы обеих функций — вызывающей и вызываемой. Тестирование совместной работы программных модулей называют ин теграционным. В ходе такого тестирования модули сначала объединяются в пары, потом в большие блоки, пока наконец все модули не будут объеди нены в единую систему. Восходящее тестирование — это прекрасный способ локализации оши бок. Если ошибка обнаружена при тестировании единственного модуля, то очевидно, что она содержится именно в нем — для поиска ее источника не нужно анализировать код всей системы. А если ошибка проявляется при совместной работе двух предварительно протестированных модулей, значит, дело в их интерфейсе. Еще одним преимуществом восходящего тестирова- Глава 3: Типы тестов ... 75 ния является то, что выполняющий его программист концентрируется на очень узкой области (единственном модуле, передаче данных между парой модулей и т.п.). Благодаря этому тестирование проводится более тщатель но и с большей вероятностью выявляет ошибки. Главным недостатком восходящего тестирования является необходи мость написания специального кода-оболочки, вызывающего тестируемый модуль. Если он, в свою очередь, вызывает другой модуль, для него нуж но написать заглушку. Заглушка — это имитация вызываемой функции, возвращающая те же данные, но ничего больше не делающая. Понятно, что написание оболочек и заглушек замедляет работу, а для конечного продукта они абсолютно бесполезны. Но написанные однажды, эти элементы могут использоваться повторно при каждом изменении про граммы. Хороший набор оболочек и заглушек — это очень эффективный инструмент тестирования. В противоположность восходящему тестированию, стратегия целостного тестирования предполагает, что до полной интеграции системы ее отдель ные модули не проходят особо тщательного тестирования. Преимуществом такой стратегии является то, что нет необходимости в написании дополнительного кода. Поэтому многие руководители выбира ют этот способ из соображений экономии времени — они считают, что лучше разработать один обширный набор тестов и с его помощью за один раз проверить всю систему. Но такое представление совершенно ошибоч но, и вот почему. • Очень трудно выявить источник ошибки. Это главная проблема. Поскольку ни один из модулей не проверен как следует, в большин стве из них есть ошибки. Получается, что вопрос не столько в том, в каком модуле произошла обнаруженная ошибка, сколько в том, какая из ошибок во всех вовлеченных в процесс модулях привела к полученному результату. И когда накладываются ошибки несколь ких модулей, ситуацию может быть гораздо труднее локализовать и повторить. Кроме того, ошибка в одном из модулей может блокировать тести рование другого. Как протестировать функцию, если вызывающий ее модуль не работает? Если не написать для этой функции програм му-оболочку, придется ждать отладки модуля, а это может затянуть ся надолго. • Трудно организовать исправление ошибок. Если программу пишут несколько программистов (а именно так и бывает в больших систе мах), и при этом неизвестно, в каком модуле ошибка, кто же будет ее искать и исправлять? Один программист будет указывать на дру гого, тот, выяснив, что его код ни при чем, снова обратится к пер вому, а в результате будет сильно страдать скорость разработки. 76 Часть I: Основы • Процесс тестирования плохо автоматизирован. То, что на первый взгляд кажется преимуществом целостного тестирования — отсут-. ствие необходимости писать оболочки и заглушки, — на самом деле оборачивается его недостатком. В процессе разработки программа ежедневно меняется, и ее приходится тестировать снова и снова. А оболочки и заглушки помогают автоматизировать этот однообразный труд. Поскольку большинство руководителей проектов отнюдь не глупы, когда кто-нибудь из них выбирает целостное тестирование, мы предпола гаем, что в своем конкретном случае он видит такие преимущества этого подхода, о которых в разговоре с нами просто не упоминает. Есть и такие руководители, которые вообще не слишком заботятся об эффективности тестирования. Главное для них — как можно скорее отрапортовать началь ству о завершении работ, даже если на самом деле ничего не работает. Если после этого с проектом возникнут проблемы, они будут обвинять законы Мэрфи, тестировщиков, невезение, но только не самих себя — собствен ную часть работы они будут считать выполненной успешно и в срок. Мы не понимаем такой позиции, хотя каждый из нас был в свое время руко водителем проекта, а также встречался с руководителями, действующими подобным образом. Нисходящее тестирование против восходящего Существует и еще один принцип организации тестирования, при кото ром программа так же, как и при восходящем способе, тестируется не целиком, а по частям. Только направление движения меняется — сначала тестируется самый верхний уровень иерархии модулей, а от него тестиров- щик постепенно спускается вниз. Такая технология называется нисходящей (top-down). Обе технологии — и нисходящую и восходящую — называют также инкрементальными. При нисходящем тестировании отпадает необходимость в написании оболочек, но заглушки остаются. По мере тестирования заглушки по оче реди заменяются на реальные модули. Мнения специалистов о том, какая из двух инкрементальных стратегий тестирования более эффективна, сильно расходятся. Йордан (Yourdon, 1975) доказывает, что гораздо лучше нисходящее тестирование, а Майерс (Myers, 1976) утверждает, что, хотя у обоих подходов есть свои преимуще ства и недостатки, в целом восходящее тестирование лучше. По мнению же Данна (Dunn, 1984), эти способы примерно эквивалентны. На практике вопрос выбора стратегии тестирования обычно решается просто: каждый модуль по возможности тестируется сразу после его напи сания, в результате последовательность тестирования одних частей про граммы может оказаться восходящей, а других — нисходящей. Глава 3: Типы тестов ... 77 Статическое тестирование против динамического При статическом тестировании программный код вообще не выполня ется — он тестируется только путем логического анализа. Две описанные выше базовые стратегии — тестирование "черного ящи ка" и тестирование "стеклянного ящика" — являются динамическими. Программа запускается, вводятся данные, и программист или тестировщик анализирует результат. Разница только в том, на какой информации осно вывается подбор тестов. Для статического анализа существует множество инструментальных средств. Самое известное из них — компилятор. Встретив синтаксическую ошибку или недопустимую операцию, компилятор выдает соответствующее сообщение. Ряд полезных сообщений выдает и компоновщик — о повто ряющихся именах переменных и других объектов, ссылках на необъявлен ные переменные и функции. О других полезных средствах автоматизации статического и динамического тестирования рассказывается в главе 11. Статический анализ программы может выполняться и людьми. Они читают исходный код, возможно, обсуждают его, и, как правило, находят достаточно много ошибок. Вот примеры такой работы. • Обзорные, инспекционные и рецензионные совещания. Это точно та кие же совещания, какие проводятся для анализа проекта программ ного продукта. Майерс (Myers, 1979) предлагает для них очень полезный список контрольных вопросов. Он утверждает (1978), что для выявления ошибок обзорные совещания не менее полезны, чем динамическое тестирование специалистом, который не является автором кода программы. А у Фергана (Fergan, 1976) можно найти описание классического способа проведения таких дискуссий. • Работа за столом. Статический анализ программного кода может выполняться и в одиночку. Специалист читает и анализирует про граммный код. Если он не может понять, что делает конкретный фрагмент программы, он может поработать и за компьютером, но большую часть времени он все же проводит за столом. Он работает дольше, чем обычно длятся совещания, и, как правило, анализиру ет гораздо большие объемы кода. Этот вид тестирования может выполнять как сам автор программного кода, так и кто-то другой — в любом случае оно будет очень полезным. Вычитывать программный код (как собственный, так и чужой) — работа Довольно скучная, и многие ее не любят. В пользу этой процедуры горячо высказывается Вейнберг (Weinberg, 1971), а Бейзер (Beizer, 1984, 1990) 78 Часть I: Основы советует ускорить работу за счет игнорирования синтаксиса и всего того, что может сделать компилятор. Одним из полезных результатов чтения программного кода может быть заключение о том, достаточно ли он прост и логичен. Если код трудно читать, то очень вероятно, что программист недостаточно четко видит за дачу. И, кроме того, даже если в такой программе и нет ошибок, в даль- нейшем могут возникнуть большие сложности с ее модернизацией. Соответствие стандартам У каждой компании могут быть свои стандарты, определяющие, сколько в коде должно быть комментариев, какой максимальной длины могут быть отдельные модули, должен ли программист строго следовать соглашениям об именах переменных и функций и каковы эти соглашения. Анализ соот- ветствия кода таким стандартам прекрасно поддается автоматизации. Программная статистика Существует практика анализировать сложность программного кода с помощью статистических вычислений. Мы относимся к этому весьма скеп тически. На наш взгляд, такой математический анализ интересен разве что с теоретической точки зрения, практическая же его полезность весьма ограничена. Некоторые руководители требуют, чтобы программисты пере рабатывали код, пытаясь добиться "приемлемого", по их мнению, коэффи циента сложности. Но на практике это может привести разве что к замедлению работы и появлению лишних ошибок и проблем, поскольку, как известно, лучшее — враг хорошего. Да и как вообще можно "измерить" качество программы. Если она работает, выполняет все необходимое, если она тщательно про думана и протестирована — оставьте ее в покое. И еще не следует забывать, что краткость — сестра таланта. А относи тельно посредственный программист, пытаясь предельно сократить и опти мизировать код, не сможет его как следует отладить и в результате испортит то, что прекрасно работало. НИ В КОЕМ СЛУЧАЕ НЕ СЖИМАЙТЕ ПРО ГРАММНЫЙ КОД! Если вы потребуете, чтобы программисты уменьшили коэффициент сложности кода, можете ли вы быть уверены, что новый код будет полностью функционально эквивалентным старому? Глава 3: Типы тестов ... 79 Намеренные ошибки: псевдоотладка и мутационное тестирование Одной из довольно специфических технологий тестирования программ является псевдоотладка (bebugging). В программу намеренно внедряется ряд ошибок. По мнению Вейнберга (Weinberg, 1971), программист, зная навер няка, что в программе есть ошибки, будет гораздо тщательнее ее тестиро вать. Но главное назначение этой технологии — оценить эффективность проводимых тестов. Если внедрить в программу 100 ошибок, а затем про вести тестирование, в результате которого будет выявлено только 20 из них, значит, в программе останется еще около 80% невыявленных ошибок. Еще одним способом проверки адекватности проводимых тестов явля ется мутационное тестирование(тutatiоп testing). В программу вносится ма ленькое изменение, называемое мутацией. В процессе тестирования результат такого изменения должен обязательно проявиться. Если же это го не случится, это может означать, что тесты подобраны неудачно. Подробное описание технологии мутационного тестирования можно най ти у Де Милло (DeMillo, 1987). Анализ производительности Для анализа производительности программного продукта можно приме нять технологию как "черного" так и "стеклянного ящика". Однако вторая из них дает лучшие результаты, поскольку позволяет с помощью дополни тельных программных средств фиксировать время выполнения отдельных модулей, прохождения конкретных путей или скорость обработки специ фических типов данных. Одной из целей выполнения анализа производительности является ее увеличение. Если выявить модули, которые выполняются чаще или доль ше остальных, их можно переработать и тем самым повысить скорость работы всей системы. Группы тестирования обычно работают с программой как с "черным ящиком". Они сравнивают производительность последовательных версий программы, и, если обнаруживают, что после последней доработки она замедлилась, это является для них сигналом о возможной ошибке. Производительность программного продукта анализируют и еще с од ной целью — для выяснения его конкурентоспособности: если имеющие ся на рынке аналогичные программы работают значительно быстрее, придется "ускорить" и свою. 80 Часть I: Основы За более обширной информацией об анализе производительности про граммного обеспечения мы советуем обратиться к книге Бейзера (Beiaer, 1984). Регрессионное тестирование Основной работой тестировщиков является регрессионное тестирование. У этого т е р м и н а два значения, объединенных идеей повторного использо вания разработанных тестов. • Представьте, что вы провели тест, обнаружили ошибку и програм мист ее исправил. Вы снова проводите тот же тест, чтобы убедить ся, ч т о ошибки больше нет. Это и есть регрессионное тестирование. М о ж н о провести несколько вариаций исходного теста, чтобы как следует проверить исправленный фрагмент программы. В данном случае задача регрессионного тестирования состоит в том, чтобы убедиться, что выявленная ошибка полностью исправлена програм мистом и больше не проявляется. В н е к о т о р ы х коллективах в набор регрессионных тестов включают каждую найденную ошибку, даже если она исправлена уже давным- д а в н о . Каждый раз, когда в программу вносится изменение, все эти тесты проводятся снова. Особенно важно провести такое обстоятель ное тестирование, если программа изменяется спустя достаточно длительное время или новым программистом. Исправления очень чувствительны к таким изменениям, поскольку в тексте программы, если только они не задокументированы самым тщательным образом, выглядят как непонятные или неудачные фрагменты. • Второй пример применения регрессионного тестирования. После в ы я в л е н и я и исправления ошибки проводится стандартная серия тестов, но уже с другой целью: убедиться, что исправляя одну часть програмлмы, программист не испортил другую. В этом случае тестиру ется целостность всей программы, а не исправление одной ошибки. При инкрементальном тестировании основой автоматизации прове дения регрессионных тестов служат разработанные заглушки и обо лочки . А для автоматизации регрессионного тестирования "черного я щ и к а " можно воспользоваться программами перехвата и воспроиз веленияя ввода, описанными в главе 11. |