Опубликован: 04.04.2012 | Доступ: свободный | Студентов: 1934 / 37 | Оценка: 4.60 / 4.40 | Длительность: 13:49:00
Лекция 11:

Технологии инженерии программ

Тестирование

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

Ошибочный тест выявляет дефект, успешный тест мало о чем говорит, так как в любой реальной программе число тестов невероятно велико. Даже программа, которая умножает два числа из 64-х битов, имеет 2128 вариантов.

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

За последние годы технология тестирования существенно прогрессировала. Эволюция шла в направлении большей автоматизации. Для всех известных языков программирования появились специальные инструментальные средства — каркасы, позволяющие записывать тесты и автоматически запускать систему тестов. Эти средства известны под общим именем "XUnit", следуя названию JUnit — каркасу Java. Их широкий успех объясняется тем, что альтернативный способ — ручное управление и выполнение тестов — стал нереалистичным для современных программ, требующих запуска большого числа тестов. Мощь компьютеров сделала возможным проверку системы на многих тестах, но для этого требуется автоматическая поддержка.

Автоматизация, в частности, необходима для задачи, известной как регрессионное тестирование. Имеет место факт, удивительный для новичков, но характерный для разработки ПО,— исправленные дефекты могут вновь появляться в последующих релизах (ПО частично регрессирует к прежнему состоянию, отсюда и название — регрессионное тестирование). Причины регрессии следующие:

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

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

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

Современные исследования автоматизации тестирования идут значительно дальше. Примером новых возможностей является каркас — EiffelTestFramework, который, начиная с версии 6.3, стал интегральной частью EiffelStudio. Здесь в добавление к стандартному механизму "XUnit" появились два продвинутых свойства.

  • Синтез теста из отказа: каждое выполнение, приводящее к отказу, в соответствии с принципом ошибочного теста автоматически создает тест. Новинкой здесь является автоматизация. Многие из наиболее важных потенциальных тестов приходят из интерактивных выполнений, которые приводят к отказам в процессе разработки, но при обычном подходе теряются после коррекции и не появляются в наборе регрессионного тестирования. Здесь же процесс преобразования отказа в воспроизводимый тест осуществляется автоматически.
  • Генерация тестов по спецификациям: можно сделать запрос к тестирующему каркасу на тестирование класса, не задавая при этом никаких входных данных. Инструментарий автоматически будет проверять все методы класса, используя значения и объекты, создаваемые автоматически. Процесс может идти в фоновом режиме, в то время как вы можете продолжать работать над проектом. Вас потревожат только в случае обнаружения ошибки. Помните, что тестирование служит не для выяснения качества, а для обнаружения ошибок. Ошибки в данном случае будут возникать при нарушении постусловий и инвариантов.

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

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

Тестирование встречается на разных уровнях гранулярности.

Юнит-тестирование (модульное тестирование) предназначено для тестирования отдельных модулей — типично классов или кластеров в ОО-разработке. Обычно оно выполняется индивидуальными разработчиками соответствующих модулей.

Интеграционное тестирование оценивает, как выполняется группа модулей или подсистем при их соединении. Обычно это задача группы разработчиков, возможно, выполняемая специализированным подмножеством — командой тестеров или командой страхования качества.

Системное тестирование тестирует систему в целом. Часто этот шаг по-прежнему выполняется командой тестеров. Приемочное тестирование предполагает тестирование с позиций заказчика, и ответственность за него лежит на организации заказчика, оно выполняется объединенной группой представителей разработчиков и заказчика.

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

В заключение отметим концепцию покрытия тестов, применимую главным образом к белым ящикам. Покрытие — это мера качества набора тестов, позволяющая оценить объем протестированной функциональности. Меры покрытия могут включать:

  • покрытие операторов: какой процент операторов программы выполнялось при запуске набора тестов?
  • покрытие ветвей: какой процент ветвей программы (элементарных путей программы, например, каждый условный оператор задает две ветви) был пройден при запуске набора тестов?

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

Статические методы

Закончим обзор В&П рассмотрением статических методов.

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

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

Моя статья "Design and Code Reviews in the Age of the Internet" (Communications of the ACM, vol. 51, no. 9, September 2008) описывает этот процесс в деталях.

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

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

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

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

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

Поскольку такие доказательства требуют включения многих деталей, а также и потому, что доказательствам, сделанным человеком, нельзя полностью доверять (ошибки в доказательствах возможны не в меньшей степени, чем ошибки в программе), процесс должен основываться на автоматически работающем инструментарии, выполняющем доказательство корректности программ. Многие такие программы представляют надстройку над программами, доказывающими правильность теорем, способных выполнять математические выводы. Работы над автоматизацией методов доказательства ведутся десятилетиями; в последние годы они получили новые импульсы благодаря продвижениям в технологии доказательств и лучшего понимания проблем. Это активно развиваемая область исследований.

Наиболее впечатляющий прогресс достигнут в направлении, где обычно не пытаются дать полное доказательство корректности, но фокусируются вместо этого на идентификации специфических отказов, — то, что обычно делает тестирование. Проверка модели использует преимущество мощи компьютера для исследования пространства состояний программы или, более реалистично, ее упрощенной версии — модели. Если это позволяет уменьшить пространство состояний до приемлемых размеров, то тогда можно определить, будет ли нарушать любое из состояний интересующее нас свойство — часто корректность или безопасность. Этот подход интегрирует некоторые идеи тестирования (исследование многих случаев, фокусируясь на непокрытых дефектах вместо установления полной корректности). Но в целом это статический метод, являющийся формой доказательства. Абстрактная интерпретация определяет абстрактную версию программы, к которой применимы продвинутые методы статического анализа. Одним из успешных проектов было доказательство большой критически важной программы, встроенной в "Аэробус" A330/340 и A380, в результате чего не было отказов системы в период ее эксплуатации.

Что это дает, можете вы спросить, для повседневной практики программирования? Это во многом зависит от места вашей работы. Долгое время формальные методы — доказательства и связанные приемы — рассматривались как интеллектуально привлекательные идеи, не применимые в индустриальных разработках (назвать подход "академическим" равносильно "поцелую смерти"). Эта точка зрения сегодня уже не доминирует. При постоянном улучшении как теории, так и инструментария, и с возрастающей опасностью рисков неверно функционирующих программных систем растет число индустриальных разработок, использующих формальные методы и инструменты. Некоторые из уроков обнадеживают, некоторые разочаровывают.

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

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

Остальная часть индустрии обычно не готова принять такой вид аскетизма, который требует техника доказательств от своих последователей. Ведутся серьезные исследования, чтобы сделать их более применимыми в главных направлениях разработки ПО. Тони Хо-ар выступил с инициативой "Grand Challenge" ("Большой вызов"), предполагающей сосредоточенные международные усилия по созданию верифицируемого ПО. Можно, в самом деле, надеяться, что в ближайшие годы формальные методы покажут свои преимущества даже тем из нас, чьи программы при неправильном функционировании не угрожают жизни людей.

Надежда Александрова
Надежда Александрова

Уточните пожалуйста, какие документы для этого необходимо предоставить с моей стороны. Курс "Объектно-ориентированное программирование и программная инженения".