Другие технологии верификации, такие как статический анализ, проверка на модели и испытания, обладают огромным потенциалом, но ни одна из них не является столь совершенной, чтобы заменить тесты как доминирующую технологию. Поэтому необходимо понять, какова сфера действия и ограничения тестирования и как правильно его выполнять.

Все изложенные в статье принципы сформулированы на основе практического опыта тестирования программного обеспечения и исследований, предварявших разработку автоматизированных инструментальных средств, таких как AutoTest (se.inf.ethz.ch/research/autotest).

Определение тестирования

Тестирование как метод верификации — это парадокс. Тестирование программы для того, чтобы проверить ее качество, теоретически равносильно втыканию булавок в куклу, причем очень маленьких булавок в очень большую куклу. Разрешить этот парадокс можно, определив реалистичные ожидания.

Слишком часто тестированию в литературе по программной инженерии придается огромное значение, что, собственно, и отражено в определении, данном в Wikipedia: «Тестирование программного обеспечения — это процесс оценки качества компьютерных программ. Тестирование — это практическое техническое изучение, проводимое для того, чтобы предоставить заинтересованным сторонам информацию о качестве продукта или сервиса с учетом контекста, в котором, как предполагается, он будет работать». На самом деле, тестирование программы мало что говорит о ее качестве, поскольку 10 или даже 10 млн тестов — это лишь капля в океане всех возможных случаев.

Безусловно, связь между тестами и качеством программ существует, но она довольно слаба?— успешный тест позволяет оценить качество только в том случае, если прежде он не был пройден. Тогда это свидетельствует об отсутствии неудачи и, как правило, — отсутствии самой ошибки*.

Если систематически отслеживать неудачи и ошибки, то их регистрация может дать представление о том, сколько еще ошибок осталось. Если последние три недели при работе тестов было обнаружено 550, 540 и 530 ошибок, то тенденция обнадеживает, но маловероятно, что при следующем тестировании не будет выявлено ни одной ошибки или всего 100 таковых. (Математические модели надежности позволяют оценить это более точно и достоверно, если реализован эффективный процесс долгосрочной сборки данных.)

Единственная бесспорная связь — это отрицательная связь, или, в терминологии Поппера, «фальсификация». Неудачный тест говорит о недостаточном качестве программы. Кроме того, если раньше тест проходил, а теперь нет, то это свидетельство регрессии, указывающее на возможные проблемы качества в программе и в процессе разработки. Самое известное высказывание о тестировании поразительно точно это описывает.

«Тестирование программ, — отмечал Эдсгер Дейкстра, — можно использовать для того, чтобы показать наличие ошибок и никогда — для того чтобы показать их отсутствие!» Очень немногие (Дейкстра, вероятно, на это и не рассчитывал) понимают, что для тестировщиков это означает наилучшую возможность для саморекламы. Безусловно, любая методика, позволяющая находить ошибки, крайне важна для «заинтересованных лиц», от менеджеров до разработчиков и пользователей. Мы должны воспринимать эту максиму не как некое обвинение, а как определение тестирования. Конечно, это не столь амбициозно звучит, как «предоставление информации о качестве», но зато более реалистично и практично.

Принцип 1. Определение. Для того чтобы протестировать программу, нужно попытаться заставить ее работать неверно.

В силу этого принципа процесс тестирования обретает цель: его единственная задача — найти ошибки, инициируя неудачное выполнение. Любое умозаключение по поводу качества относится к области гарантии качества, но никак не к области тестирования. Это определение также напоминает нам, что тестирование, в отличие от отладки, не связано с исправлением ошибок, — оно связано только лишь с их поиском.

Тестирование и спецификации

При создании программ, опирающемся на тестирование (test-driven) и получившем известность благодаря методам «скорой» (agile) разработки, тесты ставят на первое место, но иногда считают, что они могут заменить спецификации. Не могут. Тесты, даже если их миллион, — это лишь примеры; в них отсутствует абстракция, которую может дать только спецификация.

Принцип 2. Тесты или спецификации. Тесты не заменяют спецификации.

Опасность заблуждения, что тесты могут выступать в роли спецификаций, была продемонстрирована целым рядом программных катастроф, которые произошли только потому, что никто не подумал об исключительном случае. Несмотря на то, что в спецификациях тоже могут быть упущены из виду какие-то случаи, по крайней мере, в них предпринимается попытка некого обобщения. В частности, спецификации могут использоваться для генерации тестов, даже автоматически (как в случае тестирования, опирающегося на модели); но обратное без вмешательства человека невозможно.

Регрессивное тестирование

Особенностью тестирования, если судить по практике создания программного обеспечения, является печальная склонность к появлению уже исправленных ошибок. Старые головы у гидры, как бы глубоко они ни были отсечены, вырастают снова. Это явление известно как регрессия и заставляет использовать регрессивное тестирование, то есть проверку того, что исправленное ранее по-прежнему работает. Как следствие, вы всегда должны помнить об ошибке, которую уже когда-то обнаружили.

Принцип 3. Регрессивное тестирование. Любое неудачное выполнение должно порождать тестовый случай, который навсегда становится частью тестового пакета данного проекта.

Этот принцип касается всех ошибок, возникших во время разработки и тестирования. Отсюда вытекает необходимость в инструментарии, позволяющем превращать неудачное исполнение в воспроизводимый тестовый случай, и недавно такой инструментарий появился: Contract-Driven Development, ReCrash, JCrasher.

Предсказания

Выполнение теста полезно только в том случае, если вы можете однозначно определить, прошел ли он. Критерий называется предсказанием теста. Если у вас есть несколько десятков или даже сотен тестов, то вы в состоянии проверить их результаты по отдельности, однако с увеличением их количества это становится все менее вероятным. Данная задача требует автоматизации.

Принцип 4. Использование предсказаний. Определение успеха или неудачи тестов должно происходить автоматически.

Эта формулировка оставляет открытым вопрос о форме таких предсказаний. Зачастую предсказания специфицируются отдельно. В исследованиях, таких как наше, они встроенные, поскольку анализируемое программное обеспечение уже включает в себя контракты, согласно которым тесты используются как предсказания.

Принцип 4 (вариант). Контракты как предсказания. Предсказания должны быть частью текста программы как контракты. Успех или неудача теста должны определяться автоматически, причем в рамках этого процесса необходимо вести мониторинг выполнения контракта во время работы программы.

Этот принцип детализирует предыдущий, в силу чего те, кто не использует контракты, могут придерживаться более простой его формы.

Тестовые случаи, проверяемые вручную и автоматически

Многие тестовые случаи проверяются вручную. Тестеры продумывают интересные сценарии выполнения и готовят соответствующие тесты. К этой категории можно добавить тесты, полученные в соответствии с принципом 3 как результат некорректного выполнения, их первоначально не предполагалось использовать при тестировании. Сейчас все чаще можно дополнить эти две категории автоматическими тестовыми случаями, полученными из спецификаций с помощью генератора автоматических тестов. Процесс, ограниченный тестами, выполняемыми вручную, не в полной мере использует возможности современных компьютеров.

Все подходы лишь дополняют друг друга.

Принцип 5. Тестовые случаи, проверяемые вручную и автоматически. Эффективный процесс тестирования должен включать в себя тестовые случаи, проверяемые как вручную, так и автоматически.

Достоинством тестов, выполняемых вручную, является их глубина: они отражают понимание разработчиком имеющегося круга проблем и структуры данных. Преимущество автоматических тестов в их широте: они выполняют проверку большого диапазона значений, в том числе экстремальных, которые люди могут пропустить.

Стратегии тестирования

Теперь мы переходим от практики тестирования к исследованиям новых технологий тестирования, которые, однако, уязвимы в смысле рисков мыслительного процесса. Вы придумали идею, которая, как вам кажется, обещает усовершенствования и подтверждается вашей интуицией. Тестирование — это сложный процесс, и после объективного анализа далеко не все разумные идеи оказываются полезными. Типичный тому пример — случайное тестирование. Интуитивно кажется, что любая стратегия, использующая знания о программе, должна быть лучше случайного ввода. Однако объективные показатели, такие как число найденных ошибок, свидетельствуют о том, что случайное тестирование часто превосходит казавшиеся разумными идеи. В обзоре Ричарда Хемлета, посвященном случайному тестированию (Encyclopedia of Software Engineering, J.J. Marciniak, ed., Wiley, 1994), приводится захватывающее противопоставление человеческого знания и научного анализа.

Ничто не заменяет эмпирические оценки.

Принцип 6. Эмпирические оценки стратегий тестирования. Оценивайте любую стратегию тестирования, однако, какой бы интересной она ни казалась, прибегайте к объективной оценке, используя точные критерии в воспроизводимом процессе тестирования.

Я радовался, как ребенок, читая в книге «Жизнь пчелы» (The Life of the Bee, Fasquelle, 1901) бельгийского драматурга Мориса Метерлинка о том, что происходит, если поместить несколько пчел и мух в бутылку и перевернуть ее донышком к источнику света. Как показано на рис. 1, пчелы, привлеченные светом, будут биться о стекло и умрут от голода и истощения. Мухи же, ничего не понимающие, испробуют все направления и вылетят из бутылки через пару минут. Метерлинк был поэтом, а не профессиональным биологом, и я не знаю, проводил ли он на самом деле этот эксперимент, но это прекрасная метафора для тех случаев, когда, очевидная глупость превосходит очевидный ум, как это часто происходит при тестировании.

Умнее — не всегда значит лучше. Если поместить в бутылку пчел и мух, а затем повернуть бутылку донышком к источнику света, пчелы, которые считаются умнее, устремятся к свету, разобьются о стекло и погибнут, в то время как более глупые мухи вылетят из бутылки через пару минут. Не кажется ли вам эта метафора подходящей для стратегий тестирования?

Критерий оценки

При применении последнего принципа остается вопрос, какой же критерий использовать. Литература по тестированию предлагает такие показатели, как «число тестов, впервые вызвавших сбой программы». Для практиков данный показатель далеко не самый полезный. Мы хотим найти все ошибки, а не всего лишь одну. Предположим, что использование критерия «первая неудача» будет корректным, и этот критерий применяется повторно. Но последующие неудачи могут иметь совсем иную природу; а автоматический процесс должен выявлять максимально возможное число ошибок, а не останавливаться на первой.

Число тестов тоже не очень полезно ни для менеджеров (которым нужно помочь принять решение о том, можно ли прекращать тестирование и выпускать продукт), ни для пользователей (которым нужно оценивать плотность ошибок). Более адекватным показателем является время тестирования, требуемое для обнаружения ошибок. С другой стороны, мы рискуем отдать предпочтение стратегиям, которые могут быстро обнаруживать ошибки, но только после длительного процесса продумывания теста, что увеличивает общее время. Вот почему (как и мухи, которые вылетают быстрее пчел) глупая, на первый взгляд, стратегия, такая как случайное тестирование, в целом может оказаться лучше.

Часто используются и другие показатели, в том числе различного рода степень охвата тестов (такие как степень охвата команд, ветвей или путей). Интуитивно они кажутся более полезными, но фактически нет убедительных подтверждений того, что более высокая степень охвата как-то влияет на качество. Фактически несколько последних исследований показали отрицательную корреляцию. Если модуль имеет более высокую степень охвата тестов, то, как правило, это происходит потому, что вы знаете, что с этим модулем связаны определенные сложности, и, действительно, зачастую он будет содержать больше ошибок.

Больше чем любой из этих показателей, имеет значение то, насколько быстро стратегия порождает неудачи, позволяющие выявлять ошибки.

Принцип 7. Критерий оценки. Самое важное свойство стратегии тестирования — это число обнаруженных ошибок как функция времени.

Функция обнаружения, то есть число ошибок в зависимости от времени fc (t), полезна по двум причинам — используя программную базу с известными ошибками, можно оценить стратегию, посмотрев, сколько ошибок база позволит обнаружить за данное время. Менеджеры проектов могут добавить fc (t) в модель надежности для оценки того, сколько еще ошибок осталось, и ответить на старый вопрос «Когда заканчивать тестирование?»

***

Мы старались не отходить от темы статьи: первый принцип говорит о том, что тестирование — это порождение неудач; последний — о количественном подтверждении этого общего соображения, что также лежит в основе всех других принципов. n

Бертран Мейер (bertrand.meyer@inf.ethz.ch) — профессор программной инженерии Высшей политехнической школы (Цюрих, Швейцария) и главный архитектор компании Eiffel Software (Санта-Барбара, Калифорния).


Bertrand Meyer, Seven Principles of Software Testing. IEEE Computer, August 2008. IEEE Computer Society, 2008. All rights reserved. Reprinted with permission.

* Я следую стандартной терминологии IEEE. Неудовлетворительное выполнение программы — это «неудача» (failure), указывающая на «ошибку» (fault) в программе, которая сама по себе является следствием «заблуждения» (mistake) программиста. Неформальным термином «дефект» (bug) могут называть любое из этих явлений. — Прим. автора.