Тестирование софта - статьи

Документальная поддержка.

В комплект приобретённого инструмента должен входить пакет сопроводительной документации, который включает описание технических возможностей и требований к окружению системы (system requirements), руководство пользователя (user guide), системного администратора (installation guide) и в некоторых случаях руководство администратора системы (process manager guide). Комплект документации должен быть предоставлен в печатном и электронном виде, кроме того, стоит обратить внимание на локализацию документации, так как документация, предоставленная на родном для покупателей / разработчиков языке, будет гораздо быстрее обработана. Самым удобным и эффективным в работе, можно считать электронный вид документации, выполненный по типу MSDN или TechNet. Конечно, такой вариант оформления сопроводительной документации применим при достаточно большом объёме. Такая система требует отдельной инсталляции, является по сути своей отдельным программным продуктом и подходит под классификацию не как документация, а скорее как база знаний. Такого типа система документации позволяет строить более сложные запросы и базируется не только на описании программного продукта или технологии, но включает в себя также и статьи схожей тематики, описания наиболее часто встречаемых проблем, информацию о тематических ресурсах в сети internet.

Интеграция с системами разработки.

Наравне с интеграцией процесса тестирования в процессы проектирования и разработки ПО, современные средства автоматизации процессов тестирования должны предоставлять механизмы интеграции с системами разработки. Под системами разработки ПО будем понимать не только саму среду разработки (development environment) уровня Visual Studio и Delphi, но и инструменты планирования и управления процессом разработки (к примеру Microsoft Project Manager, DevPartner, Rational Unified Process), документооборота и управления ошибками, конфигурациями (Borland StarTeam, Rational ClearQuest ) и средства централизованного хранения и изменения данных (Visual Source Safe, CVS). Виды интеграции. Какие сервисы может предоставить современная система автоматизации тестирования в разрезе взаимодействия с системой разработки?
  • Генерация тестовых сценариев на основе модели проектируемой системы (многие системы позволяют проводить анализ моделей Rational Rose и на их основе создавать сценарии использования и прототипы интерфейсов приложения).
  • Генерация наборов тестовых данных на основе систем разработки структур баз данных.
  • Доступ и выполнение unit-тестов или других тестовых процедур в коде приложения.
  • Передача тестовых данных и анализ получаемых результатов.
  • Автоматическая генерация данных для создания запросов на изменение в системах управления ошибками.
  • Генерация отчётов в системах документооборота или офисных приложениях.. Таким образом, система автоматизации тестирования в идеальном случае должна иметь возможность интеграции со всеми типами систем разработки ПО и иметь достаточную гибкость конфигурирования, которая позволит использовать её с используемыми средствами хранения и обработки данных. Что даёт тесная интеграция с технической точки зрения? Преимущества использования среды автоматизации тестирования выдержанной в рамках общих требований к системе разработки и использующей схожие технологии хранения данных:
  • процесс тестирования можно будет рассматривать как полноценный этап разработки с точки зрения решения проблем технического обеспечения (создание тестовых сред на базе инфраструктуры отдела разработки с последующим выделением в отдельную информационную единицу, обеспечение вычислительными и производственными мощностями, такими как дисковое пространство серверов общего использования, базы данных, распределённая память и т.д.),
  • процесс тестирования получает возможность технического и технологического обеспечения надёжности в рамках обеспечения надёжности всего процесса разработки (механизмы резервного копирования / восстановления данных, сервисные обновления операционных систем и т.д.),
  • к данным и документации процесса тестирования можно применять аналогичные механизмы защиты от сбоев и распространять на них политику безопасности и разграничения доступа, которая выработана для всего процесса разработки в целом (возможность применения доменной политики безопасности и разграничения прав доступа к ресурсам). Выбор системы автоматизации должен происходить на этапе выбора технологии разработки и инструментария процесса разработки. Построение системы автоматизации тестирования происходит параллельно с построением среды и инфраструктуры разработки. Система, выбранная из нескольких аналогичных по функционалу, но более органично интегрируемая с инструментарием и обеспечением системы разработки, даёт более широкие возможности использования и как следствие повышает окупаемость вложений.

    Обучение и сертификация персонала, работающего с набором инструментов и/или методологией.

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

    Поддерживаемые процессы тестирования.

    Так как система автоматизации тестирования тесно связана с реальными процессами разработки программных систем, а также опирается на определённые процессы тестирования, при анализе необходимо в первую очередь обращать внимание на поддержку инструментом или набором инструментария определённых процессов/технологий тестирования и жизненного цикла разработки ПО. Итак, первый критерий анализа: Поддерживаемые процессы тестирования. Стоит оговориться, что на данном этапе развития рынка систем автоматизации тестирования, существует два подхода к построению инструментария тестирования. Одним типом можно считать продукты, которые охватывают все технологии тестирования, обработку разноплановой информации, которая описывает этапы разработки и требования к программному продукту, а также осуществляют поддержку процессов генерации проектной документации. Другой тип инструментария можно оценивать как узкопрофильный, то есть не охватывающий все этапы тестирования и жизненного цикла ПО, а осуществляющий полноценную поддержку какого-либо важного функционала. К примеру, в последнее время получили широкое распространение так называемые bug-tracking системы, то есть системы управления ошибками. Для более полного анализа стоит разносить в процессе оценки инструменты разных типов, по разным категориям, одновременно расширяя набор критериев для узкоспециализированных инструментов. Рассмотрим более подробно технологии и процессы тестирования программного обеспечения, которые могут поддерживаться инструментальными средствами тестирования.
  • Управление жизненным циклом (Lifecycle Management), как процесс тесно связанный с планированием этапов тестирования, как при "водопадной", так и при циклической модели разработки ПО. ресурсов этапов разработки / тестирования.
  • Управление тестированием (Test management), оценка затрат, времени, ресурсов этапов. Организация хранения, использования тестовых сценариев, их организации в тестовые группы, анализ результатов работы.
  • Управление изменениями (Change request management), как процесс специфичный для этапов тестирования, но интегрированный в процесс внесения изменений в программный код.
  • Управление ошибками (Tracking and defect management), процесс отработки обнаруженных ошибок, выявление повторяющихся ошибок, анализа причин их возникновения.
  • Управление требованиями (Requirements management), процесс управления изменяющимися требованиями к разрабатываемой системе.
  • Управление конфигурациями (Configuration management), управление конфигурациями и настройками разрабатываемых систем. Автоматизирование, как процесс построения автоматизированных окружений, для выполнения однотипных базовых операций (построений билдов, соблюдение версионности, генерация отчетной и проектной документации; создание, хранение, выполнение тестовых процедур, обработка результатов их работы), а также как процесс интеграции систем разработки и тестирования. Поддержка этих процессов является важным критерием при оценке системы автоматизирования тестирования.

    Поддерживаемые технологии.

    Под технологиями будем понимать - организованную совокупность процессов, элементов, устройств и методов, используемых для обработки информации. К примеру, технологии .NET, CORBA, OLE, COM, DCOM, COM+ и т.д. Под поддержкой технологий инструментарием тестирования будем понимать возможность записи и воспроизведения тестовых скриптов, которые будут производить тестовые вызовы элементов программного кода выполненных в стандартах той или иной технологии. Следует оговориться, что уровень поддержки той или иной технологии существенно различается на уровне реализации тестовых скриптов. К примеру, большинство производителей инструментальных средств тестирования реализуют возможность записи и воспроизведения скриптов имитирующих действия пользователя по отношению к пользовательскому интерфейсу, то есть поддерживаются возможности выбора из набора графического интерфейса программы определённого элемента (контрола) и передачи ему фокуса с последующим выполнением нажатия. Такая поверхностная поддержка, конечно, производит впечатление на потенциального заказчика (довольно эффектно выглядит, к примеру, автоматическое заполнение многостраничных форм регистрации за несколько секунд, даже с генерацией случайных данных из заданного диапазона), но без возможности непосредственной работы с объектной моделью приложения, этот функционал остаётся лишь красивым дополнениям к системе автоматизированного тестирования, чем её основой. Приведём пример: Есть форма с элементом выпадающий список. Количество элементов списка определяется количество записей в таблице базы данных и есть динамически изменяемая величина. Значения элементов списка представляют, к примеру, числовые данные. Список отсортирован не по величине значений, а по дате последнего изменения этого значения. Задача: при выполнении тестового скрипта выбрать из выпадающего списка максимальное (или минимальное) значение. Напомним, что список не отсортирован по величине, и выбрать первый или последний элемент, координаты которого можно рассчитать не получится.
    Кроме того, количество элементов списка также величина динамическая - выбор последнего элемента затруднён. Нужно получить список всех значений из списка, заполнить ими массив, отсортировать, найти нужное значение. Обратиться к элементу список, споцизионироваться на нужный элемент (задача не просто решаемая перемещением курсора мышки на определённую координату), имитировать нажатие. Без возможности доступа к объектной модели элемента такая задача не решается в приемлемые сроки. Как результат, встречаются скрипты, которые в подобной ситуации просто выбирают какое-то (неуправляемое) значение. Общая картина работы такого тестового скрипта более напоминает тыканье слепого в интерфейс пользователя, чем заполнение формы тестовыми данными. Суть поддержки той или иной технологии инструментом тестирования состоит в возможности работать через средства инструмента с объектной моделью приложения, вызовами процедур и методов из тестовых скриптов, генерации вводимых данных на основе анализа диапазона передаваемых параметров. Говоря неформальным языком, поддерживаемой можно считать такую технологию, при работе с которой инструмент как минимум "видит" объектную модель приложения, выполненного согласно стандартов технологии. Если в описании инструмента заявлено, что с его помощью можно проводить тестирование приложений выполненных с поддержкой практически всех технологий, следует внимательно уточнять уровень этой самой поддержки, потому что в большинстве случаев компания разработчик имеет в виду именно запись/воспроизведение имитационных сценариев. Такая поддержка никоим образом не охватывает непосредственно тестирование серверных компонентов или сервисов приложений, выполняющих бизнес-логику приложений. Также практически невозможно оценить в целом уровень производительности приложений, моделируя пользовательскую нагрузку имитацией реальных действий пользователя через пользовательский интерфейс. Отдельно хотелось бы оговорить специализированные инструменты, которые поддерживают технологии работы с данными, серверные компоненты и сервера баз данных.


    В целом картина на рынке таких инструментов отличается от состояния дел на рынке инструментов для тестирования функциональности. Суть таких инструментов, не только создать нагрузку на сервер базы данных (для этого существует отдельный класс так называемых нагрузочных и стрессовых инструментов), с тем, чтобы зафиксировать время отклика и/или выполнения того или иного запроса или вызова. Средства, которые поддерживают технологию какой-то определённой СУБД (Oracle, Informix, DB2, MS SQL) должны обладать не только функционалом вызова, к примеру, хранимых процедур, или подобной функциональностью, которая будет создавать нагрузку и анализировать поведения сервера, но в идеале и проводить анализ конфигурации серверного окружения, структур и схем хранения данных, контроль за следованием стандартам обращений к данным с тем. Результатом работы такого средства может служить сгенерированный набор аналитических данных на основе которых сервер или окружение будет конфигурироваться под конкретную архитектуру базы данных и возможно настраиваться согласно выбранной технологии реализации клиентского приложения. Указанные требования накладывают существенные ограничения на функциональность подобных средств. Зачастую компания-производитель поддерживает на глубоком уровне только одну конкретную СУБД, разработка инструмента ведётся в тесной интеграции с компанией разработчиком самой СУБД с учётом особенностей архитектуры и технологических решений. Инструментарий получается довольно узкоспециализированный и дорогостоящий, его применения оправдано в случае внедрения долгосрочных промышленных систем автоматизации или систем повышенной отказоустойчивости.

    Поддерживаемые типы тестов.

    Вторым критерием анализа, логично было бы выделить поддержку различных типов тестов, которые автоматизируются системой. Сообществом тестировщиков и практической работой, выделены основные типы тестов, описание которых поддаётся формализации. Рассмотрим их.
  • Функциональные тесты, которые проводят тестирования пользовательских интерфейсов, моделируя реальную работу пользователя с конечной системой. В этот же раздел входит и так называемое Usability тестирование, которое в последнее время становится неотъемлемой частью процесса тестирования программных систем, а значит должно автоматизироваться наравне с другими типами тестов.
  • Регрессионные тесты, контролирующие сохранение функциональности при переходе к следующей версии (билду) разрабатываемой системы.
  • Нагрузочные тесты, испытание систем на производительность под моделируемой нагрузкой.Тестирование безопасности, как процесс выявление потенциальных уязвимостей системы, определение признаков уязвимости и локализация компонентов или модулей, потенциально ущербных в плане общей безопасности системы.
  • Нагрузочное тестирование и тестирование производительности, как этап тестирования архитектурного решения, конкретной его реализации и конфигураций оборудования.
  • Unit-тесты, как методология разработки, ведомой тестированием. Тестирование модулей, на уровне разработки самого кода.Unit-тесты, как методология разработки, ведомой тестированием. Тестирование модулей, на уровне разработки самого кода.
  • Анализ исходного кода, функционал выявления несоответствий между полученным в результате разработки кодом и принятыми правилами кодирования (code-naming-convention), анализ покрытия и используемости кода. (covering, hit-counting)
  • Анализ утечек памяти, анализ работы системы с операционной средой и ресурсами системы.
  • Наличие функционала, который позволит автоматизировать выполнение определённого типа тестов, является вторым критерием анализа инструментария средств автоматизации тестирования.

    Представительство компании-разработчика в странах ближнего зарубежья.

    Почему критерий наличия у компании разработчика представительства в странах ближнего зарубежья (а в идеальном случае и в стране, где находится компания, которая приобретает средство автоматизации) настолько важен? Одной из причин, по которой компания-разработчик должна иметь представительство или компанию, которая представляет продукты на отечественном рынке, является цена приобретаемого за границей продукта. В самом деле, на современном этапе развития платёжных систем, сложности при переводе денег, даже для частного лица, в другую страну не может служить поводом для отказа от приобретения инструмента. Одним из моментов, который стоит учитывать, это увеличение стоимости самого инструмента, а стало быть и сроков его окупаемости, в случае переводов за границу и затрат на доставку. В нашей стране вопросы также возникают вокруг процесса получения самого продукта, если он в целях безопасности не передаётся по каналам связи, а высылается почтой. "Растаможивание" программного обеспечения также является фактором, увеличивающим его стоимость. А так как серьёзные программные продукты идут в поставке с документацией и дополнительными компонентами, комплект поставки может составлять не один десяток носителей типа компакт-диск. Стоимость услуг "растаможивания" продукта определяется количеством компакт дисков. Прибавим сюда компакт с рекламой и презентацией и вполне реально получаем солидную прибавку к стоимости продукта, цена которого нас устраивала вначале. В пользу важности этого аргумента может послужить тот факт, что многие компании в СНГ и в Украине в частности переводят подписки на продукцию компании Microsoft в план поставки на DVD носителях. Как известно, DVD носители вмещают до 5 Гб полезной информации, а стало быть, могут служить заменой примерно 7-ми компакт-дискам. Зачастую, гораздо выгоднее установить несколько дополнительных приводов для чтения DVD дисков, чем платить за "растаможку" примерно 150 компактов ежеквартального обновления подписки от Microsoft. Немаловажным моментом также является тот факт, что компания уже работает на рынке, где вы готовы потреблять её продукт, что может служить большим плюсом при оценке уровня зрелости самой компании-производителя и её продуктов в частности. Итак, второй фактор, это уровень зрелости компании, который поддаётся оценке по наличию компании представителя. По наличию представительства также можно судить о качестве локализации продукта на родной для разработчиков и тестировщиков вашей компании язык. О важности такого атрибута любого программного продукта как локализованная версия программы и сопроводительной документации говорилось в предыдущем разделе. Идеальным случаем представительства можно читать компанию, которая осуществляет не только продажу и консультации по продукту, но также оказывает услуги поддержки пользователей. Кроме того, что можно рассчитывать на службу поддержки, к которой можно обратиться на родном или родственном языке (имеет смысл для стран СНГ, где русский язык получил широкое распространение), время запросов пользователей будет отличаться не более чем на несколько часов от времени работы службы поддержки, если даже сама служба работает не круглосуточно.

    Разработка критериев анализа систем автоматизации тестирования

    , автор проекта "Тестер",
    Статья была опубликована в Журнале для профессиональных программистов "argc & argv", Выпуск № 6 (51/2003)
  • Средства автоматизации процессов тестирования представлены на рынке очень широким кругом компаний производителей. Автоматизация тестирования затрагивает всё более глубокие технические процессы разработки ПО и всё глубже интегрируется в процесс его производства.
  • Каким образом начать анализ рынка средств автоматизации тестирования?
  • На что стоит обращать внимание при анализе средств автоматизации тестирования?
  • По каким критериям можно оценивать функциональные возможности инструментов и группировать системы автоматизированного тестирования? Статья затрагивает вопросы классификации средств тестирования и предлагает систему анализа, основанную на оценке качественных характеристик инструментария и сопутствующих условий внедрения и использования. Рассмотрен широкий спектр критериев: от набора функционала, который реализован в инструменте, до оценки уровня зрелости самой компании производителя и службы поддержки.

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

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

    Техническая поддержка.

    Под технической поддержкой будем понимать возможность обратиться в службу технической поддержки, воспользовавшись одним из электронных средств связи или телефон. Особо стоит обратить внимание на круглосуточную поддержку при приобретении средств у западных производителей, так как из-за разницы во времени запросы в службу поддержки могут прийтись на ночное время. Аналогичным критерием при оценке уровня тех поддержки служит поддержка 365 дней в году, так как обращение в службу поддержки должны обрабатываться и в дни религиозных праздников или выходных дней той страны, в которой работает служба поддержки. Уровень доступности поддержки принято оценивать по шкале 365/7/24 - кол-во дней в году / кол-во дней в неделю / кол-во часов в сутки. Так к факторам оценки уровня технической поддержки стоит отнести скорость реакции службы на запросы клиентов, сопровождение базы вопросов и ответов, которые встречаются наиболее часто, реакция на обнаружение критических ошибок (фиксирование описание критических ошибок в доступном месте на сайте, к примеру), а также работу, как с новыми, так и с постоянными клиентами. Критическим может оказаться любой из приведённых факторов, так как служба поддержки не может считаться полноценной, если она не доступна круглосуточно и не работает в ожидаемых и заявленных рамках.

    Тестирование софта - статьи

    Аннотация

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

    Автоматическая генерация позитивных

    С.В. Зеленов, С.А. Зеленова Труды Института Системного Программирования РАН, 2004 г.

    Критерии покрытия

    Как видно из описания LL- и LR-анализаторов, основной момент их работы – принятие решения о дальнейших действиях на основании некоторых неполных данных (прочитанной части входного потока). Для LL-анализатора ситуации выбора соответствует пара (нетерминал на вершине стека, текущий входной символ), а для LR-анализатора – пара (символ состояния конечного автомата на вершине стека, текущий входной символ). Отсюда возникают следующие критерии покрытия для позитивных тестовых наборов: (PLL) Покрытие всех пар (нетерминал A, допустимый следующий токен t),где пара (A,t) считается покрытой тогда и только тогда, когда в тестовом наборе существует последовательность токенов, являющаяся предложением целевого языка, имеющая вывод SКритерии покрытия?A?Критерии покрытия?t??. Иными словами, LL-анализатор, обрабатывая эту последовательность, получит ситуацию, когда на вершине стека будет находиться символ A, а текущим входным символом будет токен t. Модификация этого критерия для расширенной формы BNF грамматики была сформулирована в работе [1]. (PLR) Покрытие всех пар (символ si состояния конечного автомата, помеченный символом X переход из состояния si),где пара (si,X) считается покрытой тогда и только тогда, когда в тестовом наборе существует предложение языка, имеющее вывод SКритерии покрытия?X? такой, что префикс ? отвечает состоянию si. Или, что то же самое, LR-анализатор, обрабатывая это предложение получит ситуацию, когда на вершине стека будет находиться символ si, а началом текущего входного потока будет последовательность токенов, отвечающая символу X.Аналогично возникают следующие критерии покрытия и для негативных тестовых наборов (эти критерии имеют параметр r – количество “правильных” токенов, предшествующих “неправильному” токену): (NLLR) Пусть A – нетерминал. Последовательность токенов t1...tr назовем допустимой для A предпоследовательностью токенов, если существует сентенциальная форма ?t1...trA?, выводимая из стартового правила.
    Рассмотрим объединение множеств Критерии покрытияt1... tr по всем допустимым для A предпоследовательностям токенов длины r < R. Критерий состоит в том, что все пары (A,t'), где t' из рассмотренного объединения, должны быть покрыты. Здесь покрытие пары (A,t') означает, что среди тестов имеется последовательность токенов, не принадлежащая целевому языку, такая, что LL-анализатор, обрабатывая эту последовательность, получит ситуацию, когда на вершине стека будет находиться символ A, а текущим входным символом будет “некорректный” символ t'. (NLRR) Пусть si – символ состояния конечного автомата, определяющего активные префиксы. Последовательность токенов t1...tr назовем допустимой для si предпоследовательностью токенов, если существует выводимая из стартового правила последовательность токенов ?t1...tr? такая, что ее префикс ?t1...tr отвечает состоянию si. Рассмотрим объединение множеств Критерии покрытияt1...tr по всем допустимым для si предпоследовательностям токенов длины r < R. Критерий состоит в том, что все пары (si,t'), где t' из рассмотренного объединения, должны быть покрыты. Здесь покрытие пары (si,t') означает, что среди тестов имеется последовательность токенов, не принадлежащая целевому языку, такая, что LR-анализатор, обрабатывая эту последовательность получит ситуацию, когда на вершине стека будет находиться символ si, а текущим входным символом будет t'.Для получения ситуации (A,t') в критерии (NLL) или ситуации (si,t') в критерии (NLR) требуется, чтобы парсер нормально проработал какое-то время, а затем встретил неверный символ. Для достижения этой цели необходимо, чтобы токены, идущие в последовательности до неверного токена t', образовывали префикс некоторого предложения языка, при разборе которого возникала бы требуемая ситуация в стеке. Поэтому в качестве негативного теста мы будем рассматривать измененное (с помощью вставки или замены токенов) предложение целевого языка так, чтобы в нем содержалась неправильная последовательность токенов t1...trt', где t'? Критерии покрытияt1...tr.2Завершая этот параграф, введем еще два полезных критерия покрытия для грамматик специального вида. Пусть грамматика G такова, что ее каноническая система множеств пунктов удовлетворяет следующему свойству: если Ii и Ij – два различных множества из канонической системы, то множества базисных пунктов из Ii и Ij не пересекаются.


    Заметим, что для такой грамматики покрытие всех пар (состояние конечного автомата, переход из этого состояния в другое) достигается при покрытии всех пунктов грамматики. Рассмотрим следующий критерий покрытия для наборов позитивных тестов: (WPLR) Покрытие всех пар (пункт ? = B > ?•X? грамматики G, допустимый первый токен t для символа X). Пара (?,t) считается покрытой тогда и только тогда, когда в тестовом наборе существует предложение языка, имеющее вывод SКритерии покрытия?B?Критерии покрытия??X??Критерии покрытия??t???.Для грамматик указанного типа этот критерий является более сильным, чем критерий PLR. Действительно, нетрудно показать, что каждое состояние определяется множеством своих базисных пунктов. Отсюда, поскольку для грамматик указанного класса подмножества базисных пунктов у разных состояний не пересекаются, то покрыв все пункты, мы покроем и все состояния. Аналогично можно сформулировать критерий покрытия для наборов негативных тестов: (WNLRR) Пусть ? = B > ?•? – пункт грамматики G. Последовательность токенов t1...tr назовем предпоследовательностью токенов допустимой для ?, если существует выводимая из стартового правила последовательность токенов ?t1...tr•?, имеющая вывод Критерии покрытия
    т.е. ?t1...tr выводится из ??, а ? – из ??. Рассмотрим объединение множеств Критерии покрытияt1...tr по всем допустимым для ? предпоследовательностям токенов длины r < R. Критерий состоит в том, что все пары (?,t'), где t' из рассмотренного объединения, должны быть покрыты. Здесь покрытие пары (?,t') означает, что среди тестов имеется последовательность токенов, не принадлежащая целевому языку, такая, что некоторый ее префикс имеет вид ?t1...trt', где t1...tr – некоторая допустимая для ? предпоследовательность токенов такая, что t'? Критерии покрытияt1...tr.Сноски В общих словах мутационное тестирование состоит в следующем. Из тестируемого компонента получают множество его модификаций (мутантов), каждая из которых содержит ровно одну ошибку.


    Говорят, что мутант убит, если на некоторых входных данных его выход отличен от выхода исходной программы. Если имеется множество тестовых входных данных для программы, то с помощью мутационного анализа (mutation analysis) можно оценить качество этих тестов. Именно, если имеется множество мутантов, то критерий покрытия говорит, что все мутанты должны быть убиты. В случае синтаксического анализатора логично в качестве мутируемого материала рассматривать грамматику языка, т.к. ошибочный анализатор фактически распознает другой язык. В этом случае грамматика-мутант будет убита, если найдется последовательность токенов, принадлежащая языку, задаваемому этой грамматикой-мутантом и не принадлежащая исходному языку (в терминах анализаторов это как раз будет означать, что анализатор-мутант распознал данное предложение, а исходный анализатор – нет, т.е. анализатор-мутант оказался убитым). 2 Существует связь между предлагаемым подходом и методом мутационного тестирования. Именно, для любой последовательности токенов, являющейся негативным тестом в описанном выше смысле (т.е. предложением языка, “испорченным” с помощью вставки/замены “нехорошего” токена) можно построить грамматику-мутант такую, что данный негативный тест будет являться предложением языка, описываемого этой грамматикой-мутантом. Одним из принципов мутационного тестирования является так называемый эффект взаимосвязи (coupling effect): при обнаружении простых ошибок будут обнаруживаться и более сложные (см. [16]). Согласно этому эффекту взаимосвязи, мутации должны быть простыми. Заметим, что предлагаемый подход вполне согласуется с этим принципом. Продолжение

    Позитивные и негативные тесты для синтаксического анализатора

    В данной работе парсером мы называем булевскую функцию, заданную на множестве последовательностей токенов и принимающую значение “истина”, если последовательность является предложением данного формального языка, и “ложь” – иначе. Конечно, реальные парсеры могут иметь дополнительную функциональность (например, помимо булевского значения выдавать дерево разбора или идентификацию ошибки), но здесь мы такую функциональность не рассматриваем. Позитивный тест для парсера – это последовательность токенов, на которой парсер выдает вердикт “истина”, т.е. последовательность токенов, являющаяся предложением целевого языка. Негативный тест для парсера – это последовательность токенов, на которой парсер выдает вердикт “ложь”, т.е. последовательность токенов, не являющаяся предложением целевого языка. Для построения какого-нибудь позитивного теста достаточно, следуя правилам грамматики и ограничив рекурсию правил, вывести из стартового символа некоторую сентенциальную форму, состоящую из одних токенов. При построении же негативных тестов возникают два вопроса: насколько произвольна должна быть соответствующая последовательность токенов, и как добиться того, чтобы она действительно не принадлежала целевому языку. Сначала ответим на второй вопрос: как получить последовательность токенов, гарантированно не принадлежащую множеству предложений целевого языка. Рассмотрим грамматику G= (T ,N,P,S). Для каждого грамматического символа X ?T ?N, определим множество UX вхождений символа X в грамматику G. Это множество состоит из всех пар (правило p ?P, номер i символа в правиле p)таких, что символ, стоящий на i-ом месте в правой части правила p является грамматическим символом X. Пару (p,i) ?UX будем называть вхождением символа X в правило p. Пусть t – токен. Для каждого вхождения u ?Ut, u = (p,i), p = X > ?t? токена t в грамматику G можно построить множество Fu токенов t'?T таких, что существует вывод Позитивные и негативные тесты для синтаксического анализатораЗдесь греческие буквы обозначают некоторые субсентенциальные формы, т.е.
    последовательности нетерминалов и токенов. Если в грамматике G существует вывод SПозитивные и негативные тесты для синтаксического анализатора?XПозитивные и негативные тесты для синтаксического анализатора?? t предложения, оканчивающегося токеном t, то будем считать, что множество Fu содержит пустую последовательность ? ?Fu. Через Ft будем обозначать объединение множеств Fu для токена t: Позитивные и негативные тесты для синтаксического анализатораИными словами, множество Ft – это множество токенов, каждый из которых допустим для токена t в качестве следующего. В дальнейшем нас главным образом будет интересовать дополнение к множеству Ft в множестве T ?{?}. Будем обозначать это дополнение через Позитивные и негативные тесты для синтаксического анализатораТеорема 1. Последовательность токенов, содержащая подпоследовательность tt', где t'? Позитивные и негативные тесты для синтаксического анализатораt, не является предложением языка, описываемого грамматикой G. Доказательство. Очевидно из построения множества Позитивные и негативные тесты для синтаксического анализатораt. > Для последовательности токенов ? = t1...tn такой, что существует вывод SПозитивные и негативные тесты для синтаксического анализатора???, можно определить множество токенов Позитивные и негативные тесты для синтаксического анализатораПозитивные и негативные тесты для синтаксического анализаторатакое, что если t'? Позитивные и негативные тесты для синтаксического анализатораПозитивные и негативные тесты для синтаксического анализатора, то не существует вывода SПозитивные и негативные тесты для синтаксического анализатора??t'?. Тогда любая последовательность ??t'?, где t'? Позитивные и негативные тесты для синтаксического анализатораПозитивные и негативные тесты для синтаксического анализатора, не является предложением языка, описываемого грамматикой G. Итак, мы научились получать последовательности токенов, заведомо не являющиеся предложениями целевого языка. К вопросу о произвольности негативной последовательности токенов мы вернемся в следующем параграфе.

    Предварительные сведения

    В этом разделе мы приводим некоторые сведения из теории синтаксического анализа. Более подробное изложение приведенных фактов можно найти в известной книге А. Ахо, Р. Сети и Д. Ульмана (см. [5]). Грамматика формального языка задается четверкой G = (T,N,P,S), где
  • T – множество терминальных символов или токенов;
  • N – множество нетерминальных символов;
  • P – список правил грамматики;
  • S – стартовый символ грамматики.
  • Множество предложений формального языка, задаваемого грамматикой G, будем обозначать Предварительные сведенияG. Дадим несколько определений. Расширением грамматики G (или просто расширенной грамматикой) называется грамматика G' = (T,N',P',S'), где S' – новый нетерминальный символ, а к множеству правил добавлено правило S' > S. Сентенциальной формой будем называть последовательность грамматических символов – нетерминалов и токенов. Далее, греческими буквами из начала алфавита (?, ?, ?, ?, ...) мы будем обозначать какие-либо сентенциальные формы. Пустую сентенциальную форму будем обозначать через ?. Правосентенциальной формой называется сентенциальная форма, для которой существует правый вывод из стартового правила. Пример. Рассмотрим следующую грамматику:
    S > AB
    A > cd
    B > eCf
    C > Ae
    В ней сентенциальная форма cdB не имеет правого вывода, т.е. не может быть получена с помощью последовательного раскрытия самых правых нетерминалов. Примером правосентенциальной формы может служить форма AeCf. > Основой правосентенциальной формы называется подпоследовательность ее символов, которая может быть свернута в некоторый нетерминал, такая, что сентенциальная форма, полученная из исходной после свертки, может быть свернута в стартовый символ. Пример. Пусть грамматика та же, что и в предыдущем примере. Форма AeCf, как мы уже заметили, является правосентенциальной. В ней есть две подпоследовательности символов Ae и eCf, которые могут быть свернуты в нетерминалы C и B соответственно. Однако основой является только последовательность eCf, так как сентенциальная форма CCf невыводима из стартового символа.
    > Активным префиксом правосентенциальной формы называется префикс, не выходящий за границы самой правой основы этой формы. Пунктом грамматики G называется правило вывода с точкой в некоторой позиции правой части. Множество всех пунктов грамматики G будем обозначать через Предварительные сведения. Пример. Правило вывода A > XYZ дает 4 пункта: A > •XYZ, A > X•YZ, A > XY•Z и A > XYZ•. > Базисным называется пункт с точкой не у левого края правой части правила, а также пункт S' > •S в расширенной грамматике. Замыканием множества пунктов I (обозначается closure(I)) называется наименьшее множество пунктов, содержащее I в качестве подмножества такое, что для всякого пункта A > ?•B?? I и любого правила B > ? пункт B > •? лежит в closure(I). Для пары (I,X), где I некоторое множество пунктов грамматики G, а X – символ грамматики (терминал или нетерминал), определим функцию goto(I,X) – замыкание множества всех пунктов A > ?X•? таких, что A > ?•X? ? I. Рассмотрим расширенную грамматику G' = (T,N',P',S'), где N' = N?{S'}, P = P?{S' > S}. Пусть I0 = closure({S' > •S}). Начиная с I0, строится система множеств пунктов I0,...,IN так, что для всякой пары (Ik,X), где k = 0,...,N и X – символ грамматики, существует индекс j = 0,...,N такой, что goto(Ik,X) = Ij. Эта система пунктов называется канонической системой множеств пунктов. Используя каноническую систему I0,...,IN, можно построить конечный автомат V, распознающий активные префиксы, если в качестве состояний sj взять канонические множества Ij, а переходы задать с помощью функции goto. Широко известны два класса алгоритмов синтаксического анализа: LL-анализ и LR-анализ (см. [5]). LL-анализатор с помощью диаграммы переходов или таблицы разбора строит левый вывод предложения целевого языка. Нерекурсивная реализация LL-анализатора использует стек и таблицу разбора. Изначально в стеке находится символ конца строки $ и стартовый символ грамматики.


    На каждом шаге рассматривается символ на вершине стека X и текущий входной символ a. Действия анализатора определяются этими двумя символами:
  • если X = a = $, то анализатор прекращает работу и сообщает об успешном завершении разбора;
  • если X = a ? $, анализатор удаляет из стека символ X и переходит к следующему символу входного потока;
  • если X является нетерминалом, анализатор ищет такую альтернативу раскрытия символа X, для которой символ a является допустимым первым символом. После того, как требуемая альтернатива найдена, символ X в стеке заменяется обратной последовательностью символов альтернативы. Например, если искомая альтернатива X > ABC, то анализатор заменит X на вершине стека на последовательность CBA, т.е. на вершине стека окажется символ A. Конфликты, возникающие в процессе поиска альтернатив, могут разрешаться, например, с помощью “заглядывания вперед”, т.е. просмотра нескольких входных символов вместо одного.
  • Анализатор завершает работу, когда на вершине стека оказывается символ конца строки $. Рассмотрим теперь LR-анализатор, построенный на основе стека. У такого LR-анализатора имеются две основные операции:
  • перенос символа из входного потока в стек;
  • свертка нескольких последовательных символов на вершине стека в некоторый нетерминал.
  • Работа анализатора происходит так, что в стеке все время находится активный префикс некоторой правосентенциальной формы. При переносе символа и свертке на вершину стека кладется символ состояния sj конечного автомата V, кодирующий текущий активный префикс. LR-анализатор принимает решение о переносе или свертке, исходя из пары (символ sj, текущий токен входного потока). Анализатор завершает работу, когда в стеке оказывается стартовый символ грамматики.

    к надежности которого чрезвычайно высоки.

    Компилятор является инструментом, требования к надежности которого чрезвычайно высоки. И это неудивительно, ведь от правильности работы компилятора зависит правильность работы всех скомпилированных им программ. Из-за сложности входных данных и преобразований задача тестирования компиляторов является весьма трудоемкой и непростой. Поэтому вопрос автоматизации всех фаз тестирования (создания тестов, их прогона, оценки полученных результатов) стоит здесь особенно остро. Синтаксический анализ является частью функциональности любого компилятора. От корректности синтаксического анализа зависит корректность практически всей остальной функциональности – проверки семантических ограничений, оптимизирующих преобразований, генерации кода. Поэтому решение задачи тестирования синтаксических анализаторов является базой для решения задач тестирования всех остальных компонент компилятора. Для очень многих языков программирования существует формальное описание синтаксиса – описание грамматики языка в форме BNF, а для тех языков, для которых существуют только эталонные компиляторы (например, COBOL), делаются активные попытки построить такое описание (см.[9, 10, 14]). BNF языка является одновременно и спецификацией функциональности синтаксического анализа, таким образом, в этой области наиболее привлекательным является тестирование на основе спецификаций (см. [17]). Существование формального описания позволяет автоматизировать процесс построения тестов, что существенно снижает трудозатраты, а систематичность тестирования повышает доверие к его результатам. Построением тестов по грамматике занимались многие авторы. Основополагающей работой в этой области является работа [18], в которой сформулирован следующий критерий покрытия для множества позитивных тестов: для каждого правила в данной грамматике в множестве тестов должно присутствовать предложение языка, в выводе которого используется это правило. В той же работе Пардом предложил метод построения минимального тестового набора, удовлетворяющего этому критерию.
    Однако указанный критерий оказался недостаточным. Ламмель в работе [9] показал, что тестовые наборы, построенные алгоритмом Пардома, не обнаруживают простейших ошибок. Ламмель также предложил более сильный критерий покрытия, состоящий в том, что покрывается каждая пара правил, одно из которых можно применить непосредственно после другого. Предлагаемые другими авторами методы являются вероятностными (см. [7, 13, 11, 12]) и не описывают критериев покрытия, и потому для них возникает вопрос остановки генерации тестов, который решается, например, с помощью введения вероятностей появления правил и уменьшения этих вероятностей при каждом новом появлении правила в выводе. В любом случае завершение работы алгоритма за конечное время является проблемой. Кроме того, произвольность остановки генерации нарушает систематичность тестирования. Все приведенные выше работы касаются генерации позитивных тестов для синтаксического анализатора (т.е. тестов, являющихся предложениями целевого языка). В настоящее время работы, предлагающие методы генерации негативных тестов для синтаксических анализаторов (т.е. тестов, не принадлежащих целевому языку), практически отсутствуют. Однако такие тесты также важны, поскольку пропуск неверной последовательности лексем на этапе синтаксического анализа может привести к аварийному завершению компиляции. В работе [8] высказано предположение, что если имеется генератор предложений языка из грамматики (генератор позитивных тестов для синтаксического анализатора), то для генерации негативных тестов для синтаксического анализатора можно использовать метод мутационного тестирования (mutation testing)1 (см. [6, 15]). Идея состоит в том, что в исходную грамматику вносятся изменения (мутации) для получения грамматик, описывающих близкие, но не эквивалентные исходному языки. Эти мутированные грамматики подаются на вход генератору тестов для получения потенциально негативных тестов. Общие проблемы данного подхода состоят в следующем:
  • Грамматика-мутант может оказаться эквивалентной исходной грамматике.


    Такие мутанты должны быть выявлены и не должны использоваться для генерации тестов.
  • Даже если грамматика-мутант не эквивалентна исходной, полученные из нее тесты могут оказаться правильными. Выявить эти тесты можно лишь прогнав их через эталонный синтаксический анализатор, которого может и не быть (например, в случае создания нового или расширения существующего языка).
  • В настоящей работе описаны критерии покрытия, нацеленные на алгоритмы синтаксического анализа. Такой подход представляется оправданным, поскольку тестовые наборы строятся для тестирования синтаксических анализаторов, и эффективность тестового набора должна оцениваться исходя из характеристик, относящихся к тестируемым компонентам (т.е. синтаксическим анализаторам), таких как, например, покрытие функциональности или кода. Данная методика разработана в рамках общего модельного подхода к тестированию компиляторов (см. [2, 3, 4]). Мы рассматриваем известные алгоритмы синтаксического анализа в качестве алгоритмов, моделирующих поведение синтаксического анализатора. Как уже говорилось, в литературе практически отсутствуют работы, посвященные генерации негативных тестов. Настоящая работа призвана закрыть этот пробел. Статья состоит из введения и трех разделов. В первом разделе содержатся сведения из теории алгоритмов синтаксического анализа. Второй раздел посвящен описанию предлагаемой методики. В нем вводятся понятия позитивных и негативных тестов, описываются критерии покрытия для тестовых наборов, опирающиеся на алгоритмы синтаксического анализа, а также приводятся алгоритмы построения наборов, удовлетворяющих этим критериям покрытия. В третьем разделе описаны результаты практического применения методики.

    Тестирование софта - статьи

    Анализаторы и метрики

    Измерение тестового покрытия производится с помощью соответствующих анализаторов, которые делятся на две группы: инструментирующие исходный код и "бинарники". Принцип действия в обоих случаях состоит во вставке в код приложения вызовов специальных функций между инструкциями. Затем, во время работы приложения, подсчитывается количество вызванных функций, отношение которых к их общему числу и составляет коэффициент тестового покрытия. Библиотеки для генерации mock-объектов позволяют автоматически создавать mock-объекты по заданному интерфейсу, а также определить ожидаемый порядок вызова и параметры функций mock-объектов.
    Средства мутационного тестирования или вставки дефектов позволяют оценить качество тестов. Они работают с помощью инструментирования кода, модифицируя его таким образом, чтобы тесты заканчивались неудачно. Широкой практики применения таких средств у нас, к сожалению, пока не наработано.
    Как и любая другая методология, TDD достаточно непросто встраивается в старые проекты. Технические и человеческие проблемы внедрения в общих чертах уже рассматривались; кроме того, существует ряд организационных моментов, о которых также хотелось бы упомянуть.
    Прежде всего, для успешного внедрения необходима ясная, выраженная в цифрах задача. В случае TDD, как правило, задача сводится к достижению определенного коэффициента покрытия кода, что, в общем, удобно. Важно обеспечить доступность метрик, для этого хорошо использовать, например, e-mail reporting. Другой неплохо зарекомендовавший себя вариант - настенный график, отображающий колебания в coverage. Необходимым условием успешного применения метрик является регулярность их сбора. В идеале метрики должны собираться автоматически. Сбор метрик должен быть максимально прост, чтобы каждый программист мог самостоятельно оценивать результаты своей работы.
    Чтобы тесты воспринимались всерьез, нужно делать их запуск частью стандартной процедуры сборки "билдов". Если тесты на собранном "билде" проходят неудачно, имеет смысл останавливать "билд".

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

    В этом случае тесты пишутся только тогда, когда затрагивается какой-либо код: во-первых, при написании нового кода; во-вторых, при рефакторинге; в-третьих, во время bug-fixing. Неплохо зарекомендовал себя подход, при котором весь новый код должен иметь coverage значительно больший, чем общепроектная норма, вплоть до 100%.

    В заключение хочется подчеркнуть, что главный положительный эффект от TDD состоит в том, что этот стиль позволяет нам быть значительно более уверенными в своем коде. Как сказал Кент Бек, TDD - это способ управления страхом в программировании. Речь, конечно, не о том страхе, который порождается плохим владением предметной областью или инструментарием, здесь тесты не спасут. Но при решении сложных и очень сложных проблем модульное тестирование - один из лучших способов проявить разумную осторожность.

    Модульное тестирование и Test-Driven Development, или Как управлять страхом в программировании

    Сергей Белов,
    менеджер проекта компании StarSoft Development Labs
    , #21/2005

    Модульное тестирование имеет довольно длинную по компьютерным меркам историю. Впервые о нем заговорили в 1975 году (именно тогда оно упоминается в знаменитом "Мифическом человеко-месяце" Брукса), затем в 1979-м его подробно описал в книге "The Art of Software Testing" Гленфорд Майерс. А через 12 лет, в 1987-м, IEEE приняла специальный стандарт модульного тестирования ПО. Тем не менее наблюдаемый в последние годы рост популярности модульных тестов связан почти исключительно с распространением так называемых "легких" методологий, и особенно с экстремальным программированием. Начало этой тенденции положил Кент Бек своей книгой "Extreme Programming Explained", увидевшей свет в 1999 году, где, помимо прочего, были сформулированы основные идеи Test-Driven Development (TDD). Главная мысль автора очень проста: если тестирование - это хорошо, значит, программисты должны постоянно тестировать свой код. Набор рекомендаций, позволяющих добиться этой цели на практике, и составляет сегодня ядро TDD. Существует несколько распространенных определений TDD. Каждое из них акцентирует внимание на определенной стороне вопроса. В первом приближении удобно считать, что TDD - это методика разработки, позволяющая оптимизировать использование модульных тестов. Хочется подчеркнуть, что речь идет именно об оптимальном, а не максимальном применении. Задача, которую преследует TDD, - достижение баланса между усилиями и результатом.

    Пять причин

    В классическом XP тесты принято считать основным средством документирования кода, своего рода исполняемой спецификацией. Даже в том случае, если эта точка зрения кажется чересчур экстремальной, тесты можно применять по меньшей мере в качестве примеров использования кода. В отличие от настоящей документации тесты не могут не быть актуальными. Закономерный вопрос: если модульное тестирование в рамках TDD позволяет добиваться таких замечательных результатов, почему оно не было широко распространено до этого? Известно пять основных причин, которыми люди объясняют свое нежелание тестировать. Все эти причины касаются модульного тестирования вообще, вне зависимости от методологий, и мы в XP с ними по-прежнему сталкиваемся. Разница между традиционным и экстремальным подходами состоит в том, что TDD предлагает разумные ответы для каждой из этих причин.
    Начнем с самого простого аргумента типа "тестировать - не моя работа" или "тестеры и так все найдут". Он, как правило, выдвигается людьми, полагающими, что единственная задача модульного тестирования - снижение количества "багов". Возражения подобного рода обычно снимаются, когда удается объяснить, что TDD прежде всего помогает программистам, а не тестерам. Как только возражающая сторона осознает возможности модульного тестирования как инструмента разработки, такой аргумент, что называется, теряет силу.
    Во-вторых, среди программистов бытует мнение, что тестировать - скучно. В рамках традиционного подхода, когда тесты пишутся после кода, для уже работающих модулей, тестировать действительно неинтересно. Нет ощущения, что создаешь что-то новое и полезное. Но в случае с "Test First" ситуация изменяется в противоположную сторону, и написание тестов превращается в дизайн методов, а дизайн - одна из самых увлекательных практик программирования. Кроме того, тесты дают возможность сразу ощутить положительный результат своего труда: цель находится не за горизонтом, а рядом и достижима очень быстро.
    Программисты, постоянно практикующие "Test First", на скуку не жалуются.

    Не менее часто приходится слышать что-нибудь вроде "модульное тестирование - это здорово, но времени на него у нас нет". Что ж, как уже говорилось, тестирование действительно увеличивает затраты времени на кодирование (по разным оценкам, на величину от 15 до 100%). С другой стороны, тестирование радикально сокращает затраты времени на:

  • отладку;
  • рефакторинг;
  • общение между программистами и тестерами по поводу "багов" и другие последствия попадания ошибок в очередной build.
  • Соотношение, как правило, складывается в пользу тестирования. Кроме того, необходимо учитывать, что кодирование тестов - это хорошо прогнозируемая по времени величина, в отличие от отладки или общения, оценить которые в лучшем случае сложно, а зачастую и невозможно. Таким образом, использование модульных тестов не только сокращает в общем и целом сроки разработки, но и делает их более предсказуемыми.

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

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

    Наконец, программисты порой не считают нужным тестировать, утверждая, что код, который они написали, работает и так. Иногда такая позиция оправдывается. Тем не менее необходимо помнить, что модульное тестирование надежно выявляет некоторые классы ошибок. Если код не тестировался, никто не может дать гарантии, что эти ошибки в нем отсутствуют.Кент Бек выражает эту же мысль более резко: единственное безопасное предположение, которое можно сделать относительно кода, который не тестировался, - это то, что он не работает.

    Практические рекомендации

    Причины, позволяющие программистам оправдать отсутствие тестов, сами по себе с введением TDD никуда не исчезают. Но TDD предоставляет простые средства, позволяющие почти полностью нейтрализовать их негативное влияние. Перейдем к практическим рекомендациям. Несмотря на то что концепция модульного тестирования относительно проста, использование TDD в реальных проектах требует от программистов, и особенно от "техлидов", определенных навыков. Прежде всего, для успешного применения TDD необходимо умение собирать и интерпретировать некоторые стандартные тестировочные метрики. В XP-группе для оценки качества тестирования применяются:
  • коэффициент покрытия кода (code coverage);
  • количество тестов;
  • количество asserts;
  • количество строк кода в модульных тестах;
  • суммарное время исполнения тестов.
  • Наиболее важный показатель - коэффициент тестового покрытия, или code coverage, измеряемый как отношение числа инструкций, выполненных тестами, к общему числу инструкций в модуле или приложении. Общий coverage приложения является основным средством оценки полноты модульного тестирования, и в нашем случае даже существует соглашение с заказчиком относительно его минимально допустимого уровня. Как правило, удовлетворительным считается coverage не ниже 75% или более, в зависимости от конкретного приложения. 100% сoverage не является чем-то из ряда вон выходящим и достаточно легко достигается при использовании "Test First". Использовать сoverage для оценки состояния модульных тестов следует осторожно. Эта метрика скорее позволяет выявить проблемы, чем указать на их отсутствие. "Плохие" значения coverage четко сигнализируют о том, что тестов в приложении недостаточно, в то время как "хорошие" значения не позволяют сделать обратного вывода. Проблема в том, что полнота тестов никак не связана с их корректностью. За рамками coverage остается также важный вопрос о диапазонах параметров функций. Тем не менее coverage удобно применять, с одной стороны, для общего наблюдения за тестированием в проекте, а с другой - для выявления не покрытых тестами участков кода.

    Три других показателя обычно имеет смысл рассматривать вместе. В отличие от coverage, количество тестов, asserts и строк кода интереснее всего наблюдать в динамике. При нормальном использовании TDD все три значения должны расти ежедневно и равномерно, причины резких изменений необходимо выявлять.

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

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

    В заключение разговора о метриках отмечу, что в XP-группе хорошо зарекомендовала себя практика их ежедневного автоматизированного сбора с рассылкой report'а команде. Анализом результатов, как правило, занимаются "PM" и "техлид". Метрики, несмотря на удобство работы с ними, в большинстве случаев не позволяют оценить тесты по целому ряду важных неформальных критериев. Поэтому существует набор требований к тестам, отслеживаемых, как правило, на code review. К ним относятся:

  • Простота. Тесты кроме всего остального должны объяснять код, который они используют, и делать это максимально прозрачно для постороннего человека образом.
  • Правильное именование тестов. Существуют разные соглашения, но в любом случае название должно адекватно отражать суть теста.
  • Не допускается зависимость тестов друг от друга или от порядка вызова.


    Выполнение этого правила позволяет отлаживать тесты произвольными группами.
  • Тесты должны быть атомарными, каждый из них должен проверять ровно один тестовый случай. Громоздкие и сложные тесты необходимо разбивать на несколько более мелких.
  • Разумеется, к тестам применяются те же требования стандартов кодирования, что и к основному коду. Один из главных вопросов в модульном тестировании - что нужно тестировать и в каком объеме? Классический ответ TDD: тестировать нужно все, что потенциально может не работать. К сожалению, руководствоваться такой расплывчатой формулировкой в реальном проекте бывает не всегда просто. Наиболее практичный критерий - тестовое покрытие. Тестов должно быть написано как минимум столько, чтобы coverage находился в пределах нормы.

    В соответствии с идеями TDD в большинстве случаев, кроме тестов, необходимых для обеспечения coverage, программисты пишут дополнительные тесты на ситуации, которые они по каким-либо причинам хотят проверить дополнительно. Такая практика приветствуется. Модульное тестирование, как уже отмечалось, не является "серебряной пулей" и может использоваться далеко не всегда. Так, например:

  • Обычно в модульных тестах не проверяется performance. С технической точки зрения, это можно делать, и некоторые тестировочные среды даже предоставляют для этого специальные средства. Но требования по производительности обычно указываются для приложения в целом, а не для отдельных функций; кроме того, performance testing часто занимает большое количество времени.
  • Как правило, не имеет смысла тестировать чужой код и автоматически сгенерированный код.
  • На уровне модульного тестирования часто тяжело или невозможно проверять сложные функциональные требования к приложению. Этим у нас занимаются тестеры с помощью автоматизации или ручного тестирования.
  • Иногда в силу различных причин принимается решение вообще не тестировать ту или иную функциональность. Так, в XP-группе в настоящее время модульные тесты не пишутся для сборок, имплементирующих GUI, хотя технически это вполне возможно.


    Модульное тестирование - это специфическая область программирования. Чтобы получить общее представление о его особенностях, рассмотрим некоторые паттерны, применяющиеся в программировании тестов.

    Необходимость запускать тесты отдельными наборами заставляет использовать механизмы структурирования тестов. В зависимости от конкретной среды, тесты организуют либо в иерархические наборы (Boost Test Library), либо в пространства имен (NUnit). Многие frameworks, кроме того, предоставляют возможность задать категорию теста, это удобно для того, чтобы запускать тесты из разных веток одновременно. Правила категоризации тестов имеет смысл определять в стандартах кодирования.

    Важнейшим паттерном модульного тестирования, поддерживаемым всеми развитыми frameworks, являются SetUp/TearDown-методы, предоставляющие возможность выполнения кода перед и после запуска теста или набора тестов. Как правило, существуют отдельные методы SetUp/TearDown уровня тестов и test suites. Важной и очень удобной возможностью являются SetUp/TearDown-методы для всех тестов (уровень сборки в терминах mbUnit).

    Основная задача SetUp/TearDown - как правило, создание тестовых наборов данных. При тестировании кода, работающего с базами данных на запись, в этих методах производится backup и восстановление базы либо создаются и откатываются транзакции.

    Mock-объекты - тестировочный паттерн, суть которого состоит в замене объектов, используемых тестируемым кодом, на отладочные эквиваленты. Например, для тестирования кода, обрабатывающего обрыв коннекции к базе данных, вместо настоящей коннекции можно использовать специальный mock-объект, постоянно выбрасывающий нужное исключение.

    Mock-объекты удобно использовать, если:

  • заменяемый объект не обладает необходимым быстродействием;
  • заменяемый объект тяжело настраивать;
  • нужное поведение заменяемого объекта сложно смоделировать;
  • для проверки call-back-функций;
  • для тестирования GUI.
  • Существуют библиотеки для динамической генерации Mock-объектов по заданным интерфейсам.


    На практике регулярно приходится тестировать один и тот же код с разными комбинациями параметров. Row Test - это тестовая функция, принимающая несколько предопределенных наборов значений:

    [RowTest] [Row("Monday", 1)] ... [Row("Saturday", 7)] public void TestGetDayOfWeekName(string result, int arg) { Assert.AreEqual(result, Converter.GetDayOfWeekName(arg)); } Combinatorial Test - тестовая функция, проверяющая код на всех возможных комбинациях для одного или нескольких массивов значений:

    [Factory] public static int[] Numbers() { int[] result = { 1, ..., 9 }; return result; } [CombinatorialTest ] public void TestMultiplicationTable ( [UsingFactories("Numbers") int lhs, [UsingFactories("Numbers") int rhs) { Assert.AreEqual(lhs * rhs, Foo.Multiply(lhs, rhs)); } Прямая поддержка комбинаторного и строчного тестирования во framework серьезно облегчает тестирование чувствительного к параметрам кода. Хорошим примером такого framework является mbUnit.

    Инструментарий модульного тестирования богат и разнообразен. Разница в ощущениях между использованием правильного и неправильного средства также будет большой. Не вдаваясь в описание конкретных продуктов, рассмотрим основные виды ПО, применяемого для модульного тестирования.

    Главный инструмент модульного тестирования, конечно, unit test framework. Большинство современных framework базируются на дизайне, предложенном Беком в 1994 году в статье "Simple Smalltalk Testing". Задача framework - предоставлять библиотеки для создания тестов и средства их запуска. При выборе framework, с технической точки зрения, наиболее важно учитывать наличие необходимых клиентов (командная строка, GUI, модули для запуска из-под NAnt/Ant или IDE), поддержку используемых паттернов тестирования и reporting.

    Поддержка тестирования из IDE в идеале должна включать в себя средства для запуска тестов по одному и группами с разной гранулярностью, под отладчиком и без него. Полезной является возможность измерения coverage для классов и сборок.Для применения "Test First" удобны средства генерации пустых определений для еще ненаписанных методов.

    Три цвета

    С практической точки зрения, основой TDD является цикл "red/green/refactor". В первой фазе программист пишет тест, во второй - код, необходимый для того, чтобы тест работал, в третьей, при необходимости, производится рефакторинг. Последовательность фаз очень важна. В соответствии с принципом "Test First", следует писать только такой код, который абсолютно необходим, чтобы тесты выполнялись успешно.
    Три цвета
    Попробуем проиллюстрировать этот цикл простейшим примером. Допустим, нам необходим метод, преобразующий числа от 1 до 7 в названия соответствующих дней недели. Следуя принципу "Test First", вначале пишем тест для этого метода:
    [Test, ExpectedException(typeof(ArgumentException))] public void TestGetDayOfWeekNameInvalidArgument() { Converter.GetDayOfWeekName(8); } Затем, создаем метод-заглушку на тестируемом классе, необходимую для того, чтобы проект собрался:
    static public string GetDayOfWeekName(int dayNumber) { return string.Empty; } Заглушку следует писать так, чтобы тест не выполнялся - это поможет удостовериться, что тест правильно реагирует на ошибку. Другая, более важная задача "красной" фазы состоит в том, чтобы в процессе написания теста определить сигнатуры тестируемых методов, порядок их вызова и другие особенности работы с ними. Как правило, имеет смысл начинать тестирование метода с наиболее общей, дефолтной ситуации, которая в нашем случае заключается в том, что в метод подаются числа, выходящие за пределы заданного диапазона. Поэтому мы пишем тест, который закончится удачно только в том случае, если тестируемый метод выбросит исключение заданного типа.
    В "зеленой" фазе мы имплементируем и отлаживаем наш метод. При наличии альтернатив имеет смысл использовать самую простую имплементацию. Если возникает необходимость написать код, который не проверяется тестом, необходимо вернуться в красную фазу и исправить тесты.
    static public string GetDayOfWeekName(int dayNumber) { throw new ArgumentException(); } Добившись успешного выполнения всех написанных тестов, просматриваем код в поисках потенциально полезных рефакторингов.
    Чаще всего в этой фазе приходится убирать дублируемый код и изменять имена переменных, методов и классов. Иногда в фазе рефакторинга не нужно делать ничего, кроме повторного просмотра написанного кода. В данном случае мы имеем дело именно с такой ситуацией.

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

    [Test] public void TestGetDayOfWeekName() { Assert.AreEqual("Monday", Converter.GetDayOfWeekName(1)); ... Assert.AreEqual("Saturday", Converter.GetDayOfWeekName(7)); } В "зеленой" фазе мы снова заставляем тест работать.

    static public string GetDayOfWeekName(int dayNumber) { switch (dayNumber) { case 1: return "Monday"; ... case 7: return "Saturday"; } throw new ArgumentException(); } В заключительной фазе цикла иногда приходится полностью менять имплементацию. В частности, в нашем случае хотелось бы снизить цикломатическую сложность кода за счет замены switch'а на хэш-таблицу. Запуск тестов после рефакторинга докажет, что ничего не было сломано.

    static public string GetDayOfWeekName(int dayNumber) { Hashtable ht = new Hashtable(); ht[1] = "Monday"; ht[7] = "Saturday"; string result = ht[dayNumber] as string; if (result == null) throw new ArgumentException(); return result; } Как правило, цикл "red - green - refactor" должен занимать от 5 до 20 минут, хотя исключения, конечно, встречаются. Не следует работать в нескольких фазах одновременно: рефакторить код, например, в "красной" фазе неразумно. Чем больше тестов отлаживается в одном цикле, тем хуже; в идеале нужно работать только с одним тестом одновременно.

    Обычно программистам, впервые сталкивающимся с "Test First", этот стиль кажется значительно более трудоемким.


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

    Ранее считалось, что модульное тестирование имеет только одну цель - уменьшение количества "багов". XP значительно усиливает роль тестирования и выделяет пять его основных функций:

  • Традиционная: уменьшение количества "багов".
  • Поддержка низкоуровневого дизайна.
  • Поддержка рефакторинга.
  • Поддержка отладки.
  • Наконец, тест помогает документировать код.
  • Значение традиционной роли модульного тестирования в последнее время в TDD-сообществе обычно не подчеркивается. Часто можно встретить утверждения, что смысл TDD вообще не в тестировании и снижение количества ошибок - просто приятный побочный эффект. Связано это с тем, что, во-первых, этим эффектом значение тестирования действительно не исчерпывается, а во-вторых, в реальных программах никакое количество модульных тестов не в состоянии гарантировать полное отсутствие ошибок.

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

    Модульное тестирование, при некотором навыке, играет важную роль в качестве средства поддержки дизайна. Чтобы написать тест, необходимо детально продумать интерфейс проектируемого класса и протокол его использования. Что еще более важно, тестирование в рамках TDD позволяет получить простой способ оценки полноты интерфейсов: необходимым и достаточным считается такой интерфейс, который позволяет выполнить все написанные тесты. Все, что находится за этими рамками, считается ненужным. Наконец, использование тестов в качестве инструмента дизайна заставляет программиста в первую очередь концентрироваться на интерфейсе, а уже во вторую - на имплементации, что также положительно влияет на результат.


    Значение тестов для рефакторинга переоценить невозможно. Любой, даже самый небольшой рефакторинг, как известно, требует наличия написанных тестов. Эта мысль важна настолько, что Мартин Фаулер в своей книге "Refactoring" посвятил модульному тестированию целую главу. Конечно, некоторые виды рефакторингов, такие, как, например, переименование полей, вполне возможно применять и без тестов. Но набор модульных тестов, покрывающих большую часть приложения, позволяет модифицировать систему значительно более агрессивно и со значительно более предсказуемыми результатами. Именно сочетание рефакторинга и тестов позволяет XP-программистам быстро изменять систему в любом нужном заказчику направлении. И именно поэтому мы можем позволить себе не искать гибких решений, а использовать самые простые (цена изменений при наличии тестов не слишком высока).

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

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

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

    Тестирование софта - статьи

    Чем могут помочь модели

    В голове разработчика и тестировщика всегда присутствует та или иная «модель» устройства программы, а также «модель» ее желаемого поведения, исходя из которой, в частности, составляются списки проверяемых свойств и создаются соответствующие тестовые примеры. (Заметим, что это разные модели; первые часто называют архитектурными, а вторые — функциональными или поведенческими.) Они зачастую составляются на основе документов или обсуждений в неформальном виде.
    Разработка моделей и спецификаций связана с «математизацией» программирования. Попытки использовать различные математические подходы для конструирования и даже генерации программ предпринимались с первых лет возникновения компьютеров. Относительный успех был достигнут в теории компиляторов, реляционных баз данных и в нескольких узкоспециальных областях; серьезных результатов в большинстве практических областей достичь не удалось. Многие стали относиться к формальным методам в программировании скептически.
    Новый всплеск интереса к формальным методам произошел в первой половине 90-х. Его вызвали первые результаты, полученные при использовании формальных моделей и формальных спецификаций в тестировании.
    Преимущества тестирования на основе моделей виделись в том, что:
  • тесты на основе спецификации функциональных требований более эффективны, так как они в большей степени нацелены на проверку функциональности, чем тесты, построенные только на знании реализации;
  • на основе формальных спецификаций можно создавать самопроверяющие (self-checking) тесты, так как из формальных спецификаций часто можно извлечь критерии проверки результатов целевой системы. Однако не было ясности в отношении качества подобных тестов. Модели обычно проще реализации, поэтому можно было предположить, что тесты, хорошо «покрывающие» модель, слишком бедны для покрытия реальных систем. Требовались широкие эксперименты в реальных проектах.
    Модель — некоторое отражение структуры и поведения системы. Модель может описываться в терминах состояния системы, входных воздействий на нее, конечных состояний, потоков данных и потоков управления, возвращаемых системой результатов и т.д.
    Для отражения разных аспектов системы применяются и различные наборы терминов. Формальная спецификация представляет собой законченное описание модели системы и требований к ее поведению в терминах того или иного формального метода. Для описания характеристик системы можно воспользоваться несколькими моделями в рамках нескольких формализмов. Обычно, чем более общей является нотация моделирования, тем больше трудностей возникает при автоматизации тестирования программы на основе модели/спецификации, описанной в этой нотации. Одни нотации и языки больше ориентированы на доступность и прозрачность описания, другие — на последующий анализ и трансляцию, в частности, трансляцию спецификации в тест. Предпринимались попытки разработки языка формальных спецификаций, удовлетворяющего требованиям промышленного использования (например, методология RAISE), однако широкого применения они не нашли.

    Имеется несколько ставших уже классическими нотаций формальных спецификаций: VDM, Z, B, CCS, LOTOS и др. Некоторые из них, например, VDM, используются преимущественно для быстрого прототипирования. Язык B удобен для анализа, в частности для аналитической верификации моделей. Все эти языки активно используются в рамках университетских программ. В реальной практике для описания архитектурных моделей используется UML, а для построения поведенческих моделей — языки SDL/MSC, исполнимые диаграммы UML и близкие к ним нотации.

    Перечисленные языки и нотации для поведенческих моделей, к сожалению, не обладают достаточной общностью. Они хорошо себя зарекомендовали в телекоммуникационных приложениях и практически бесполезны для описания функциональности программных систем «общего вида»: операционных систем, компиляторов, СУБД и т.д.

    На роль инструментов разработки тестов для подобных систем претендует новое поколение средств описания моделей/спецификаций и средства генерации тестов на проверку согласованности поведения реализации заданной модели.

    Инструменты тестирования на основе моделей

    Test Real Time — один из первых представителей этой группы. Более широкие возможности предоставляет Jtest компании Parasoft. Интересен инструментарий компании Comformiq. Семейство инструментов разработки тестов на основе моделей предлагает Институт системного программирования РАН в кооперации с компанией ATS. Поскольку семейство UniTesK авторам знакомо существенно ближе, мы изложим общую схему подхода тестирования на основе моделей на примерах из UniTesK.
    Инструменты тестирования на основе моделей
    Рис. 1. Фазы процесса разработки спецификаций и тестов
    Общая схема процесса разработки спецификаций и тестов состоит из четырех фаз (рис. 1).
    Первая фаза относительно коротка, но в реальных проектах она важна. Именно здесь закладывается уровень абстрактности модели. Модель должна быть максимально простой: это позволит требовать исчерпывающего набора тестов. В то же время, модель должна быть содержательной, раскрывать специфику тестируемой реализации. Таким образом, задача первой фазы — найти компромисс между абстрактностью и детальностью.
    Задача второй фазы — описание требований к поведению системы. Многие подходы (например, SDL) предлагают описывать исполнимые модели, которые можно рассматривать как прототипы будущей реализации. Задание требований в таком случае определяется формулой «реализация должна вести себя так же, как модель». Подход понятен, но, к сожалению, во многих реальных ситуациях он не работает. Допустим, в заголовке некоего сообщения, построенного моделью, указано одно время, а в аналогичном заголовке от реализации — несколько другое. Это ошибка или нет? Еще один пример. Модель системы управления памятью сгенерировала указатель на свободный участок памяти, а реальная система выдала другой указатель: модель и система работают в разных адресных пространствах. Ошибка ли это?

    При записи скрипта можно делать

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

    Впрочем, возможности данного вида тестирования ограничены:

  • запись скриптов возможна только при наличии прототипа будущего графического интерфейса;
  • поддержка скриптов очень трудоемка; часто скрипт легче записать заново, чем отредактировать;
  • как следствие, проводить работы по созданию тестов параллельно с разработкой самой системы не эффективно, а до создания прототипа вообще невозможно. Следующий класс инструментов — инструменты тестирования компонентов. Примером является Test Architect (IBM/Rational). Такие инструменты помогают организовать тестирование приложений, построенных по одной из компонентных технологий (например, EJB). Предусматривается набор шаблонов для создания различных компонентов тестовой программы, в частности, тестов для модулей, сценариев, заглушек.

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

    Последний из рассматриваемых здесь классов инструментов — инструменты тестирования модулей. Примером может служить Test RealTime (IBM/Rational), предназначенный для тестирования модулей на C++. Важной составляющей этого инструмента является механизм проверочных «утверждений» (assertion). При помощи утверждений можно сформулировать требования к входным и выходным данным функций/методов классов в форме логических условий, в аналогичной форме можно задавать инвариантные требования к данным объектов.Это существенный шаг вперед по сравнению с Test Architect. Аппарат утверждений позволяет систематическим образом представлять функциональные требования и на базе этих требований строить критерии тестового покрытия (правда, Test RealTime автоматизированной поддержки анализа покрытия не предоставляет).

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

    Решение перечисленных проблем предлагает новое поколение инструментов, которые следуют подходу тестирования на основе модели (model based testing) или на основе спецификаций (specification based testing).

    Инструменты тестирования — реальная практика

    Закончив экскурс в методику, вернемся к вопросу, какие инструменты тестирования используются в настоящее время и насколько они соответствуют новым представлениям о месте тестирования в процессе разработки программ.
    На данный момент в наибольшей мере автоматизированы следующие этапы работ: исполнение тестов, сбор полученных данных, анализ тестового покрытия (для модульного тестирования обычно собирают информацию о покрытых операторах и о покрытых логических ветвях), отслеживание статуса обработки запросов на исправление ошибок.
    Обзор инструментов тестирования будем вести в обратном порядке — от системного тестирования к модульному.
    Широко распространены инструменты тестирования приложений с графическим пользовательским интерфейсом. Их часто называют инструментами функционального тестирования. Если уровень ответственности приложения не велик, то таким тестированием можно ограничиться; подобное тестирование наиболее дешево.
    В данном виде тестирования широко применяются инструменты записи-воспроизведения (record/playback); из наиболее известных продуктов можно назвать Rational Robot (компания IBM/Rational), WinRunner (Mercury Interactive), QARun (Compuware). Наряду с этим существуют инструменты для текстовых терминальных интерфейсов, например, QAHiperstation компании Compuware.
    Для системного нагрузочного тестирования Web-приложений и других распределенных систем широко используется инструментарий LoadRunner от Mercury Interactive; он не нацелен на генерацию изощренных сценариев тестирования, зато дает богатый материал для анализа производительности, поиска узких мест, сказывающихся на производительности распределенной системы.
    Примерная общая схема использования инструментов записи-воспроизведения такова:
  • придумать сценарий (желательно, на основе систематического анализа требований);
  • провести сеанс работы в соответствии с данным сценарием; инструмент запишет всю входную информацию, исходившую от пользователя (нажатия клавиш на клавиатуре, движения мыши и проч.), и сгенерирует соответствующий скрипт. Полученный скрипт можно многократно запускать, внося в него при необходимости небольшие изменения.

    Новое качество, которое обещают новые инструменты

    Как отмечалось выше, создатели инструментов тестирования обычно сталкиваются со следующими проблемами:
  • отсутствие или нечеткость определения критериев тестового покрытия, отсутствие прямой связи с функциональными требованиями;
  • отсутствие поддержки повторного использования тестов;
  • отсутствие автоматической генерации собственно теста (это касается как входных воздействий, так и эталонных результатов или автоматических анализаторов корректности реализации). Имеются ли у инструментов тестирования, которые для генерации теста используют модель или формальную спецификацию целевой системы, принципиальные преимущества перед традиционными средствами? Чтобы ответить на этот вопрос, укажем, как отмеченные проблемы решаются для инструментов, использующих модели.
    Критерии тестового покрытия. Основной критерий — проверка всех утверждений, в частности, утверждений, определяющих постусловия процедур или методов. Он легко проверяется и легко связывается с функциональными требованиями к целевой системе. Так, инструменты UniTesK, инструменты для платформ Java и C# предоставляют четыре уровня вложенных критериев.
    Повторное использование тестов. Уровень повторного использования существенно выше, чем у традиционных инструментов. Разработчик тестов пишет не тестовый скрипт, а критерии проверки утверждения и тестовый сценарий. И то, и другое лишено многих реализационных деталей, и поэтому их проще переиспользовать для новой версии целевой системы или для адаптации спецификаций и тестов для сходного проекта. Например, статистика UniTesK показывает, что уровень переиспользования для тестирования ядер разных операционных систем превышает 50%.
    Автоматическая генерация тестов. Это главное достоинство новых инструментов; здесь они существенно опережают традиционные средства, поскольку используют не произвольные виды нотаций и методов моделирования и спецификации, а именно те, которые дают преимущества при автоматической генерации тестов. Так, утверждения позволяют сгенерировать тестовые «оракулы» — программы для автоматического анализа корректности результата; различные виды конечных автоматов или их аналоги позволяют сгенерировать тестовые последовательности.
    К тому же, поскольку модели обычно проще, чем реализации, для них удается провести более тщательный анализ, поэтому набор тестов становится более систематическим.

    Рассмотренные инструменты опробованы на реальных, масштабных проектах. Конечно, каждый проект несет в себе некоторую специфику, возможно, препятствующую исчерпывающему тестированию. Однако опыт использования данных инструментов показывает, что обычно удается достичь хороших результатов, лучших, чем результаты, полученные в аналогичных проектах при помощи ручного тестирования. Пользователи UniTesK, обычно, за приемлемый уровень качества принимают 70-80% покрытия кода целевой системы; при этом должен быть удовлетворен, как минимум, критерий покрытия всех логических ветвей в постусловиях. Для некоторых сложных программ (в том числе, для блока оптимизации компилятора GCC) был достигнут уровень покрытия 90-95%.

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

    Литература
  • Уокер Ройс. Управление проектами по созданию программного обеспечения. М.: Лори, 2002.
  • Г. Майерс. Надежность программного обеспечения. М.: Мир, 1980.
  • Элфрид Дастин, Джефф Рэшка, Джон Пол. Автоматизированное тестирование программного обеспечения. Внедрение, управление и эксплуатация. М.: Лори, 2003.
  • Everette R. Keith. Agile Software Development Processes: A Different Approach to Software Design, www.cs.nyu.edu/courses/spring03/V22.0474-001/lectures/agile/AgileDevelopmentDifferentApproach.pdf. Александр Петренко, Елена Бритвина, Сергей Грошев, Александр Монахов, Ольга Петренко ({petrenko, lena, sgroshev, monakhov, olga} @ ispras.ru) — сотрудники Института системного программирования РАН.


    Рис. 2. Спецификационные расширения языков программирования на примере спецификации требований к методу sqrt, вычисляющему квадратный корень из своего аргумента Обозначения элементов общей структуры спецификации метода:
    S — Сигнатура операции
    A — Спецификация доступа
    < — Предусловие
    B — Определение ветвей функциональности
    > — Постусловие

    Java: Class SqrtSpecification { S Specification static double sqrt(double x) A reads x, epsilon { < pre { return x >= 0; } post { > if(x == 0) { B branch «Zero argument»; > return sqrt == 0; > } else { B branch «Positive argument»; > return sqrt >= 0 && > Math.abs((sqrt*sqrt-x)/x) } } } } Си: S specification double SQRT(double x) A reads (double)x, epsilon { < pre { return x >= 0.; } coverage ZP { if(x == 0) { B return(ZERO, «Zero argument»); } else { B return(POS, «Positive argument»); } } post { > if(coverage(ZP, ZERO)) { > return SQRT == 0.; > } else { > return SQRT >= 0. && > abs((SQRT*SQRT — x)/x) < epsilon; > } } } C#: namespace Examples { specification class SqrtSpecification { S specification static double Sqrt(double x) A reads x, epsilon { < pre { return x >= 0; } post { > if(x == 0) { B branch ZERO («Zero argument»); > return $this.Result == 0; > } else { B branch POS («Positive argument»); > return $this.Result >= 0 && > Math.Abs( ($this.Result * $this.Result — x)/x) < epsilon; > } > } > } } }

    Подходы к улучшению качества программ

    «Борьба за качество» программ может вестись двумя путями. Первый путь «прост»: собрать команду хороших программистов с опытом участия в аналогичных проектах, дать им хорошо поставленную задачу, хорошие инструменты, создать хорошие условия работы. С большой вероятностью можно ожидать, что удастся разработать программную систему с хорошим качеством.
    Второй путь не так прост, но позволяет получать качественные программные продукты и тогда, когда перечисленные условия соблюсти не удается — не хватает хороших программистов, четкости в поставке задачи и т.д. Этот путь предписывает стандартизировать процессы разработки: ввести единообразные требования к этапам работ, документации, организовать регулярные совещания, проводить инспекцию кода и проч. Одним из первых продвижений на этом фронте стало введение понятия жизненного цикла программной системы, четко определявшее необходимость рассмотрения многих задач, без решения которых нельзя рассчитывать на успех программного проекта.
    В простейшем варианте набор этапов жизненного цикла таков:
  • анализ требований;
  • проектирование (предварительное и детальное);
  • кодирование и отладка ("программирование");
  • тестирование;
  • эксплуатация и сопровождение. Стандартизованная схема жизненного цикла с четкой регламентацией необходимых работ и с перечнем соответствующей документации легла в основу так называемой «водопадной» или каскадной модели. Водопадная модель подразумевает жесткое разбиение процесса разработки программного обеспечения на этапы, причем переход с одного этапа на другой осуществляется только после того, как будут полностью завершены работы на предыдущем этапе. Каждый этап завершается выпуском полного комплекта документации, достаточной для того, чтобы разработка могла быть продолжена другой командой. Водопадная модель стала доминирующей в стандартах процессов разработки Министерства обороны США. Многие волей или неволей, даже отклоняясь от этой модели, в целом соглашались с ее разумностью и полезностью.
    Водопадная модель требовала точно и полно сформулировать все требования; изменение требований было возможно только после завершения всех работ.
    Водопадная модель не давала ответ на вопрос, что делать, когда требования меняются или меняется понимание этих требований непосредственно во время разработки.

    В конце 80-х годов была предложена так называемая спиральная модель, был развит и проверен на практике метод итеративной и инкрементальной разработки (Iterative and Incremental Development, IID). В спиральной модели были учтены проблемы водопадной модели. Главный упор в спиральной модели делается на итеративности процесса. Описаны опыты использования IID с длиной итерации всего в полдня. Каждая итерация завершается выдачей новой версии программного обеспечения. На каждой версии уточняются (и, возможно, меняются) требования к целевой системе и принимаются меры к тому, чтобы удовлетворить и новые требования. В целом Rational Unified Process (RUP) также следует этой модели.

    Позволило ли это решить проблему качества? Лишь в некоторой степени.

    Проблема повышения качества программного обеспечения в целом и повышения качества тестирования привлекает все большее внимание; в университетах вводят специальные дисциплины по тестированию и обеспечению качества, готовят узких специалистов по тестированию и инженеров по обеспечению качества. Однако по-прежнему ошибки обходятся только в США от 20 до 60 млрд. долл. ежегодно. При этом примерно 60% убытков ложится на плечи конечных пользователей. Складывается ситуация, при которой потребители вынуждены покупать заведомо бракованный товар.

    Вместе с тем, ситуация не безнадежна. Исследование, проведенное Национальным институтом стандартов и технологии США, показало, что размер убытков, связанных со сбоями в программном обеспечении, можно уменьшить примерно на треть, если вложить дополнительные усилия в инфраструктуру тестирования, в частности, в разработку инструментов тестирования.

    Каково же направление главного удара? Что предлагают «наилучшие практики»?

    В 80-е и 90-е годы ответ на этот вопрос звучал примерно так. Наиболее дорогие ошибки совершаются на первых фазах жизненного цикла — это ошибки в определении требований, выборе архитектуры, высокоуровневом проектировании.


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

    В последние годы в связи с появлением методов, которые принято обозначать эпитетом agile («шустрый», «проворный») предлагаются и внедряются новые конструктивные методы раннего обнаружения ошибок. Скажем, современные модели, такие как Microsoft Solutions Framework (MSF, www.microsoft.com/rus/msdn/msf) и eXtreme Programming (XP), выделяют следующие рекомендации к разработке тестов:

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

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


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

    Итак, различные работы в процессе производства программ должны быть хорошо интегрированы с работами по тестированию. Соответственно, инструменты тестирования должны быть хорошо интегрированы со многими другими инструментами разработки. Из крупных производителей инструментов разработки программ, первыми это поняли компании Telelogic (набор инструментов для проектирования, моделирования, реализации и тестирования телекоммуникационного ПО, базирующийся на нотациях SDL/MSC/TTCN) и Rational Software (аналогичный набор, преимущественно базирующийся на нотации UML). Следующий шаг сделала компания IBM, начав интеграцию возможностей инструментов от Rational в среду разработки программ Eclipse.

    Тезис XP — «Пиши тест перед реализацией» — хорош как лозунг, но в реальности столь же неконструктивен. Для крупных программных комплексов приходится разрабатывать тесты различного назначения: тесты модулей, интеграционные или компонентные тесты, системные тесты.

    Тестирование на основе моделей

    Александр Петренко, Елена Бритвина, Сергей Грошев, Александр Монахов, Ольга Петренко
    18.09.2003
    Тестирование на основе моделей Индустрия программного обеспечения постоянно пытается решить вопрос качества, но насколько значимы ее успехи, на данный момент сказать довольно сложно. В статье идет речь о новом поколении инструментов тестирования, которые призваны повысить качество программ. Однако инструменты, даже автоматические, не в состоянии помочь, если их используют неправильно. Поэтому обсуждение инструментов предваряет изложение общих положений «правильного» тестирования.

    Три составляющие тестирования — экскурс в теорию

    Модульному тестированию подвергаются небольшие модули (процедуры, классы и т.п.). При тестировании относительного небольшого модуля размером 100-1000 строк есть возможность проверить, если не все, то, по крайней мере, многие логические ветви в реализации, разные пути в графе зависимости данных, граничные значения параметров. В соответствии с этим строятся критерии тестового покрытия (покрыты все операторы, все логические ветви, все граничные точки и т.п.).
    Проверка корректности всех модулей, к сожалению, не гарантирует корректности функционирования системы модулей. В литературе иногда рассматривается «классическая» модель неправильной организации тестирования системы модулей, часто называемая методом «большого скачка». Суть метода состоит в том, чтобы сначала оттестировать каждый модуль в отдельности, потом объединить их в систему и протестировать систему целиком. Для крупных систем это нереально. При таком подходе будет потрачено очень много времени на локализацию ошибок, а качество тестирования останется невысоким. Альтернатива «большому скачку» — интеграционное тестирование, когда система строится поэтапно, группы модулей добавляются постепенно.
    Распространение компонентных технологий породило термин «компонентное тестирование» как частный случай интеграционного тестирования.
    Полностью реализованный программный продукт подвергается системному тестированию. На данном этапе тестировщика интересует не корректность реализации отдельных процедур и методов, а вся программа в целом, как ее видит конечный пользователь. Основой для тестов служат общие требования к программе, включая не только корректность реализации функций, но и производительность, время отклика, устойчивость к сбоям, атакам, ошибкам пользователя и т.д. Для системного и компонентного тестирования используются специфические виды критериев тестового покрытия (например, покрыты ли все типовые сценарии работы, все сценарии с нештатными ситуациями, попарные композиции сценариев и проч.).

    UniTesK — унифицированное решение

    UniTesK предлагает использовать так называемые неявные спецификации или спецификации ограничений. Они задаются в виде пред- и постусловий процедур и инвариантных ограничений на типы данных. Этот механизм не позволяет описывать в модели алгоритмы вычисления ожидаемых значений функций, а только их свойства. Скажем, в случае системы управления памятью модель будет задана булевским выражением в постусловии типа «значение указателя принадлежит области свободной памяти». Простой пример постусловия для функции «корень квадратный» приведен на риc. 2; одна и та же спецификация представлена в трех разных нотациях: в стиле языков Cи, Java и C#. Использование спецификационных расширений обычных языков программирования вместо классических языков формальных спецификаций — шаг, на который идут почти все разработчики подобных инструментов. Их различает только выразительная мощность нотаций и возможности анализа и трансляции спецификаций.
    Третья фаза — разработка тестового сценария. В простейшем случае сценарий можно написать вручную, но в данной группе инструментов — это плохой тон. Тест, т.е. последовательность вызовов операций целевой системы с соответствующими параметрами, можно сгенерировать, отталкиваясь от некоторого описания программы или структуры данных. Будем называть такое описание сценарием. Компания Conformiq предлагает описать конечный автомат. Различные состояния автомата соответствуют различным значениям переменных целевой системы, переходы — вызовам операций этой системы. Определить автомат — это значит для каждого состояния описать, в какое состояние мы перейдем из данного, если обратимся к любой наперед заданной операции с любыми наперед заданными параметрами. Если такое описание получить легко, больше ничего делать не понадобится: инструмент сгенерирует тест автоматически и представит результаты тестирования, например, в виде MSC-диаграмм. Но легко ли это, скажем, для программы с одной целочисленной переменной и двумя-тремя операциями? Скорее всего, да. Однако в общем случае сделать попросту невозможно.

    В UniTesK для генерации тестовых последовательностей конечный автомат не описывается, а генерируется по мере исполнения теста. Все, что требуется от разработчика теста, — это задание способа вычисления состояния модели на основании состояния целевой системы и способа перебора применяемых в текущем состоянии тестовых воздействий. Эти вычисления записываются в тестовых сценариях. Очередное тестовое воздействие выбирается на основании спецификации сценария в зависимости от результатов предыдущих воздействий. Такой подход обладает двумя важными преимуществами. Во-первых, это позволяет строить сложные тестовые последовательности в чрезвычайно компактной и легкой для написания и понимания форме. Во-вторых, тесты приобретают высокую гибкость: они легко могут быть параметризованы в зависимости от текущих потребностей тестирования и даже могут автоматически подстраиваться под незначительные изменения модели. На рис. 3 приведен пример сценарного метода.

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

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

    В реальных системах количество различимых состояний и количество допустимых в каждом из них тестовых воздействий очень велико, что приводит к комбинаторному «взрыву состояний».


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

    UniTesK — унифицированное решение
    Рис. 5. Использование UniTesK в среде разработки Forte 4.0
    Создатели UniTesK, полагая, что не должно быть отдельной среды для разработки тестов, не только наделили его возможностью мимикрии под различные языки программирования, но обеспечили интеграцию составляющих его инструментов в популярные средства разработки программ. На рис. 5 представлен сеанс использования UniTesK в среде разработки Forte 4.0 компании Sun Microsystems.

    Тестирование софта - статьи

    Аннотация

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

    Использование в проектах

    Количество файлов в директории; количество выделенных идентификаторов; количество выделенных семафоров; количество элементов меню; количество слушателей сообщений интерфейса (action listeners); количество элементов списка (List); размер списка ожидающих обработки операций send, receive; количество сообщений в очереди; количество синхронизированных потоков (joined threads); количество потоков, которые могут быть синхронизированы (joinable); количество отмененных (canceled), заблокированных (blocked), отсоединенных (detached) потоков.
    Размер множества свободных идентификаторов; размер множества ресурсов разделяемой памяти; размер множества выделенных буферов (allocated buffers); размер пула выделенных ресурсов; размер пула выделенных процессов; размер пула выделенных семафоров; размер множества активных RMI-объектов; размер множества идентификаторов активных RMI-объектов; размер множества дескрипторов очереди сообщений.


    Произведение количества свободных ресурсов и количества выделенных ресурсов; количество свободных элементов в каждом пуле из списка; произведение количества выделенных элементов и множества идентификаторов; произведение количества слушателей и количества уникальных слушателей; количество элементов в списке для каждого списка из набора; произведение типа упорядочивания в отображении и размера отображения; произведение зарегистрированных RMI объектов и количества активных объектов RMI; произведение количества зарегистрированных и количества пересоздаваемых при перезапуске RMI-объектов; произведение количества зарегистрированных и количества экспортированных RMI-объектов; произведение глубины дерева, количества компаний и количества активных компаний; произведение типа доступа к очереди сообщений и ее размера; произведение максимального размера очереди, максимального размера сообщения и размера очереди; произведение количества ждущих вызовов send, receive, максимального размера очереди, размера очереди и типа блокировки; произведение количества заявок, количества заявок ожидающих обработку и количества заявок находящихся в обработке.


    Паттерн использовался для деревьев, представляющих иерархию компаний, в которой дочерние компании присутствовали как в основной, так и в альтернативной иерархии. В качестве обобщенного состояния выбиралось мультимножество пар: количество дочерних компаний в основной иерархии и количество дочерних компаний в альтернативной иерархии. Паттерн использовался для тестирования модели данных Service Data Objects, представляющей собой дерево со ссылками, в котором можно хранить XML-данные, реляционные данные, EJB. В качестве обобщенного состояния выбиралось мультимножество пар: количество дочерних вершин, количество ссылок на другие вершины.

    Итерация параметров методов

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


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


    Будем считать, что интерфейс содержит методы add, delete и createRoot. У метода add имеются два параметра: вершина, к которой нужно добавить ребенка, и добавляемая вершина. Метод требует, чтобы вершина, к которой добавляется ребенок, существовала. У метода delete имеется один параметр - удаляемая вершина. Можно выделить две разновидности метода: метод удаляет только листовые вершины, метод удаляет все поддерево, корнем которого является заданная вершина. Метод createRoot создает корневую вершину; вершину можно создать, если дерево пусто. Описанные таким образом методы оказываются детерминированными, так как по заданному дереву и набору параметров любого метода однозначно определяется вид результирующего дерева. Легко видеть, что итерация вершин дерева в качестве параметров методов описывает недетерминированный автомат. На показано обобщенное состояние {0, 1, 1} и два соответствующих ему дерева, которые различаются порядком вершин. В этом обобщенном состоянии переход, соответствующий вызову метода add(C, D) , может переводить автомат как в обобщенное состояние {0, 1, 1, 1} так и в обобщенное состояние {0, 0, 1, 2} в зависимости от дерева, соответствующего исходному обобщенному состоянию. Этот недетерминизм легко преодолеть, заменив итерацию вершин итерацией различных элементов мультимножества обобщенного состояния. Элементу мультимножества при выполнении перехода ставится в соответствие произвольная вершина дерева с заданным числом непосредственных детей. Таким образом, имеем следующие переходы:
  • add(i, A) ;
  • delete(i) ;
  • createRoot(A) , где i - элемент мультимножества обобщенного состояния, А - добавляемая вершина. Переходы по методу add становятся детерминированными, так как мультимножество после перехода определяется однозначно: выбранный элемент мультимножества заменяется большим на единицу, и дополнительно к мультимножеству добавляется нуль (см. ). Однако переходы по методу delete по-прежнему оказываются недетерминированными. В примере, показанном на , в состоянии {0, 0, 1, 2} результирующее состояние при удалении вершины без детей зависит от того, сколько было детей у родителя.

    Краткое описание

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


    В качестве обобщенного состояния выбирается произведение других обобщенных состояний.


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

    Область применения

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


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


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

    Обобщенное состояние

    Используется целочисленное или натуральное состояние 0, 1, 2, … . Функция вычисления обобщенного состояния возвращает количество элементов списка. Для обеспечения конечности обобщенных состояний вводится ограничение на количество состояний, задаваемое как параметр сценария.
    Используется целочисленное или натуральное состояние 0, 1, 2, … . Функция вычисления обобщенного состояния возвращает количество элементов множества. Для обеспечения конечности обобщенных состояний вводится параметр сценария, который может задавать ограничение как на количество состояний, так и на количество разнообразных элементов, итерируемых в сценарных методах.


    Тип состояния зависит от типов элементов произведения. В общем случае можно пользоваться PairComplexGenState и ListComplexGenState, конструируемыми из пары и списка обобщенных состояний соответственно. Для произведения целочисленных состояний можно пользоваться классами обобщенных состояний IntPairGenState, IntTripleGenState, IntListGenState.


    В качестве обобщенного состояния выбирается мультимножество, элементы которого - количество непосредственных дочерних вершин. Рассмотрим пример. На рис. 7 показаны два дерева, в которых одна вершина имеет две дочерних, одна имеет одну дочернюю, и две вершины в каждом дереве не имеют дочерних вершин. Таким образом, обоим этим деревьям соответствует одно и тоже обобщенное состояние, мультимножество {0,0,1,2}. Обобщенное состояние Рис. 7. Примеры деревьев Результаты подсчета количества обобщенных состояний в зависимости от числа вершин приведены в таблице 2. Как видно, количество обобщенных состояний значительно меньше числа корневых деревьев для того же числа вершин. Это делает данное обобщенное состояние практически пригодным для использования при тестировании.

    Число вершин 1 5 10 15 20 25
    Число корневых деревьев 1 9 719 87811 12826228 2067174645
    Число обобщенных состояний 1 5 30 135 490 1575
    Таблица 2. Количество обобщенных состояний Вместе с тем, данное обобщенное состояние определяет разнообразные виды деревьев. Мультимножества вида {0, …, 0, N}, где N - количество вершин определяют широкие деревья, а мультимножества вида {0, 1, …, 1} определяют высокие деревья.

    Описание паттернов

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

    Понятие паттерна

    Паттерны представляют собой удачные решения часто встречающихся задач. Там, где использование процесса разработки тестового сценария приводит к хорошему результату, паттерны позволяют повторно использовать полученные результаты. Там, где использование процесса затруднительно, знание паттернов позволяет выделить части, к которым они применимы. Паттерны позволяют использовать инструментальную поддержку. Паттерны, описываемые в данной статье, получены на основе анализа более чем десятилетнего опыта разработки тестов ИСП РАН [] в различных проектах:
  • Nortel Networks (ядро ОС);
  • Luxoft (банковское приложение);
  • Intel (стандартная библиотека Java);
  • Microsoft Research (протокол IPv6);
  • Вымпелком (детализация по счетам);
  • НИИ системных исследований РАН (ОС 2000);
  • Persistent (Service Data Objects, реализация BEA). Было проанализировано около трехсот тестовых сценариев. В результате анализа проектов выделено десять наиболее распространенных паттернов. Названия выделенных паттернов, их краткая характеристика и статистика использования показаны в таблице 1.

    НазваниеКраткая характеристикаСтатистика использования
    Длина спискаВ качестве обобщенного состояния выбирается длина списка 13%41%
    Размер множества В качестве обобщенного состояния выбирается размер множества 17%
    Размер отображения В качестве обобщенного состояния выбирается размер отображения 8%
    Число вершин дерева В качестве обобщенного состояния выбирается число элементов дерева 3%
    Декартово произведение В качестве обобщенного состояния выбирается декартово произведение других обобщенных состояний 18% 39%
    Выделение элементов Паттерн основан на выделении элементов обладающих некоторыми свойствами 5%
    Единственное состояние В качестве обобщенного состояния выбирается одно единственное состояние 10%
    Мультимножество чисел детей В качестве обобщенного состояния выбирается мультимножество, элементами которого являются числа - количество непосредственных детей для каждой вершины дерева 3%
    Код дерева В качестве обобщенного состояния выбирается код дерева, однозначно определяющий его структуру 2%
    Среднее состояние Все промежуточные состояния объединяются в одно обобщенное состояние 1%
    ? (без паттерна) 20%
    Таблица 1. Паттерны проектирования Наиболее широкое применение имеет группа паттернов с размером структуры данных в качестве обобщенного состояния: длина списка, размер множества, размер отображения, число вершин дерева.
    Эти паттерны используются в более чем половине случаев применения паттернов. В большой части паттернов явным образом определяется обобщенное состояние; это такие паттерны, как длина списка, размер множества, размер отображения, число вершин дерева, единственное состояние, мультимножество чисел детей, код дерева, среднее состояние. Оставшиеся два паттерна декартово произведение и выделение элементов явным образом состояние не определяют, а используются совместно с другими паттернами. Паттерны покрывают большинство распространенных структур данных: списки, множества, отображения, деревья. Для объединения нескольких обобщенных состояний, соответствующих разным структурам данных, используется паттерн декартово произведение. В паттерне выделение элементов учитываются свойства элементов, так как в остальных паттернах при выборе обобщенного состояния предполагается, что тестовые ситуации от свойств элементов не зависят. Описание каждого паттерна состоит из следующих частей:
  • название;
  • краткое описание;
  • область применения;
  • обобщенное состояние;
  • итерация параметров методов;
  • примеры;
  • совместное использование;
  • использование в проектах. Название служит для краткого именования паттерна. Область применения описывает ситуации, в которых применим описываемый паттерн, а также известные расширения паттерна. В части примеров приводятся простые и наглядные примеры применения паттернов; в данной статье примеры приводятся на расширении языка Java []. В части совместного использования приводятся паттерны, с которыми можно удачно использовать описываемый паттерн. Примеры использования паттерна в проанализированных проектах описанываются в части использования в проектах.

    Метод удаляет элемент по индексу

    List modelList; // Метод добавляет элемент e в список. void add(Integer e); // Метод удаляет элемент по индексу index из списка. // Если индекс выходит за границы списка, // вырабатывается исключение IndexOutOfBoundsException. void remove(int index) throws IndexOutOfBoundsException; Тестовые ситуации для метода add:
  • список пуст;
  • список не пуст. Тестовые ситуации для метода remove:
  • индекс index отсутствует в списке;
  • индекс index есть в списке:
  • список пуст;
  • список содержит единственный элемент;
  • список содержит больше одного элемента. Обобщенное состояние - IntGenState, параметр конструктора - длина списка modelList: modelList.size(). Для ограничения количества состояний в сценарий добавляется переменная int maxSize. Для метода add с использованием конструкции iterate итерируются элементы списка - целые числа; итерация происходит, только если длина списка не превышает maxSize. Для метода remove итерируются индексы списка: scenario boolean add() { //objectUnderTest - модель, содержащая спецификационные методы //add и remove if(objectUnderTest.modelList.size() Set modelSet; // Метод добавляет элемент e в множество. void add(Integer e); // Метод удаляет элемент e из множества. // Если элемент присутствовал во множестве, возвращает true, иначе false. boolean remove(Integer e); Тестовые ситуации для метода add:
  • множество пусто;
  • множество не пусто:
  • добавляемый элемент присутствует в множестве;
  • добавляемый элемент отсутствует в множестве. Тестовые ситуации для метода remove:
  • множество пусто;
  • множество содержит единственный элемент:
  • удаляемый элемент присутствует во множестве;
  • удаляемый элемент отсутствует во множестве;
  • множество содержит более одного элемента:
  • удаляемый элемент присутствует во множестве;
  • удаляемый элемент отсутствует во множестве. Обобщенное состояние - IntGenState, параметр конструктора - размер множества modelSet: modelSet.size(). Для ограничения количества состояний в сценарий добавляется переменная int maxSize. Для методов add и remove итерируются ветви функциональности, соответствующие отсутствию или присутствию элемента во множестве. Для каждой ветви перебираются элементы множества до тех пор, пока не будет найден элемент, попадающий в выбранную ветвь функциональности. Для ограничения количества обобщенных состояний итерация для метода add происходит, только если размер множества не превышает maxSize. scenario boolean add() { //objectUnderTest - модель, содержащая спецификационные методы //add и remove if(objectUnderTest.modelSet.size()

    Пример 1. Произведение длин списков. Спецификация описывает список, такой же, как в примере для паттерна Длина списка. List modelList; // Метод добавляет элемент e в список. void add(Integer e); // Метод удаляет элемент по индексу index из списка. // Если индекс выходит за границы списка, // вырабатывается исключение IndexOutOfBoundsException. void remove(int index) throws IndexOutOfBoundsException; В сценарии заводится массив ListMediator testLists[], в котором хранятся списки, сконструированные для тестирования. Т.е. в этом массиве хранятся те же объекты, что используются для тестирования одного списка (objectUnderTest), - медиаторы списков с присоединенными оракулами. Обобщенное состояние - IntListGenState. При конструировании обобщенного состояния производится итерация по элементам массива testList и добавляется длина каждого списка modelList: modelList.size(). Так же, как и для тестирования одного списка, вводится ограничение на максимальную длину всех списков int maxSize. Для тестирования методов добавления и удаления в сценарных методах итерируются тестируемые списки, а затем параметры методов, так же, как для одного списка. scenario boolean add() { iterate(int i=0; i<=objectUnderTest.testLists.length; i++; ) { objectUnderTest = testLists[i]; //objectUnderTest - модель, содержащая спецификационные методы //add и remove if(objectUnderTest.modelList.size()

    Tree modelTree; // Метод добавляет вершину node к вершине parent, // если parent есть в дереве. // Если вершины parent нет в дереве, вершина не добавляется. add(Node parent, Node node); // Метод удаляет вершину node, если таковая есть в дереве // и является листовой. // Иначе вершина не удаляется. delete(Node node); // Метод создает корневую вершину; вершину можно создать, // если дерево пусто. Node createRoot(); Тестовые ситуации для метода add:
  • родитель не существует;
  • родитель существует:
  • родитель не имеет дочерних вершин;
  • родитель имеет дочерние вершины. Тестовые ситуации для метода delete:
  • вершина не существует;
  • вершина существует:
  • вершина не имеет дочерних вершин;
  • вершина имеет дочерние вершины. Тестовые ситуации для метода createRoot:
  • дерево пусто;
  • дерево не пусто. Обобщенное состояние - мультимножество целых чисел IntMultisetGenState. Считаем, что имеется метод, возвращающий вершину дерева по номеру от нуля до числа вершин в дереве modelTree.getNodeByIndex(int index) . IntMultisetGenState genstate = new IntMultisetGenState(); for(int i=0; i

    Процесс разработки тестового сценария

    В технологии UniTesK задание конечного автомата, на основе которого строится тестовая последовательность, вынесено в отдельный компонент - тестовый сценарий []. В тестовом сценарии задаются обобщенные состояния и итерации параметров методов. Обобщенные состояния определяют состояния конечного автомата и задаются с помощью функции, возвращающей обобщенное состояние на основе состояния модели. Таким образом, состояния модели разбиваются на классы с одинаковым обобщенным состоянием. Обобщенные состояния позволяют уменьшить количество состояний автомата. Итерации параметров методов задают перебор параметров, с которыми следует вызвать методы, определяя тем самым возможные переходы автомата. Однако при таком задании переходов состояние после выполнения метода неизвестно до вызова метода. Поэтому окончательный вид автомата определяется только в процессе обхода автомата. На основе описания обобщенного состояния и итераций параметров методов специальный компонент тестовой системы, называемый обходчиком, строит обход переходов автомата, получая тестовую последовательность. В процессе обхода, выполняя вызовы методов, обходчик определяет окончания переходов автомата. Процесс разработки тестового сценария начинается с определения тестовых ситуаций (рис. 1). Тестовая ситуация определяет множество состояний, метод, и наборы параметров при которых она может быть покрыта. Тестовая ситуация является достижимой в данном состоянии, если в этом состоянии существует такой набор параметров, что метод, вызванный с этим набором параметров в данном состоянии, покрывает эту ситуацию. Тестовые ситуации позволяют определить, какие состояния являются "интересными" для тестирования, а также судить о разнообразности обобщенных состояний. Обобщенные состояния являются разнообразными, если они позволяют покрыть выбранные тестовые ситуации. Основным источником тестовых ситуаций является покрытие, определенное в спецификациях, так как именно оно является основным критерием завершенности тестирования в технологии UniTesK.
    При определении тестовых ситуаций могут учитываться также дополнительные соображения, не нашедшие отражения при определении покрытия в спецификации. Например, если в качестве модельного состояния используются деревья, то дополнительным соображением может быть наличие разнообразных деревьев: высоких, широких, сбалансированных и т.д. Подобные свойства сложно отображать в спецификации. Другой пример дополнительного соображения - перебор значений свойства, не определяющего внешнее поведение реализации и, соответственно, не отраженнного в спецификации. Однако внутреннее поведение реализации может зависеть от этого свойства, используемого для оптимизации. Процесс разработки тестового сценария Рис. 1. Процесс разработки тестового сценария На следующем шаге процесса разработки тестового сценария происходит выбор обобщенного состояния и итераций параметров методов. Выбор обобщенного состояния является непростой задачей Это поиск компромисса между количеством состояний, их разнообразием и возможностями обходчика, использующего данное состояние для построения тестовой последовательности. Количество состояний непосредственным образом отражается на длине тестовой последовательности и времени работы тестов. Приемлемое количество состояний зависит от скорости работы системы. Опыт показывает, что для быстрых систем приемлемое количество состояний - несколько сотен, для медленных - несколько десятков. Обобщенные состояния должны быть достаточно разнообразными, чтобы обход всех переходов автомата покрывал выбранные тестовые ситуации. Для этого достаточно в качестве обобщения взять разбиение состояний на группы, в которых достижимы одинаковые наборы тестовых ситуаций. Такое разбиение обычно не приводит к большому количеству состояний, так как количество тестовых ситуаций обычно невелико. Рассмотрим пример обобщения. На рис. 2 в левой части показаны состояния, у каждого из которых нарисованы возможные переходы. Переходы, соответствующие разным тестовым ситуациям, показаны стрелками с разной штриховкой. Обобщением является объединение состояний с одинаковым набором штриховок выходящих стрелок.


    Очевидно, что обход, покрывающий все переходы автомата, покроет все возможные тестовые ситуации. Процесс разработки тестового сценария Рис. 2. Пример начального обобщения Если количество обобщенных состояний слишком велико, то приходится жертвовать разнообразием, укрупняя обобщенные состояния. Укрупнение обобщенных состояний может приводить к тому, что в двух модельных состояниях одного обобщенного состояния, достижимыми являются разные множества тестовых ситуаций. Может получиться так, что в одном модельном состоянии тестовая ситуация достижима, а в другом - нет. Однако можно надеяться, что в процессе обхода будут получены оба состояния, и все тестовые ситуации будут все равно покрыты. В ситуации, показанной на рис. 3, обобщение состояний приводит к тому, что не всякий обход автомата покроет переходы, заштрихованные вертикальными линиями, сеточкой и точками. На рисунке эти стрелочки обозначены пунктирной линией. Процесс разработки тестового сценария Рис.3. Укрупнение состояний В ряде случаев достигнуть покрытия, не меняя обобщенного состояния, удается с помощью введения дополнительных сценарных методов или путем разработки дополнительного тестового сценария, возможно, с другим обобщенным состоянием. На этом же шаге процесса разработки тестового сценария выбираются итерации параметров методов. Итерации задаются в сценарных методах тестового сценария. В общем случае одному тестируемому методу может соответствовать несколько сценарных методов, и один сценарный метод может задавать итерации сразу для нескольких методов, а также последовательность вызовов. В наиболее частом случае каждому методу соответствует один сценарный метод. Итерация параметров может быть выполнена с фильтрацией по критерию покрытия. В этом случае итерируются идентификаторы элементов покрытия (тестовых ситуаций), и для каждого элемента подбирается набор параметров, покрывающий выбранный элемент. Фильтрацию также называют обобщением переходов, так как она разбивает параметры и состояния на группы, соответствующие разным элементам тестового покрытия. Фильтрация может использоваться как для сокращения количества переходов, так и для детерминизации автомата. После начального выбора обобщенного состояния и итераций следует проверить выполнение требований обходчика.


    На данный момент в инструментах UniTesK есть пять видов обходчиков:
  • базовый обходчик;
  • обходчик детерминированных автоматов;
  • обходчик детерминированных автоматов с функцией сброса;
  • обходчик автоматов, имеющих детерминированный, сильно связный покрывающий подавтомат;
  • обходчик сильно дельта-связных автоматов. Во всех этих обходчиках требуется, чтобы число состояний и переходов было конечно. В базовом обходчике не используется обобщенное состояние; считается, что у автомата имеется одно единственное состояние, и, таким образом, не накладываются какие-либо дополнительные ограничения. В обходчике детерминированных автоматов требуется, чтобы автомат, описываемый сценарием, был детерминированным и сильно связным. В обходчике детерминированных автоматов с функцией сброса требуется детерминированность автомата, а сильная связность обеспечивается с помощью функции сброса, задаваемой разработчиком сценария. Четвертый обходчик может работать с недетерминированными автоматами, однако в нем требуется, чтобы существовал детерминированный сильно связный подавтомат, который содержит все состояния исходного автомата. В последнем обходчике требуется, чтобы для любых двух состояний автомата существовала адаптивная тестовая последовательность, ведущая из одного состояния в другое. Заметим, что этому требованию заведомо удовлетворяют автоматы, содержащие детерминированный сильно связный покрывающий подавтомат. Для удовлетворения требований обходчиков можно использовать следующие методы:
  • дробление состояний;
  • введение связующих переходов;
  • обобщение переходов. Метод дробления состояний описан в [] (в статье ему соответствуют алгоритмы 1 и 2 построения дельта детерминированного и вполне определенного фактор-графа). Здесь он называется методом дробления, так как его применение для заданного начального обобщения состояний и переходов приводит к разбиению состояний, принадлежащих одному обобщенному состоянию, на несколько непересекающихся групп, которые образуют новые обобщенные состояния ().


    Использование метода дробления позволяет во многих случаях достичь детерминированности автомата. Многие предлагаемые в данной статье паттерны могут быть получены применением метода дробления. Однако для применения метода требуется представление модели в виде конечного автомата. Преставление модели в виде конечного автомата зачастую бывает слишком сложным из-за слишком большого числа получающихся состояний и переходов. Кроме того метод применим не для всех автоматов. Метод дробления может построить автомат, удовлетворяющий требованиям обходчика, однако число его состояний может быть слишком велико. Тогда для уменьшения количества состояний можно жертвовать разнообразностью состояний. Метод связующих переходов используется совместно с обходчиком автоматов, имеющих детерминированный, сильно связный покрывающий подавтомат. К уже имеющемуся недетерминированному переходу добавляется дополнительный детерминированный переход, гарантированно переводящий систему в заданное состояние. Например, если метод в зависимости от некоторых свойств переводит систему в два разных состояния, можно ввести дополнительный переход (сценарный метод), при выполнении которого перед вызовом метода свойства устанавливаются таким образом, чтобы перейти в требуемое состояние. На рис. 5 показан метод add, который в зависимости от некоторых свойств либо изменяет, либо не изменяет состояние. Для обеспечения существования детерминированного подграфа вводится сценарный метод add2, гарантированно переводящий систему в новое состояние. Процесс разработки тестового сценария Рис. 5. Метод введения связующих переходов Метод обобщения переходов состоит в использовании фильтрации при итерации параметров методов. Например, если при тестировании метода добавления элемента во множество целых чисел в качестве обобщенного состояния выбирать размер множества, то простая итерация параметров приводит к недетерминированному автомату. Обобщение переходов по ветвям функциональности "добавляемый элемент есть во множестве", "добавляемого элемента нет в множестве" позволяет получить детерминированный автомат (см.


    рис. 6). Процесс разработки тестового сценария Рис. 6. Метод обобщения переходов До запуска тестов желательно проверить выполнение требований обходчика. Однако данная проверка затруднительна, поскольку до запуска тестов сложно представить точный вид автомата. В сценарии задаются лишь состояние автомата и итерации переходов, а результирующие состояния переходов определяются только во время работы теста. С одной стороны, неявное задание автомата позволяет упростить задание автомата в тестовом сценарии, но, с другой стороны, затрудняет проверку требований обходчика без запуска тестов. Кроме того, при неоднозначности в спецификации возможных пост-состояний тестирование разных реализаций может приводить к построению разных автоматов, отличающихся результирующими состояниями переходов. В процессе запуска тестов определяется точный вид автомата. Результатами запуска тестового сценария является
  • достигнутое покрытие спецификаций (тестовых ситуаций);
  • нарушение или выполнение требований обходчика;
  • количество состояний, переходов и время работы. Таким образом, процесс построения тестового сценария оказывается сложным. Процесс затрудняется тем обстоятельством, что количество состояний модели велико, а порой и бесконечно. Построение автомата приемлемых размеров не гарантируется; кроме того, не для всех моделей можно построить автомат, удовлетворяющий требованиям обходчиков. Поскольку окончательный вид автомата определяется в процессе обхода, проверка требований обходчика до запуска тестов затруднительна, что еще больше усложняет задачу.

    Совместное использование

    Используется совместно с паттерном выделение элементов. Для тестирования списков при максимальном заполнении рекомендуется использовать паттерн среднее состояние.
    Используется совместно с паттерном выделение элементов. Для тестирования максимального заполнения множества рекомендуется использовать паттерн среднее состояние.


    Используется совместно с паттерном выделение элементов. Для элементов декартова произведения используются другие паттерны.


    Используется совместно с паттерном выделение элементов. Для тестирования максимального количества вершин в дереве рекомендуется использовать паттерн среднее состояние.

    В тестировании на основе моделей

    В тестировании на основе моделей модели используются для нескольких целей: для проверки соответствия тестируемой системы требованиям, представленным в модели; для задания покрытия; для генерации тестовых данных. Нас, в первую очередь, будет интересовать последний случай - применение моделей для генерации тестовых данных. При тестировании на основе моделей широко распространенным подходом к генерации тестовых данных является представление системы в виде конечного автомата и генерация тестовой последовательности на основе его обхода [, , ]. Авторы, использующие конечные автоматы для тестирования, отмечают, что это позволяет повысить качество тестирования, улучшить сопровождаемость тестов [, ]. В работе [] отмечается, что использование конечных автоматов дает возможность получать последовательности, которые было бы сложно получить при применении других подходов. В отличие от ручного тестирования и тестирования с автоматизированным прогоном тестов, при использовании автоматов тестовая последовательность строится автоматически. В случайных тестовых последовательностях сложно управлять тестами, направлять их на проверку определенных требований. В результате часть важных требований может быть не проверена вовсе. В данной работе в качестве базовой технологии рассматривается технология UniTesK []. В UniTesK для генерации тестовых данных используются модели конечных автоматов, задаваемые в тестовых сценариях. Тестовая последовательность получается автоматически в результате обхода такого автомата. Для проверки требований и задания покрытия используются формальные спецификации. Технология UniTesK позволяет использовать только тестовые сценарии без спецификаций; при этом проверка требований и оценка покрытия может производиться в тестовом сценарии. Паттерны, описываемые в статье, могут применяться для разработки тестовых сценариев и без использования спецификаций. Однако обычно применение технологии предполагает наличие спецификации системы, и поэтому мы будем считать, что для задания требований используется спецификация, определяющая модельное состояние.

    использующие конечные автоматы для тестирования

    Методы, использующие конечные автоматы для тестирования программ, накладывают ограничения на приемлемые размеры автоматов, при которых они применимы на практике. В этом смысле не является исключением и технология UniTesK, в которой приемлемое количество состояний составляет несколько сотен. Для борьбы с разрастанием состояний в UniTesK используется обобщение состояний, задаваемое в тестовом сценарии. Выбор обобщенного состояния в тестовом сценарии - это поиск компромисса между количеством состояний, их разнообразием и возможностями обходчика, использующего это состояние для построения тестовой последовательности. Процесс разработки тестовых сценариев по технологии UniTesK является сложной задачей. Процесс затрудняется тем обстоятельством, что количество состояний модели велико, а порой и бесконечно. Построение автомата приемлемых размеров не гарантируется; кроме того, не для всех моделей можно построить автомат, удовлетворяющий требованиям обходчиков. Поскольку окончательный вид автомата определяется в процессе обхода, проверка требований обходчика до запуска тестов затруднительна, что еще больше усложняет задачу. В данной статье предложены паттерны проектирования, позволяющие упростить разработку тестовых сценариев. Паттерны получены в результате анализа более чем десятилетнего опыта разработки тестов ИСП РАН в семи различных проектах. Было проанализировано около трехсот тестовых сценариев. Статистика показывает, что выделенные паттерны используются в 80% тестовых сценариев и лишь в 20% требуются дополнительные соображения. Паттерны представляют собой удачные решения часто встречающихся задач. Паттерны позволяют повторно использовать полученные результаты, передать опыт разработчиков тестовых сценариев. Знание паттернов дает возможность начинающему разработчику сценариев работать так, как работает эксперт; помогает выделить в модели части, к которым применимы паттерны. Использование паттернов позволяет опираться на библиотечные обобщенные состояния и автоматическую генерацию итераций параметров, поддерживаемые в инструментах тестирования. При использовании паттернов тестировщик может больше сосредоточиться на написании спецификаций к системе, нежели на выборе обобщенного состояния и итераций, которые должны удовлетворять требованиям обходчиков, сложно проверяемым без экспериментов. Паттерны покрывают большинство распространенных структур данных: списки, множества, отображения, деревья. Такие структуры наиболее часто встречаются при моделировании систем. Это дает уверенность в том, что и в дальнейшем в большинстве случаев можно будет использовать выделенные паттерны.

    Тестирование софта - статьи

    Что такое PDL

    PDL (Portable Dynamic Loader) - это легкая, простая и портабельная библиотека, предназначенная для создания и использования динамически загружаемых объектов классов.

    Для чего же нужна динамическая загрузка классов

    Основное применение этой технологии - это разработка динамически подключаемых плагинов, расширяющих функциональность основной программы. Главная проблема состоит в том, что динамически загружаемые библиотеки на многих платформах качественно поддерживают только процедурный стиль программирования, а с загрузкой и использованием экземпляров классов возникают разнообразные проблемы. Большинство этих проблем (но, к сожалению, пока не все) и решает библиотека PDL. На платформе Win32 PDL является сильно упрощённой альтернативой использованию технологии COM, без счётчика ссылок, без глобальной регистрации классов и множества других возможностей. Для Unix/Linux платформ существуют аналогичные библиотеки, например, С++ Dynamic Class Loader. Поддержка динамической загрузки классов присутствует и в крупной кросс-платформенной библиотеке WxWidgets. Целью разработки PDL было создание именно кроссплатформенной библиотеки, которая позволила бы использовать один и тот же код загрузки классов для Win32 и Unix/Linux платформ (в отличие от COM и C++ Dynamic Class Loader). В то же время, библиотека должна была быть максимально независимой и легковесной (поэтому была забракована WxWidgets).

    Использование динамически загружаемых классов

    Итак, у нас есть библиотека-плагин, содержащая динамически загружаемый класс. Теперь попробуем использовать этот класс.Для начала нам потребуется инстанция динамического загрузчика классов - PDL::DynamicLoader. Этот класс представляет собой синглтон, т.е. существует всегда в единственном экземпляре. Для получения ссылки на этот экземпляр используем статический метод DynamicLoader::Instance():КодPDL::DynamicLoader & dynamicLoader = PDL::DynamicLoader::Instance();Далее, нам нужно загрузить инстанцию класса и получить указатель на неё:КодMyTestInterface * instance =
    dynamicLoader.GetClassInstance< MyTestInterface >( myLibName, "MyTestClass1" );Здесь myLibName - это имя библиотеки-плагина, например "MyTestClass1.dll" или "MyTestClass.so". Не забываем подключить заголовочный файл с определением интерфейса класса - в нашем случае это MyTestInterface.hpp, упомянутый выше.Наконец, вызываем метод загруженного класса:Кодinstance -> DoSomething();Так как динамический загрузчик в случае ошибки выбрасывает исключения типа PDL::LoaderException, будет правильно обернуть его вызовы в блок try/catch. Полный код примера будет выглядеть так:Код#include
    #include try
    {
    PDL::DynamicLoader & dynamicLoader = PDL::DynamicLoader::Instance();
    MyTestInterface * instance =
    dynamicLoader.GetClassInstance< MyTestInterface >( myLibName, "MyTestClass1" );
    instance -> DoSomething();
    }
    catch( PDL::LoaderException & ex )
    {
    fprintf( stderr, "Loader exception: %s\n", ex.what() );
    } Следует отметить ещё один интересный момент: все динамически загруженные классы ведут себя как синглтоны. Иными словами, при повторном вызове DynamicLoader::GetInstance() с тем же именем библиотеки и с тем же именем класса будет возвращён указатель на уже загруженный экземпляр.
    Это сделано для облегчения контроля удалением инстанций динамически загружаемых классов. Если потребуется создавать множество инстанций классов, рекомендуется реализовать фабрику классов и сделать её динамически загружаемой.А как же удаляются инстанции загруженных классов? Для этого и существует метод DynamicClass::Destroy(). Вызывать напрямую его не нужно - это делает сам загрузчик в своём деструкторе, либо при вызове метода DynamicLoader::Reset(). Почему же не вызывается обычный деструктор? Дело в том, что некоторые тонкости механизма выделения динамической памяти меняются от компилятора к компилятору. Представим себе, что плагин собран компилятором A, а основная программа, использующая его - компилятором B. Если мы загружаем класс из плагина, его инстанция создаётся кодом, сгенерированным компилятором A. И если мы попытаемся удалить эту инстанцию из кода основной программы просто вызвав деструктор, то удалить её попытается код, сгенерированный компиляторов B. И тут возможны казусы.Для того, чтобы избежать подобных проблем, деструктор ~DynamicClass() сделан защищённым, а вместо него следует вызывать метод DynamicClass::Destroy(). Это метод будет гарантирует, что код деструктора класса скомпилирован тем же компилятором, что и код его конструктора.

    Ложка дёгтя

    Есть один тонкий момент, связанный с именами библиотек. Если имя библиотеки меняется, то считается, что это уже совершенно другая библиотека, хотя по-разному записанные имена могут указывать на одну и ту же библиотеку. Например: C:\MyProg\libs\mylib.dll и MyLIB.DLL.Следует отметить также, что библиотека PDL не решает проблемы с различным декорированием имён методов различными компиляторами - такая задача в настоящий момент не ставится.В настоящий момент библиотека оттестирована на платформах:
    FreeBSD 6.2
    Debian 4.0 Linux 2.6.18-4
    openSUSE 10.2
    Windows XP
    Я буду благодарен за любую информацию относительно работы PDL на других платформах.

    Создание динамически загружаемого класса

    Итак, рассмотрим подробно процесс создания динамически загружаемого класса с использованием библиотеки PDL. Прежде всего, следует определить интерфейс класса, через который мы будем работать с экземпляром загруженного класса. Обязательное условие: этот интерфейс должен наследоваться от класса PDL::DynamicClass. Давайте повнимательнее присмотримся к определению DynamicClass: Кодсlass DynamicClass
    {
    public:
    /**
    * @brief Get class name
    * return class name
    */
    virtual const char * GetClassName() const throw() = 0;

    /**
    * @brief Destroy class instance
    */
    void Destroy() throw() { delete this; }protected:
    /**
    * @brief Destructor
    */
    virtual ~DynamicClass() throw() {;; }
    }; Чисто виртуальная функция GetClassName() возвращает имя класса. Вам не нужно беспокоиться о её определении, чуть ниже я объясню почему. Невиртуальная функция Destroy() служит для уничтожения экземпляра класса. При этом сам деструктор сделан защищённым, чтобы предотвратить его прямой вызов. Зачем именно это сделано, будет также объяснено ниже. Итак, наш интерфейс мы должны унаследовать от PDL::DynamicClass и определить в нём виртуальный деструктор, желательно в секции protected (чтобы не нарушать идеологию PDL). Кроме того, в объявлении интерфейса обязательно нужно написать макрос DECLARE_DYNAMIC_CLASS, передав ему в качестве параметра имя класса. Если посмотреть на описание этого макроса, становится очевидно его предназначение: он просто определяет виртуальную функцию GetClassName(): Код#define DECLARE_DYNAMIC_CLASS( className ) \
    public: \
    virtual const char * GetClassName() const throw() { return #className; } Ну и напоследок, добавим в описание интерфейса объявления чисто виртуальных методов, реализующих полезную функциональность будущих динамически загружаемых классов.
    Пусть в нашем случае это будет метод void DoSomething(). В итоге мы имеем следующее объявление интерфейса: Код#include сlass MyTestInterface : public PDL::DynamicClass
    {
    public:
    /**
    * @brief Test method
    */
    virtual void DoSomething() throw() = 0; /**
    * @brief Declare this class dynamically loadable
    */
    DECLARE_DYNAMIC_CLASS( MyTestInterface )
    }; Это объявление следует поместить в отдельный заголовочный файл, в нашем случае - MyTestInterface.hpp, потому как для обеспечения совместимости включаться он будет и при сборке динамически загружаемого класса, и при его непосредственной загрузке и использовании. Далее следует определить сам класс, унаследовав его от абстрактного интерфейса MyTestInterface и определив методы, реализующие полезную функциональность. Кроме того, класс нужно экспортировать с помощью макроса EXPORT_DYNAMIC_CLASS. Обратите внимание, что этот макрос должен находиться вне определения класса: Код#include
    #include class MyTestClass1 : public MyTestInterface
    {
    public:
    /**
    * @brief Test method
    */
    void DoSomething() throw()
    {
    fprintf( stderr, "MyTestClass1::DoSomething()\n" );
    }
    };EXPORT_DYNAMIC_CLASS( MyTestClass1 ) Взглянем на определение макроса EXPORT_DYNAMIC_CLASS (файл DynamicClass.hpp): Код#define EXPORT_DYNAMIC_CLASS( className ) \
    extern "C" PDL_DECL_EXPORT PDL::DynamicClass * Create##className() \
    { \
    try { return new className(); } \
    catch( ... ) {;; } \
    return NULL; \
    }Этот макрос объявляет и определяет экспортируемую функцию Create<имя_класса>, которая создаёт и возвращает экземпляр указанного в параметре класса.


    Модификатор extern "C" нужен для того, чтобы компилятор не декорировал имя функции в библиотеке. Макрос PDL_DECL_EXPORT объявляет функцию экспортируемой. Он определён в файле platform.h и его реализация зависит от конкретной платформы.Следует обратить внимание на две вещи. Во-первых, в экспортируемой функции ловятся все исключения конструктора. В случае, если вызов конструктора аварийно завершился выбросом исключения, функция просто вернёт NULL. Это связано со сложностями, возникающими при попытке обработать в основном коде исключение, выбрасываемое кодом плагина. Во-вторых, экспортируемая функция возвращает указатель на PDL::DynamicClass, к которому неявно преобразовывается созданный объект класса. Таким образом, если мы забудем унаследовать интерфейс создаваемого класса от PDL::DynamicClass, компилятор напомнит нам об этом.После того, как класс создан, мы можем скомпилировать плагин. Следует отметить, что один плагин может содержать несколько различных динамически загружаемых классов, главное условие: они должны иметь уникальные в рамках этого плагина имена. Не следует забывать, что экспортировать при помощи макроса EXPORT_DYNAMIC_CLASS нужно каждый из классов.

    Тестирование софта - статьи

    Использование интерпретируемого кода

    Еще одним подходом к обеспечению переносимости приложений является написание программного кода на интерпретируемых языках, использование которых не подразумевает создания исполняемых файлов в формате целевой операционной системы. Вместо этого интерпретатор последовательно считывает и выполняет инструкции непосредственно из текста программы. Прямолинейная интерпретация достаточно неэффективна — у интерпретатора практически нет возможностей для оптимизации кода. Для повышения эффективности во многих языках (Java, Perl, Python, семейство .NET) исходный код сначала транслируется в некоторое промежуточное представление (байт-код), который уже подается на вход интерпретатору. Стадия получения байт-кода фактически является разновидностью компиляции, и при ее осуществлении могут выполняться различные оптимизирующие преобразования. Однако и в этом случае зачастую не удается достигнуть производительности, присущей «родным» для системы приложениям, поскольку определенная часть времени тратится на разбор байт-кода и транслирование его в вид, подходящий для исполнения в целевой среде. Большей производительности удается достигнуть при использовании компиляции “на лету” (Just In Time compilation), когда байт-код транслируется в машинный код во время работы программы. Однако разработчикам JIT-компиляторов также приходится идти на компромисс между временем, уходящим на оптимизацию, и эффективностью получаемого кода, в результате чего производительность приложений может уступать коду, создаваемому “обычными” компиляторами. Кроме того, использование данной технологии увеличивает потребление памяти приложением, поскольку оттранслированный код также хранится в оперативной памяти. Также следует иметь в виду, что эффективность реализации интерпретаторов, также как и JIT-компиляторов, на различных платформах может отличаться.

    Использование эмуляторов ABI

    Говоря о бинарной переносимости, мы уже упомянули, что в ряде случаев операционная система может обеспечивать бинарную совместимость с другой системой за счет дополнительного слоя совместимости. При этом большинство существующих реализаций ограничивается обеспечением совместимости в рамках систем одного производителя. Из других примеров можно выделить поддержку запуска исполнимых файлов Linux в системе FreeBSD. Сам Linux также имел поддержку запуска исполнимых файлов других UNIX-систем, хотя эта функциональность не оказалась востребованной. В то же время существует много продуктов сторонних разработчиков, позволяющих загружать файлы других операционных систем путем использования транслятора, способного загружать файлы требуемого формата, преобразуя вызовы функций, осуществляемые внутри файла, в соответствующие вызовы текущей ОС (фактически, такой транслятор реализует ABI старой системы в новой системе). В качестве примера можно привести wine , предназначенный для запуска Windows-приложений в Linux, а также cygwin , обеспечивающий переносимость в обратную сторону. При этом, например wine достаточно легко использовать как часть приложения, не полагаясь на его доступность в целевой системе. Недостатком использования такого рода эмуляторов является потенциальная неполнота реализации интерфейса, необходимого приложению. Так, разработчики того же wine ориентируются на публично доступную информацию об API Windows (например, стандарт ECMA-234); так что если приложение использует какие-то недокументированные возможности этой ОС, то попытка его запуска в wine может оказаться неудачной. Отметим, что для достижения высокой производительности программы-эмуляторы зачастую используют достаточно низкоуровневые способы взаимодействия с системой, что, в свою очередь, ограничивает их собственную переносимость. Так, тот же wine содержит достаточно много кода на языке ассемблера архитектуры x86. Соответственно, использование этого эмулятора возможно либо в непосредственно x86 системах, либо в системах, предоставляющих возможность запуска x86 приложений. Поэтому при стремлении охватить большое число различных программно-аппаратных платформ, может возникнуть необходимость разных эмуляторов на различных системах. Кроме того, в определенных ситуациях прямолинейное использование эмуляторов по определению дает менее эффективные продукты, чем те, что используют «родные» средства целевой системы. В качестве примера можно привести уже упоминавшуюся ранее среду разработки Borland Kylix для Linux, основанную на использовании эмулятора wine. Косвенно использование эмулятора в этом случае означает использование тех же методик создания программ, которые применяются в исходной системе — ОС Windows. Однако, как показала практика, эмуляция этих методик не выдерживает конкуренции с аналогичными средствами ОС Linux — в частности, с компилятором GCC (прежде всего, в плане производительности программ, получаемых с помощью среды Kylix).

    Использование Web-технологий

    Становящиеся с каждым годом все популярнее web-технологии также могут быть использованы для повышения переносимости программных продуктов. Можно выделить два основных способа построения приложений, использующих эти технологии:
  • создание полностью локального приложения, располагающегося на машине пользователя, и использующего web-браузер для взаимодействия с ним;
  • реализация приложения в виде сервиса; при этом серверная и клиентская части приложения могут быть разнесены на разные машины. Такое разделение довольно условно, поскольку в первом случае приложение также, как правило, имеет серверную и клиентскую части, и может допускать подключение клиентов с других машин. Например, подобным образом устроен Web-интерфейс сервера печати CUPS (Common Unix Printing System), который может быть использован для настройки принтеров как на локальной, так и удаленных машинах. В любом случае, использование web-браузера и сопутствующих инструментов облегчает задачу разработки графического интерфейса пользователя. Программа может либо создавать HTML-представление интерфейса, непосредственно интерпретируемое браузером, либо использовать технологии типа Flash, Silverlight и JavaFX, позволяющие создавать интерактивные файлы, для воспроизведения которых достаточно наличия соответствующего проигрывателя на целевой платформе. Зачастую такой проигрыватель встраивается в браузер как расширение. Однако хотелось бы подчеркнуть, что во многих ситуациях требования к среде, где функционирует серверная части приложения, существенно более строгие, чем требования к клиентской системе. Последние зачастую сводятся к требованию наличия браузера, поддерживающего определенные возможности. Для серверной же части может требоваться вполне конкретная программно-аппаратная платформа. Использование web-браузера для отрисовки графического интерфейса имеет как преимущества, так и недостатки. К преимуществам, помимо упомянутого выше снижения затрат на разработку, стоит отнести возможность настройки интерфейса пользователем по своему вкусу — изменением шрифтов, масштаба страницы и т.п.
    Основной недостаток подхода является следствием его достоинств — в разных браузерах одна и та же страница может отображаться по-разному — как вследствие пользовательских настроек, так и из-за особенностей конкретного браузера. Безусловно, существуют различные стандарты на HTML, CSS, JavaScript и прочие элементы, используемые при отображении web-документов, однако разные браузеры соответствуют этим стандартам в разной степени . Поэтому разработчикам приходится либо проводить тестирование на различных браузерах , либо ограничиваться поддержкой только некоторых из них. Для автоматизации процесса тестирования существуют различные свободно доступные инструменты — например, сервис BrowserShots позволяет получить снимки экрана с видом заданной страницы в различных браузерах (на данный момент доступно более 80) в Linux, Mac OS и Windows. Также отметим, необходимость создания больших распределенных систем привела к созданию архитектур, более сложных, чем клиент-серверная, и подразумевающих взаимодействие множества компонентов различной структуры. Исследования проблем разработки сложных программных комплексов привели к разработке парадигм, подразумевающих разбиение системы на отдельные компоненты, которые взаимодействуют друг с другом по строго определенным протоколам. При таком подходе каждый компонент не только может работать на отдельной машине, но также разрабатываться независимо от других. При этом (в идеале) процесс переноса компонента на другую платформу либо замена его на альтернативную реализацию никак не затрагивает других частей системы. К одной из первых попыток описания механизма взаимодействия компонентов распределенной системы можно отнести спецификацию CORBA (Common Object Request Broker Architecture), разработанную консорциумом OMG . Однако CORBA в силу ряда причин не снискала большой популярности, и в настоящее время гораздо больший интерес проявляется к web-сервисам, использующим протоколы обмена сообщениями на базе XML. При проектировании сложных распределенных программных комплексов используется парадигма сервисно-ориентированной архитектуры (Service-oriented architecture, SOA ); при этом программные комплексы часто реализуются как набор web-сервисов.

    Методы обеспечения переносимости ПО

    ,
    Труды Института системного программирования РАН

    Переиспользование бинарных файлов

    Перенос приложения на новую программно-аппаратную платформу может пройти безболезненно для разработчиков, если старая и новая системы совместимы на бинарном уровне, то есть новая система поддерживает двоичный интерфейс приложений (Application Binary Interface, ABI), что позволяет использовать старые двоичные файлы приложения без каких-либо изменений. Можно выделить две основные составляющие ABI:
  • форматы исполняемых файлов и библиотек;
  • набор библиотек и их функций, предоставляемых системой. Совместимость форматов является критичной для возможности использовать двоичные файлы без изменений. При этом формат файлов достаточно сильно связан с аппаратной частью платформы, на которой функционирует система - в силу технических причин (разный размер указателей, различный порядок нумерации разрядов и т.п.) в общем случае сложно достичь совместимости систем, работающих на различных архитектурах. Что касается наборов библиотек и их функций, то их полной идентичности в различных системах ожидать трудно, однако во многих случаях пересечение множеств предоставляемых функций является достаточно большим. Многие производители операционных систем в настоящее время заботятся об обратной совместимости своих продуктов на бинарном уровне — гарантируется, что приложение, работающее в некоторой версии ОС, будет работать без перекомпиляции в более новых версиях системы на той же аппаратной архитектуре. В ряде случаев поддерживается возможность запуска приложений, написанных для той же системы, но на другой платформе — так, операционная система MacOS X, работающая на компьютерах Apple с процессорами Intel, использует динамический транслятор Rosetta для выполнения программ, предназначенных для машин с процессорами PowerPC . Однако пользоваться такой возможностью следует с осторожностью. Во многих случаях совместимость обеспечивается за счет некоторого дополнительного слоя совместимости между системой и приложением, который может и не гарантировать полной совместимости — так, уже упомянутая Rosetta позволяет исполнять код для процессоров G3, G4 и AltiVec, но не для G5. Кроме того, дополнительный компонент системы является дополнительным потенциальным источником ошибок, а использование посредника в общем случае снижает производительность. Например, процессор Itanium способен выполнять код, созданный для платформы x86, однако производительность его в этом случае может уступать оригинальному x86 процессору с такой же тактовой частотой .

    Переиспользование исходного кода

    К сожалению, в большинстве случаев совместимость на бинарном уровне выполняется в рамках систем одного производителя, однако лишь немногие системы способны загружать исполняемые файлы, предназначенные для выполнения на платформах других поставщиков. Альтернативой использованию одних и тех же бинарных файлов явилось использование одного и того же исходного кода для сборки приложения на различных системах. Исторически, возможность переноса исходного кода между различными платформами появилась не сразу. В те времена, когда программы писались на ассемблере конкретной аппаратной платформы, добиться компиляции приложения на системе с другой архитектурой было практически нереально. Существенной подвижкой в этом направлении стало создание высокоуровневых языков программирования, не привязанных к конкретной архитектуре и позволяющих использовать одни и те же конструкции на различных системах. Например, именно озабоченность некоторых сотрудников AT&T Labs проблемой переносимости ОС UNIX на новые аппаратные платформы привела к созданию языка Си. Для обеспечения возможности использования одного и того же кода целевые системы должны предоставлять компиляторы для соответствующего языка программирования, вместе с необходимыми библиотеками. Естественно, что компиляторы для различных систем создаются различными производителями и могут достаточно сильно отличаться друг от друга. Такие отличия следует учитывать при написании кода. Частично эта задача облегчается следованием международных стандартов, разработанных для многих языков программирования. Однако далеко не все компиляторы поддерживают стандарты в полном объеме (хотя, как правило, не поддерживаются некоторые специфические конструкции языка, в плане же предоставления библиотечных функций ситуация гораздо лучше). Другой проблемой является относительная узость стандартов — несмотря на наличие во многих из них перечня функций, которые должны предоставляться любой удовлетворяющей стандарту средой разработки, эти перечни не описывают существенную часть функциональности, которая могла бы быть полезна при создании приложений — например, функции графического интерфейса, либо функции работы с мультимедиа. Помимо стандартов языков программирования, существуют стандарты, описывающие интерфейс прикладных программ (API, Application Programming Interface) — например, POSIX.
    Однако такие стандарты также, достаточно узки, и являются недостаточными для написания большинства приложений. В случае, если необходимая приложению функциональность не охватывается ни одним из стандартов, могут быть использованы продукты сторонних разработчиков, существующие на всех целевых системах и предоставляющие необходимые возможности. Такой продукт выступает в роли медиатора между приложением и операционной системой, скрывая от приложения процесс взаимодействия с последней. Характерным примером подобных продуктов являются кросс-платформенные библиотеки графического интерфейса пользователя — такие, как Qt, Gtk или wxWidgets. Реализация этих библиотек присутствует во всех основных операционных системах (FreeBSD, Linux, Windows, MacOS), а функции API, доступные программистам, практически идентичны для всех платформ. Кроме того, либеральность лицензий позволяет использовать эти библиотеки при создании достаточно большого спектра приложений. Если же медиатора, способного удовлетворить нужды разработчиков, не нашлось, то может быть целесообразно создать свой собственный, который можно будет использовать во многих продуктах компании. Такой подход оправдан, если можно четко выделить функциональность, которую должен предоставлять медиатор, и отделить реализацию этой функциональности от остальных частей продукта. Так, разработчики Mozilla в свое время решили создать собственный набор библиотек поддержки различных стандартов безопасности (в результате появился набор Network Security Services, NSS), а также собственную кросс-платформенную библиотеку, реализующую достаточно низкоуровневые функции (работы с потоками, памятью, вводом-выводом и т.п.). Результатом второй инициативы стало создание библиотеки NSPR (NetScape Portable Runtime). Отметим, что поскольку и NSS, и NSPR, являются продуктами с открытым исходным кодом, их использование в настоящее время не ограничивается проектами, разрабатываемыми Mozilla Foundation.

    Примеры из современности

    Несмотря на то, что о проблеме переносимости известно достаточно давно и ее решению посвящено множество работ и исследований, постоянно возникают ситуации, когда выясняется, что данному вопросу вовремя не было уделено должное внимание и это привело к неприятным последствиям. Рассмотрим несколько реальных примеров таких ситуаций. В начале 2000-х годов компания Borland решила обратить свой взор на ОС Linux и выпустила среду разработки Kylix — аналог Delphi и C++ Builder. Такой шаг был изначально положительно оценен Linux-сообществом (даже несмотря на то, что Kylix не являлся продуктом с открытым исходным кодом, а бесплатной была только базовая версия системы) — в то время в этой ОС не было сравнимых по функциональности аналогов упомянутых программ.. Однако в основе Kylix лежал исходный код соответствующих сред разработки для ОС Windows, а для запуска на Linux использовался эмулятор wine. Как показала практика, такой прямолинейный перенос, как использование эмулятора, не привел к созданию конкурентноспособного продукта — довольно быстро выяснилось, что wine не является достаточно надежным, чтобы гарантировать его стабильность . Разработчикам приходилось иметь дело как с ошибками в своих программах, так и с некорректным поведением эмулятора. Ввиду закрытости проекта сложно оценить, насколько затратен был бы перенос программ на использование «родных» библиотек Linux; но основываясь на том факте, что работа над Kylix была заморожена, можно предположить, что задача оказалась слишком ресурсоемка. Другой пример недальновидного подхода к этому вопросу проявился в ходе организации проекта по разработке пакета свободного программного обеспечения (СПО) для образовательных учреждений России. Практически все программное обеспечение, которое разрабатывалось по заказу Министерства образования РФ в последние годы было предназначено для работы исключительно на платформе Microsoft Windows. Поэтому при внедрении пакета СПО на основе операционной системы Linux большая часть разработанных ранее образовательных программ оказалась недоступна, и только часть из них удавалось запустить с помощью эмулятора wine . Схожие проблемы возникали и в стане разработчиков web-приложений.
    Известно несколько случаев, когда при разработке интернет-сервисов заказчик ограничивался требованием совместимости с браузером Internet Explorer, а через некоторое время под давлением клиентов был вынужден дорабатывать ПО для поддержки набирающего популярность Mozilla Firefox. Например, на основе опроса пользователей приложения Tasktop о желаемых нововведениях, проведенного в 2008 году, выяснилось, что наиболее востребованными являются поддержка ОС Linux и браузера Firefox. Реализация этих свойств стала приоритетным направлением разработки, и была представлена пользователям уже в ноябре 2008 года, в Tasktop 1.3 . Отметим, что добавление такого нетривиального свойства, как поддержка новой операционной системы, не заняло много времени, поскольку основная часть приложения написана на интерпретируемом языке Java, а виртуальные машины для исполнения этого кода существуют как в Windows, так и в Linux. Более того, разработчики Tasktop планируют портировать свой продукт и на MacOS — ввиду наличия в этой ОС виртуальной машины Java, такая задача также не представляется слишком сложной. Приведенные примеры демонстрируют, что изначальная нацеленность на создание переносимого продукта позволяет достаточно безболезненно портировать его на платформы, поддержка которых изначально не планировалась (либо которых просто не существовало в момент начала разработки). Это позволяет производителям не оставаться в стороне от стремительного развития рынка и своевременно реагировать на запросы пользователей. В то же время невнимание к проблеме переносимости может привести к различным негативным техническим, экономическим и политическим последствиям:
  • проблемам с поддержкой ПО в долгосрочной перспективе;
  • сокращению доступных рынков и недополучению прибыли;
  • попаданию в зависимость от одного поставщика. Как мы уже отметили, проблема известна давно, и за время эволюции программного обеспечения появилось достаточно много подходов к созданию приложений, переносимых между различными системами. Рассмотрим те из них, что представляются наиболее популярными.

    Виртуализация

    Альтернативой эмуляции ABI некоторой системы является запуск копии такой системы внутри основной ОС, с использованием программ, эмулирующих аппаратное обеспечение — виртуальных машин. На такой машине устанавливается операционная система и другое окружение, необходимое приложению, а само приложение запускается уже в родной для него среде. Возможность запуска приложения на виртуальной машине зависит, в основном, от возможностей самой машины, нежели от разработчиков приложения. Тем не менее, программы, работающие с аппаратурой напрямую, могут встретить определенные трудности, поскольку им будет предоставлен доступ к устройствам виртуальной машины, а не непосредственно компьютера. Такая особенность ограничивает, например, возможность работы с графическими ускорителями изнутри виртуальных машин. В общем случае использование виртуальной машины достаточно ресурсоемко — ведь помимо собственно приложения, ресурсы компьютера потребляются самой машиной, а также работающими внутри нее программами, необходимыми для функционирования приложения (например, операционной системой). Поэтому выигрыш в производительности достигается, как правило, только в случае запуска машин, эмулирующих достаточно маломощные платформы на более производительных системах. Проблеме производительности виртуальных машин уделялось много внимания еще в 70е годы. Впервые требования, которым должна удовлетворять аппаратная архитектура машины, чтобы на ней можно было эффективно реализовать виртуализацию, были сформулированы Попеком и Голдбергом в 1974 году . Основное требование сводится к разделению всех инструкций по разным уровням привилегий; при этом все инструкции, способные изменить состояние ресурсов виртуальной машины, а также инструкции, поведение которых зависит от конфигурации этих ресурсов, должны быть привилегированными. Монитор виртуальных машин (Virtual Machine Monitor, VMM — программная прослойка, занимающаяся распределением физических ресурсов между работающими виртуальными машинами), сам работая с наивысшими привилегиями, может перехватывать эти инструкции и эмулировать их поведение в соответствии со своими потребностями.
    Все непривилегированные инструкции должны выполняться непосредственно на аппаратуре. Поскольку доля привилегированных инструкций, как правило, невелика, то и затраты на их эмуляцию будут малы. Несмотря на то, что работа Голдберга и Попека посвящена машинам третьего поколения (IBM 360, Honeywell 6000, DEC PDP-10), сформулированные в ней требования справедливы и сегодня. Однако, для большинства современных архитектур приведенное выше требование не выполняется — существуют непривилегированные инструкции, влияющие на конфигурацию ресурсов либо зависящие от нее. В частности, для «классической» архитектуры x86 к числу таких инструкций относятся такие популярные инструкции, как push/pop, call, jmp и другие (более подробно данная проблема рассмотрена в ). Безусловно, построение виртуальной машины возможно и при наличии таких инструкций. Существуют подходы по определению и перехвату необходимых инструкций в процессе работы программы; по такому принципу работают популярные продукты типа VirtualBox и VMWare , старающиеся напрямую выполнять все инструкции, для которых это возможно. Тем не менее, необходимость дополнительного отслеживания выполняемых инструкций может замедлить производительность программ внутри виртуальной машины по сравнению с «живой» системой. Отметим, что осознание разработчиками важности виртуализации привело к появлению расширений от Intel (Intel Virtualization Technology ) и AMD (AMD Virtualization ) для ее поддержки на платформах x86 и x86-64, которые позволяют либо вовсе избавится, либо существенно снизить число перехватываемых и эмулируемых инструкций. Альтернативным методом борьбы с «вредоносными» инструкциями является паравиртуализация, основанная на внесении изменений в гостевую операционную систему перед ее запуском в виртуальной среде; известным примером машин, работающих по принципу паравиртуализации, является Xen . Однако в реальной жизни такая модификация не всегда доступна конечным пользователям. Существуют и гибридные подходы — например, проект vBlades для Itanium . Стоит обратить внимание и на экономическую составляющую использования виртуальной машины конечным пользователям — в ряде случаев стоимость операционной системы и компонентов окружения, которые необходимо установить, может быть довольна значительна.Поэтому достаточно редки ситуации, когда производители сами советуют использовать виртуальные машины для запуска своих программ в системах, не поддерживаемых напрямую. Примерами исключений являются различные специфические системы, безопасное функционирование которых требует запуска в изолированной среде. Например, обучающая система Linuxgym распространяет клиентскую часть в виде образа VMware, содержащего Ubuntu Linux с предустановленными необходимыми компонентами.

    Проблема переносимости приложений между различными

    Проблема переносимости приложений между различными программно-аппаратными платформами ненамного моложе собственно компьютерных программ. Еще в конце шестидесятых годов озабоченность некоторых сотрудников AT&T Labs проблемой переносимости ОС UNIX на новые аппаратные платформы привела к созданию языка Си. Темпы развития компьютерной индустрии таковы, что проблемы сорокалетней давности кажутся достаточно простыми и решаемыми, по сравнению с тем, что мы имеем сегодня. Стремительное развитие связанных с компьютерами отраслей приводит к постоянному появлению новых программно-аппаратных платформ, информационных систем, и т.п., в то время как устаревшие комплексы уходят в небытие. Производители ПО, как правило, заинтересованы в быстром переносе своих продуктов на новые системы, чтобы захватить соответствующую долю рынка. Если приложение изначально проектировалось с оглядкой на возможность портирования, то этот процесс может оказаться существенно дешевле создания нового продукта. Будет проще и конечным пользователям, которые в новой системе увидят то же самое приложение, с которым работали раньше, что также способствует популярности продукта. В некоторых случаях большое внимание переносимости приложений уделяется и производителями платформ, на которых эти приложения выполняются — например, одним из принципиальных аспектов при разработке IBM мэйнфрейма System/S370 в конце 60х-начале 70х годов было сохранение обратной совместимости с System/S360, с целью упростить миграцию приложений своих клиентов . Обратная совместимость с System/S360 сохранялась на протяжении всего жизненного цикла System/S370; более того, сохраняется она и в ее последователях — System/S390 и zSeries. Однако далеко не все производители столь щепетильны, и далеко не все платформы могут похвастаться столь длительным сроком жизни. Нередко с рынка уходят не только сами системы, но и их создатели, так что труднодоступными становятся не только сами системы, но и все сведения об их архитектуре. Исчезновение тех или иных платформ приводит к появлению унаследованного ПО — программных продуктов, необходимых для функционирования той или иной организации, но требующих для работы устаревшей программно-аппаратной платформы. В случае выхода из строя аппаратного обеспечения может оказаться, что найти ему замену очень сложно, дорого, а иногда и попросту невозможно, так как устаревшая ОС не работает на современном оборудовании (либо по причине принципиальных отличий архитектуры, либо по более прозаическим причинам — например, ввиду отсутствия необходимых драйверов). Для многих предприятий задача переноса таких приложений в более современное окружение является крайне актуальной .

    и существует множество путей по

    Если невнимание к проблеме переносимости приводит к негативным последствиям и существует множество путей по ее решению, то возникает вопрос: так почему же ИТ индустрия не переориентируется на разработку переносимого ПО? Несложно догадаться, что разработка переносимого ПО имеет свои недостатки. Среди рассмотренных видов переносимости приложений очень привлекательным с точки зрения разработчиков является перенос непосредственно бинарных файлов на новую систему, позволяющий при относительно небольших затратах (в основном уходящих на тестирование) получить на новой системе приложение, имеющее всю необходимую функциональность. При этом потери в производительности если и возникают, то совсем небольшие. Однако для любой ОС число платформ, совместимых с ней на бинарном уровне, достаточно невелико. Использование эмуляторов может расширить их круг, но эмулятор — дополнительный потенциальный источник ошибок, который при этом может и не предоставлять всех необходимых функций. Потенциально больший охват дает переносимость исходного кода. Сложность портирования в этом случае может варьироваться в зависимости от того, насколько такая возможность учитывалась при разработке приложения; полезной с этой точки зрения является ориентация на различные интерфейсные стандарты, регламентирующие взаимодействие приложения с окружающей средой. Но существующие стандарты охватывают достаточно небольшую функциональность; в ряде случаев может помочь использование кросс-платформенных библиотек, другой же альтернативой является использование интерпретируемых языков. Спецификации таких языков не привязаны к конкретной платформе и можно полагаться на то, что интерпретаторы на разных системах поддерживают один и тот же набор функций. Среди недостатков подхода можно выделить меньшую производительность по сравнению с бинарным кодом. Архитектура SOA затрагивает более сложную проблему организации сложных программных комплексов, предлагая строить их в виде набора достаточно изолированных компонентов, каждый из которых может работать на своей собственной платформе и в случае необходимости может быть перенесен на другую (либо заменен на альтернативную реализацию). Подход Примеры
    Ориентация на стандарты Ряд утилит GNU (tar, wget и др.), написанные с ориентацией на POSIX
    Медиаторы сторонних разработчиков Skype — использование библиотеки Qt для реализации GUI
    Собственные медиаторы Mozilla Firefox — использование собственных кросс-платформенных библиотек NSS (Network Security Services) для поддержки различных стандартов безопасности
    Эмуляция Google Picasa — применение wine для работы в Linux
    Виртуализация Linuxgym — клиент распространяется в виде образа VMware
    Таблица 1.
    Примеры использования различных подходов к обеспечению функционирования приложений как в Windows, так и в Linux. Использование виртуальных машин также не требует больших усилий со стороны разработчиков ПО, хотя этот способ достаточно накладен, как в смысле производительности, так и ввиду необходимости иметь лицензии на все используемые операционные системы. Применение виртуализации оправдано в тех случаях, когда перенос приложения каким-то другим способом представляется экономически неэффективным. В частности, это относится ко многим унаследованным системам, для которых портирование на новую платформу означало бы практически полное переписывание приложения. В таблице 1 приведены примеры использования различных подходов к обеспечению переносимости ПО, которые используются разработчикам для создания программ, функционирующих как в Windows, так и в Linux. Интересно отметить подход Google, который не побоялся положиться на слой эмуляции wine для запуска Google Picasa в ОС Linux, несмотря на практически полное отсутствие успешных примеров крупных приложений, официально использующих такой метод. Подход с использованием библиотек-медиаторов более традиционен и применяется не один десяток лет. Вопрос обеспечения переносимости следует рассматривать в самом начале проекта, на стадии проектирования и выбора технологий и инструментов, которые будут использованы при его реализации. К сожалению, вряд ли можно сформулировать универсальное правило выбора средств для увеличения переносимости – такой выбор сильно зависит от конкретных требований, предъявляемых к приложению. На взгляд авторов, в настоящее время потенциально наибольший охват дает использование интерпретируемых языков – многие интерпретаторы работают на достаточно большом числе программно-аппаратных платформ. Естественно, использование таких языков имеет свои недостатки, и для ряда приложений может оказаться неприемлемым. Следующим по масштабу «охвата», на наш взгляд, идет использование кросс-платформенных библиотек и других медиаторов.


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

    Тестирование софта - статьи

    Аналогии

    Есть замечательная реклама: Как делают автомобили SAAB? Берут самолёт и отсекают всё лишнее, что помогает ему взлетать.
    Попробуем взять шаблон RUP-а для MTP (Master Test Plan) и отсечь всё лишнее, что не касается темы данной статьи. Самое интересное, что отсечь пришлось практически весь шаблон, оставив только одно приложение и табличку, для расчета ресурсов. Добавим перевод, вырежем лишнее.

    Деятельность/Задачи тестирования (Testing Activities)

    Рассмотрим более подробно существующие активности/задачи связанные с тестированием:
  • Планирование тестов (Plan Test)
  • Определение требований к тестам (identify requirements for test)
  • Оценка рисков (assess risk)
  • Разработка стратегии тестирования (develop test strategy)
  • Определение ресурсов (identify test resources)
  • Создание расписания/последовательностей (create schedule)
  • Разработка Плана тестирования (generate Test Plan)
  • Дизайн тестов (Design Test)
  • Анализ объёма работ (prepare workload analysis)
  • Определение и описание тестовых случаев (identify and describe test cases)
  • Определение и структурирование тестовых процедур (identify and structure test procedures)
  • Обзор и оценка тестового покрытия (review and assess test coverage)
  • Разработка тестов (Implement Test)
  • Запись или программирование тестовых скриптов (record or program test scripts)
  • Определение тесто-критичной функциональности в Дизайне и Модели реализации (identify test-specific functionality in the Design and Implementation Model)
  • Создание/подготовка внешних наборов данных (establish external data sets)
  • Выполнение тестов (Execute Test)
  • Выполнение тестовых процедур (execute Test procedures)
  • Оценка выполнения тестов (evaluate execution of Test)
  • Восстановление после сбойных тестов (recover from halted Test)
  • Проверка результатов (verify the results)
  • Исследование неожиданных результатов (investigate unexpected results)
  • Запись ошибок (log defects)
  • Оценка тестов (Evaluate Test)
  • Оценка покрытия тестовыми случаями (evaluate Test-case coverage)
  • Оценка покрытия кода (evaluate code coverage)
  • Анализ дефектов (analyze defects)
  • Определение критериев завершения и успешности тестирования (determine if Test Completion Criteria and Success Criteria have been achieved


  • Классификация задач и ролей в тестировании, основанная на методологии RUP.

    Вокруг ролей и задач, связанных с тестированием и обеспечением качества, сложилось несколько противоположных идейных течений, которые усердно культивируются носителями этих идей. Точки зрения во многом противоположны, во многом противоречивы. Тестирование видится с одной стороны каким-то полумеханическим процессом, который не требует особенной квалификации: тестировщика видят эдаким «кликальщиком», который просто гоняет приложение, ждёт пока оно «упадёт», потом радостно сообщает об ошибке и продолжает в том же духе. В последнее время, надо отдать должное, появляются материалы о тестировании и качестве, выходят в свет книги, развиваются сайты посвящённые этому направлению это направление мысли постепенно сходит на «нет». С другой точки зрения, которую, наверное, культивируют отчасти и сами тестировщики (в самом широком смысле этого слова), тестирование это процесс, покрытый множеством неопределённостей, трудно формализируемый и поддающийся оценкам. Если же к тестированию добавить автоматизацию, которая по оценкам тех, кто внедрял инструменты и решения для тестирования, требует больших (по сравнению с ручным тестированием) трудозатрат и говорить об оценке качества продукта, направление тестирования получается совсем непрозрачным для стороннего наблюдателя, а порой и для самих тестировщиков и QA. Как мне кажется, связана такая ситуация с тем, что существует определённое непонимание процессов связанных с тестированием и обеспечением качества. Между тем, направление тестирования чётко определено ролями и задачами, которое оно решает. Определившись с ролями и задачами, можно не только представить себе более-менее стандартный процесс тестирования, но также понять, что к нему не относится. Заметим, я не отождествляю процессы тестирования и обеспечения качества. К задачам обеспечения качества я планирую вернуться в последующих публикациях. Сейчас я предлагаю рассматривать тестирование, как часть процесса обеспечения качества. Попробуем определиться и понять, какие же роли и задачи решаются направлением тестирования. Чтобы не изобретать велосипед, я предлагаю взять за основу существующую методологию RUP (Rational Unified Process), как наиболее общий и полный вариант.

    Роли в тестировании (roles)

    Роль Описание
    Тест-менеджер, менеджер проекта по тестированию (Test Manager, Test Project Manager) Производит управленческий контроль (management oversight)Ответственность:
  • Обеспечивает техническое направление
  • Получает необходимые ресурсы
  • Обеспечивает управленческую отчётность
  • Тест дизайнер (Test Designer) Определяет, приоритизирует и обеспечивает разработку тестовых случаевОтветственность:
  • Разрабатывает план тестирования
  • Разрабатывает модель тестирования
  • Оценивает эффективность тестирования
  • Тестировщик, Инженер по тестированию (Tester) Выполняет тестыОтветственность:
  • Выполняет тесты
  • Фиксирует результаты
  • Восстанавливает тесты и систему после сбоев
  • Документирует запросы на изменение
  • Администратор тестовой системы, приложений поддерживающих жизненный цикл тестирования (Test System Administrator) Обеспечивает управление и поддержку тестовых окружений и данныхОтветственность:
  • Администрирует систему управления тестированием
  • Инсталлирует и управляет доступом к тестовым системам
  • Администратор баз данных, менеджер баз данных (Database Administrator, Database Manager) Обеспечивает управление и поддержку тестовых данных (баз данных)Ответственность:
  • Администрирует тестовые данные (базы данных)
  • Тест-дизайнер (Designer) Устанавливает и определяет операции, атрибуты и связи тестовых классовОтветственность:
  • Устанавливает и определяет тестовые классы
  • Устанавливает и определяет тестовые наборы (пакеты)
  • Разработчик тестов (Implementer) Разрабатывает юнит тесты (unit tests), тестовые классы и тестовые наборы (пакеты)Ответственность:
  • Создаёт тестовые классы, собирает тестовые пакеты и интегрирует их с тестовую модел
  • Как видите, при ближайшем рассмотрении, оказывается, что тестирование вполне определённый процесс с выделенными ролями и зоной ответственности для различных игроков проекта. Порядок перечисления задачи определяет обычный (полный) цикл проведения тестирования. Такой цикл может применятся, как для проектов ориентированных на длительные итерации, так и для «быстрых» проектов ведущихся по эволюционным методикам (evolutionary) или согласно набирающему обороты XP. Надеюсь, после небольшого экскурса во внутренний мир задач и ролей в тестировании, неразберихи станет меньше.

    Тестирование софта - статьи

    Аллилуя, то есть Славься!

    Я не преследовал цели посвятить вас в самые сложные вещи Qt — точнее, таких вещей я там и не заметил. Всё ровно, логично, слаженно, сбито и задокументированно. Конечно, были и будут баги, возникают новые возможности — но для этого есть огромное сообщество разработчиков, да и конфы в Сети просто ужасно большое количество. В общем, с проблемами не "подвиснете". Кстати, и исходный код можно посмотреть, если уж что-то совсем непонятно, а при желании можно даже частично перекомпилировать. Этого я, правда, делать не советую — расстанетесь с совместимостью. Лучше станьте контрибьютором — а там, чего доброго, дорастете до должности "платного друга Trolltech", я бы гордился. В заключение хочу сказать, что в процессе "кьютизаци" вы можете круто сэкономить на интернете — на КП-диске лежат отличные фрагменты видео, поясняющие концепции Qt, что называется, устами создателей. Там же — whitepapers. Там же — триальные инсталляшки Qt и QSA для Windows и X11. Для регистрации триала зайдите на сайт и зарегистрируйтесь без закачки, ключи придут по почте. Бесплатные версии для Линукс лучше всего закачать с сайта дистрибутива в виде пакета, чтобы не морочиться с компиляций — в последние дистро все и так наверняка включено, просмотрите список доступных пакетов, если что. Короче: надумаете начинать новый портабельный "высокооктановый" проект… ну вы уже поняли, что делать. После "пингвинизации" Китая компанией Sun (контракт на поставку 200 миллионов пингвинов…) вам будет просто скучно писать для MS Windows. Хотя, с другой стороны, Билла Гейтса тоже еще никто не отменял, так что не без этого — лучше сохранять совместимость. Кстати, кто первый создаст аутсорсинг "QTWorks.ua" — тот может реально подрубить не только у нас на районе, но и в окружающих поселениях, типа России, Германии и Кореи, поскольку в ближайшее время значение Linux-приложений во "взрослых" странах будет только расти. В статье использованы материалы и примеры с сайта trolltech.com.

    Библиотека, learning curve

    Как я уже говорил, Qt — это библиотека классов. Классов там более четырехсот, так что, если начальник требует, чтобы вы начали кодить на Qt с завтрашнего дня, то лучше послать его к ежам. Придется, как минимум, пару недель изучать доки, а лучше прочитать книжку. Я за целую ночь не смог воткнуть даже простецкие вещи. Вам тоже торопиться не советую, лучше делать все правильно — от простого к сложному. Может быть, не первая по важности, но несомненно нужная после инсталляции вещь — просмотр, распечатка, (а возможно, и татуировка на груди — вверх ногами, чтобы читать было удобно) иерархии классов Qt. Это, в общем-то, относится к любой библиотеке. Но в некоторых "системах ценностей" вы можете прожить три жизни, и проходить при этом "в лаптях", то есть использовать 3% от всех возможностей, обходя стороной то, что, собственно, и составляет ценность продукта. Пример: множество малопонятных функций в MS Exсel, масса никому не нужных ActiveX-компонент (помните, как они продавались почти по одной штучке — во цирк!) и т.д. В Qt все несколько иначе — вы в конце концов будете использовать 50-80% всех классов, так что "отбыть номер" не удастся. На приведен сильно уменьшенный вариант этой самой схемы — но вы можете распечатать и большой вариант. Он расположен по адресу и, кроме того, лежит прямо здесь. Непременно прочитайте whitepapers — в отличие от других "белок", это не просто набор рекламных лозунгов. После чтения приведенных там примеров вы даже сможете построить несколько приложений. Как маршрут для вечерних прогулок вы должны избрать посещение сайта — вас там многое заинтересует, начиная, конечно, с How To Learn Qt. Собственно, всегда лучше начинать с голого кодирования, не пользуясь автогенераторами — или пользуясь, но при этом тщательно разбирая код. В противном случае вы напишете что-то, что потом не сможете ни объяснить, ни поддержать, ни модифицировать.

    Идея

    Норвежская компания Trolltech AG (изначально называвшаяся Quasar Technologies, откуда, собственно, и пошла аббревиатура Qt) образовалась из двух (хм…) норвежцев, Эйрика Енга (Eirik Eng) и Хаварда Норта (Haavard Nort) с целью… короче, как всегда, с целью подрубить бабла. Но, кроме того, еще и создать пару нетленок из разряда универсальных библиотек, которые завоюют весь мир. Уж такой народ норвежцы — не могут без славы и денег. Дело было в 1994 г. в городе Осло. Само сочетание "Qt" некоторые произносят как "кьют" ака cute — то есть "мило, прикольно". В общем-то, так оно и есть, если, конечно, вы способны оценить красоту, выраженную в терминах С++. Говоря более конкретно, во главу угла были поставлены две вещи. Первое: библиотеки создавались для языка С++. Второе: основой всему был именно интерфейс пользователя, поскольку это самый несовместимый и позорный момент во всем софтвере — полностью разные парадигмы графического пользовательского интерфейса. Как известно, MS Windows использует "локальный" GUI API, который, однако, недавно был транспонирован на сетевые подключения в XP Remoting. Все UNIX-like, и Linux в том числе, были изначально (ну, как изначально — как только рабочие станции доросли до графики, естественно) завязаны на X Windows, который, хоть и работает от рождения по сети, но локально показывает худшие характеристики быстродействия и реактивности из всех рассматриваемых интерфейсов. Наконец, графика Mac OS, зашитая полуаппаратно в фирменных BIOS-"тулбоксах", всегда показывала скорость и красоту, даже когда Windows еще под стол пешком ходила. Но это было: а) проприетарно, б) ни с чем не совместимо — даже приблизительно. Короче — бардак и брожение умов. Вот прибалты и задумались… Точнее задумались-то не только они. В качестве переносимого графического интерфейса можно использовать хоть Java, хоть Tk/Tcl, а хоть и Macromedia Flash — на последних версиях этого Flash можно писать вполне приличные приложения. Это только те "задумавшиеся", у кого получилось,— об остальных мы просто не знаем. Да, так вот, у Trolltech тоже неплохо получилось, как именно — сейчас посмотрим.

    Инсталл

    Конечно же, сначала следует проинсталлировать систему. Под Windows нужно обратить внимание на три вещи. Во-первых, потребуется ввести имя пользователя, компанию и сериал. Вводите их точно в таком же виде, как указано в письме, которое вам придет (сериал является хеш-фанкшином от даты активации и двух остальных полей) — иначе ничего работать не будет. Во-вторых, при инсталляции предпоследним пунктом идет установка примеров. Они приходят в виде исходных текстов, но в процессе установки будут компилироваться. Компиляция происходит в командной строке, так что без установленных переменных окружения это не сработает, получите ошибку "где линкер дел, гад?". Пофиксить проблему просто: создайте и запустите bat-файл типа: call "C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\vsvars32.bat" qt-win-eval-msvc-3.3.3.exe Третья заморочка: примеры и туторы тоже так просто работать не станут. Они не могут найти свою библиотеку, хотя в переменной пути к ней все вроде и указано. На самом деле для правильной работы нужно установить переменные окружения — а они, в свою очередь, устанавливаются при загрузке профиля пользователя. Даже и перезагружаться не нужно, просто отлогоньтесь-прилогоньтесь. Кстати, о необходимости такой операции пишется в конце инсталляции — но кто же такое читает? Если вы инсталлируетесь под Linux и BSD, то я вам ничего не стану рассказывать — сами с ушами. Скажу только, что уровень проблем при инсталляции под этой ОС может колебаться в очень больших пределах, в зависимости от поддержки вашего дистрибутива и его "комплектации" на момент установки. Я ставил на Slakware, RH и Mandrake без особых проблем. В любом случае Qt нужно ставить на KDE.

    Интеграция

    Это, конечно, не бог весть что, но, тем не менее, приятно и весьма полезно — Qt во время инсталляции интегрируется в Visual Studio, так что вы сможете добавлять потом новые проекты, формы и т.д. прямо из дополнительной панельки. Кроме того, там доступно быстрое переключение на подсказку в QtAssistant — раз уж вся среда не зависит от платформы, было бы просто позором сделать справку по ее системе. В результате html’ные "хелпы" от Microsoft тут нет — вместо них используются похожие, но собственного формата и, естественно, с оболочкой на самом Qt. Интеграция Qt также хорошо интегрируется в Visual Studio, хотя это и не совсем то, для чего он создавался

    Как оно работает

    Говоря в общем, все вращается вокруг событий, называемых сигналами, и слотов — то есть регистрируемых реакций на эти события. В отличие от других "закрытых" событийных множеств (вроде тех, что встречаются в Active Script) система сигналов Qt полностью динамическая — каждый участок кода может регистрировать или обрабатывать сигналы. Это напоминает механизм, условно известный как WM_USER+1. Сигналы имеют отдаленное отношение к системным событиям и являются их произвольной трансляцией — так же как это реализовано в Delphi. Естественно, что ни одна система не должна порождать таких шквалов нотификаций, как Windows. Обеспечивать приложению доступ к событиям неклиентской области — по-моему, большая ошибка архитекторов этой системы, породившая немало "смешных" интерфейсов. Что касается графического интерфейса, то он более всего похож на Java Swing — тут тоже существуют схемы стилей, например Windows, CDE, Motif, копирующие известные оболочки. Присутствуют также layout’ы, автоматически размещающие элементы управления; кроме того, есть такие "пружинки", или "спейсеры", которые расталкивают сопредельные компоненты. Есть также немало вещей, напоминающих Delphi: хинты размеров (то есть "как было бы лучше"), масштабирование компонент и прочие вещи, претендующие на красивость. Как и в Swing, все элементы перерисованы от руки, то есть стандартные механизмы рисования элементов управления не применяются — вместо этого используется, например, GDI WinAPI. Автоматически определяется версия ОС и, соответственно, реализуются или игнорируются те или иные свойства, вроде прозрачности или XP-прибамбасов. В качестве небольшого попуска Qt использует стандартные диалоговые окна Windows, в частности диалоги открытия файла и настойки печати. Под X11 Qt не использует тулкиты вроде Motif или Xt — ну, Qt вроде и сам такой же тулкит (то есть — зачем же?). Вместо этого напрямую используется Xlib — с расширениями вроде RENDER, если они доступны. Схема "генеральной помпы", то есть основного цикла событий, не очень отличается от всех подобных во всех ОС — от MS Windows до PalmOS.
    Приложение строится просто, если не сказать примитивно: создается главное окно, устанавливаются его параметры, после чего приложение непосредственно "ранится". Вот как выглядит известный всем "Привет, мир!": #include < qapplication.h >
    #include < qlabel.h >
    int main( int argc, char **argv )
    {
    QApplication app( argc, argv );
    QLabel *hello = new QLabel( "Привет, мир!", 0 );
    app.setMainWidget( hello );
    hello->show();
    return app.exec();
    }
    Естественно, это вам не Visual Basic: вы можете создавать собственные элементы управления — это, как и в Delphi, поощряется, а не наоборот. Писать их не сложнее, чем дельфийские, а то и проще. Вот, к примеру, как реализуются LCD-часы на основе класса, отрисовывающего LCD-строку: #include < qdatetime.h >
    #include "clock.h"
    Clock::Clock( QWidget *parent, const char *name )
    : QLCDNumber( parent, name ), showingColon( true ) {
    showTime();
    startTimer( 1000 );
    }
    void Clock::timerEvent( QTimerEvent * ) { showTime(); }
    void Clock::showTime() {
    QString time = QTime::currentTime().toString().left( 5 );
    if ( !showingColon ) time[2] = ’ ’;
    display( time );
    showingColon = !showingColon;
    }
    Не сложно — а если б выключить мигание двух точек, так и вовсе тривиально. Кстати, мне показалось что знак "!" перед showingColon можно "сэкономить" — ну, конечно, заменив для мнемоничности showcolon на hidecolon. Да и showTime() в конструкторе можно бы убрать… Короче, в полном ходу лозунг Trolltech: code_less && create_more — только не стоит вычислять эту выражение по короткой схеме :).

    Кю-тэшное семейство, обзор

    На самом деле существует несколько продуктов Qt, хотя все они "завязаны" на одном и том же коде. Собственно Qt — это межплатформенная библиотека для С++. Более конкретно — библиотека классов, "цешники"-натуралы отдыхают. То есть это даже и библиотекой-то не назовешь — сами создатели именуют это как framework, то есть программная оболочка. Но, конечно, от этого она не перестает быть обычной библиотекой. Кю-тэшное семейство, обзор Применение нового "стиля" в Qt может мгновенно придать вашему приложению новый вид Помимо переносимого интерфейса библиотека также занимается интернационализацией, то есть располагает инструментом для перевода интерфейса на ваш родной китайский язык (сделано это, кстати, очень прикольно). Менеджер релизов выкусывает из кода все строки, подлежащие переводу. Они должны быть, правда, особого формата, чтоб не было накладок с непереводимыми фразами. Короче, полученные таблицы фраз поступают в средство перевода — Linguist. Сам он ничего не переводит (это делаете вы) — но зато помогает сохранять уже переведенные фразы в словарях. Так что вы можете менять программу как вам угодно, и то, что уже переведено, уже никогда не будет требовать перевода. Третье, что было сделано (кстати, уже по ходу разработки — по просьбе многих клиентов), это системно-независимые механизмы доступа к базам данных. Конечно, я уже не говорю о таких вещах, как унифицированный сетевой доступ, XML, трехмерная графика и так далее — просто различия в этих сферах не так разительны, как в перечисленных выше. Выходит, что Qt — это как бы "мегапатч" к стареньким си-либам, позволяющий:
  • работать в графике;
  • переводить программы;
  • выполнять запросы к БД;
  • унифицированно реализовывать все остальное, вроде мультимедиа.
  • Помимо основной библиотеки, существует основанный на том же коде framework для всякой мобильной живности вроде смарт-фонов и PDA под управлением Embedded Linux (особенно засветился в этом секторе Sharp). Называется эта штуковина Qtopia. Инструмент для разработки встроенных систем с ограниченной функциональностью называется Qt/Embedded. Далее, QSA — скриптовый язык, позволяющий управлять готовыми приложениями на Qt/C и расширять их возможности в скриптовом контексте. Teambuilder — средство компиляции, которое собирает статистику утилизации CPU по целой рабочей группе и позволяет оптимизировать процесс сборки на менее загруженных станциях. Ко всему, существует несколько "плугов" для известных сред разработки (говоря проще — для Visual Studio и Borland Builder), упрощающих разработку под Qt,— Qt Solutions. В качестве визуального инструмента разработки используется Qt Designer — это "малевалка" интерфейса, подобная всем "клик-энд-го". Конечно, еще полно полезного хелпняка: просто тысячи артиклов, плюс девяносто шесть (проверьте) примеров — и это есть гуд. Однако сосредоточимся на Qt — все остальное вторично.

    QT: интерфейс, и не только...

    Арсений Чеботарев, Если вы связаны с программированием, то наверняка уже встречали сочетание Qt. Многие (0,1%) из вас даже использовали Qt в своих разработках, пусть даже как следствие инсталляции Borland Kylix. В общем, самое время посмотреть на Qt, что называется, "в упор, двумя глазами"… Этот продукт на слуху у всех разработчиков, которые программируют для Linux. И не только у них — например, самый модный и удобный многоплатформенный клиент для сетей AIM/ICQ (точнее, ICQ/AIM), sim, написанный питерцем Володей Шутовым, создан именно с использованием Qt. Не говоря уже о Borland Kylix — там вообще все работает поверх Qt. Такие совпадения — там вилочка, тут ножичек — поневоле задумаешься…

    Тестирование софта - статьи

    Адаптация средств инструмента CTesK к предложенной схеме тестового набора

    В используемом тестовом стенде у инструментального и целевого узла имеется по одному сетевому интерфейсу, которые соединены кросс-кабелем, поэтому пакеты в процессе доставки не перемешиваются. Поскольку в большинстве случаев обмены сообщениями между мобильным узлом и демонами являются синхронными, порядок поступления сообщений может изменяться лишь в том случае, если два сообщения отправляются одним и тем же узлом и являются частью разных параллельно выполняющихся процедур обмена сообщениями. Стандарт RFC 3775 не накладывает никаких ограничений на связь между параллельно выполняющимися процедурами, поэтому такое нарушение порядка не может повлиять на выносимый тестовым сценарием вердикт. Это позволяет в разрабатываемом тестовом наборе не использовать сериализацию. Точнее, при сериализации рассматривается лишь один порядок поступления стимулов и реакций. В выбранной модели тестирования используются две группы стимулов: стимулы, действующие на виртуальную сеть, и стимулы, действующие на тестируемую реализацию. Сбор пакетов не приостанавливается между вызовами сценарных функций, поэтому пакеты, собранные за время обработки результатов вызова сценарной функции, будут рассматриваться после вызова следующей сценарной функции. Это означает, что отключение сериализации приводит к нарушению порядка поступления стимулов. Однако порядок вызова стимулов, воздействующих на тестируемую реализацию, будет сохраняться, а стимулы, воздействующие на сеть, не изменяют структур данных, имитирующих состояние тестируемой реализации. Поэтому такое нарушение порядка поступления стимулов не повлияет на вердикт, выносимый спецификациями. Стандарт RFC 3775 представляет собой обычный текст, из которого можно выделить несколько сотен четких требований. При построении формальных спецификаций в виде пред- и постусловий для сообщений от тестируемой реализации может оказаться, что некоторые постусловия содержат проверку для десятков требований стандарта. Инструмент CTesK в случае нарушения одного из таких требований выдаст немногословный вердикт false.
    Однако по сообщению, на котором был выдан такой вердикт, не всегда просто понять, какое же именно требование стандарта было нарушено. Чтобы решить эту проблему, введена вспомогательная функция conformance_check, осуществляющая проверку требования стандарта и сбрасывающая в тестовую трассу информацию о результате его проверки, а также о ситуации, в которой эта проверка производилась. По такой информации впоследствии генерируется детальный отчет о проверенных требованиях стандарта. Элементарной реакцией от целевой системы в рассматриваемой модели тестирования является пакет, но многие требования стандарта применяются не ко всему пакету, а к отдельным его заголовкам. Проверку таких требований в инструменте CTesK принято осуществлять в инвариантах типов данных, описывающих заголовки пакета. Однако в тестовом наборе используются одни и те же типы данных для заголовков, содержащихся в пакетах от мобильного узла и в пакетах от демонов. Проверка же должна производится только для пакетов от мобильного узла, так как некорректно построенные заголовки в пакетах от демонов допускаются и используются при тестировании внештатных ситуаций. Поэтому проверка таких требований вынесена за пределы спецификационной функции и происходит еще до регистрации реакций на этапе разбора пакета по заголовкам. Для идентификации пакета в отчете удобно использовать время, когда был получен пакет. Время получения передается стимулам и реакциям вместе с другой информацией о пакете через аргумент спецификационной функции или реакции соответственно. Однако инструмент CTesK не позволяет использовать в предусловии значение аргумента передаваемого реакции, поэтому проверку всех требований пришлось вынести в постусловие реакций. Следует также отметить, что отчеты, которые может генерировать инструмент CTesК, в данной работе не используются. Вместо этого, отчет о проведенном тестировании генерируется отдельной частью тестового набора по тестовой трассе и нескольким вспомогательным файлам. Такие отчеты ориентированы на конкретную задачу, более наглядны и понятны пользователю, не знакомому с технологией UniTESK.

    Аннотация.

    Статья посвящена разработке тестового набора для проверки соответствий реализаций мобильного узла спецификациям протокола Mobile IPv6 []. Для построения тестового набора использовались передовая технология автоматического тестирования UniTESK [] и инструмент CTesK [], разработанный на основе этой технологии. В ходе выполнения работы было выявлено несколько особенностей поведения одного из объектов протокола - мобильного узла, которые затрудняют его тестирование в рамках указанной технологии. В статье подробно описаны эти особенности и способы преодоления трудных моментов в условиях ограничений технологии UniTESK и поддерживающего эту технологию инструмента CTesK. Работа выполнялась в Институте системного программирования РАН в рамках проекта "Верификация функций безопасности и мобильности протоколов IP" при поддержке гранта РФФИ № 04-07-90308.

    Апробация разработанного тестового набора

    При помощи разработанного тестового набора было проведено тестирование открытой реализации Mobile IPv6 KAME для операционной системы FreeBSD 5.4 (CVS Snapshot 2006.11.13). Во время проведения тестирования эта реализация была одной из наиболее распространенных открытых реализаций Mobile IPv6. При прогоне тестовых сценариев был выявлен ряд нарушений требований стандарта RFC 3775 [], например:
  • мобильный узел выполняет процедуру Duplicate Address Detection не во всех случаях, когда это необходимо;
  • мобильный узел при смене точки подключения не всегда производит регистрацию основного временного адреса на домашнем агенте;
  • мобильный узел нарушает ограничения темпа отправления сообщений Binding Update;
  • мобильный узел не соблюдает некоторые рекомендации процедуры Generic Movement Detection;
  • мобильный узел не всегда использует корректные значения для времени ожидания ответа на такие сообщения, как Binding Update, Home Test Init, Care-of Test Init, Mobile Prefix Solicitation. Помимо этого, в некоторых случаях поведение тестируемого мобильного узла было заведомо некорректным, например, некоторые сообщения отправлялись на неправильный адрес канального уровня. Кроме того, исполнение некоторых тестовых сценариев приводило к зависанию или перезагрузке мобильного узла, что свидетельствует о нестабильности целевой реализации. Результаты проведенного тестирования с указанием нарушенных требований стандарта доступны по этому адресу.

    Инструмент CTesK

    Для построения тестового набора в данной работе использовался инструмент CTesK, который позволяет эффективно распараллелить разработку тестового набора и автоматизировать разработку тестовых сценариев. Ниже приведено краткое описание возможностей этого инструмента и обзор технологии UniTESK, которую он реализует. В инструменте CTesK объектом тестирования является целевая система. У этой системы имеется набор интерфейсов, с помощью которых она взаимодействует с окружением. Набор этих интерфейсов изначально определен и специфицирован. Все требования, которые предъявляются к системе, накладываются на наблюдаемые действия системы, которые можно отследить через интерфейсы ее взаимодействия с окружением. Эти требования не затрагивают внутренней реализации системы, т.е. тестирование производится по принципу "черного ящика". При использовании инструмента CTesK требования, предъявляемые к целевой системе, должны быть описаны на формальном языке SeC, который является спецификационным расширением языка C. Впоследствии из таких требований автоматически генерируется исполняемый код, который используется в процессе тестирования. Взаимодействие с целевой системой осуществляется при помощи стимулов и реакций. Стимулами называются средства, при помощи которых можно передавать целевой системе какую-либо информацию. Целевая система может непосредственно возвращать некоторый ответ на стимул, а также изменять свое внутреннее состояние. Такой ответ и изменение состояния, если имеется доступ к состоянию системы, возвращаются в тестовую систему, и производится проверка соответствия поведения системы наложенным на нее требованиям. Требования описываются на формальном языке в виде постусловий. Для стимулов в виде предусловия описываются ограничения, при которых можно вызывать данный стимул. Дополнительно могут накладываться ограничения на возможные значения данных определенного типа в виде инвариантов. Некоторые следствия работы системы и результаты воздействия на нее определенных стимулов могут наблюдаться не сразу, а после завершения внутренних процессов.
    Такое поведение приводит к тому, что реакция на стимул поступает наружу через какое-то время после воздействия на систему одного или нескольких стимулов, или может даже проявляться без воздействия стимулов на систему. Такую информацию не во всех случаях можно охарактеризовать как ответ на какой-либо стимул, и она называется реакцией (отложенной реакцией) системы. На реакции системы обычно также накладывается ряд требований. Аналогично случаю со стимулами, в инструменте CTesK они описываются в виде пред- и постусловий. Предусловия определяют ограничения на состояния системы, при которых может происходить данная реакция. А постусловия описывают ограничения на саму реакцию, то есть на данные, которые передаются окружению при помощи этой реакции, и на изменение состояния целевой системы, которое может происходить при возникновении такой реакции. Для описания требований к стимулам и реакциям тестовый набор должен обладать информацией о состоянии целевой системы и о его изменениях. Для этого тестовым набором поддерживаются структуры данных, моделирующие это состояние. Далее состояние этих структур данных будет называться модельным состоянием. Если имеется доступ к внутренним структурам данных целевой системы, описывающих это состояние, то возможна синхронизация состояния модели и целевой системы. В таком случае возможна проверка требований, касающихся изменения состояния внутренних структур данных. В противном случае состояние модели и состояние целевой системы изменяются независимо и синхронизируются лишь на начальном этапе тестирования. При этом требования накладываются только на действия системы, наблюдаемые извне. Нарушение требований обработки внутренних структур данных проверяются лишь неявно при последующих некорректных ответах на стимулы и возникновении некорректных реакций. Тестовый набор, разрабатываемый при помощи инструмента CTesK, может быть ориентирован не на конкретную целевую систему, а на группу целевых систем, поведение которых описывается одними и теми же требованиями.


    Причем некоторые целевые реализации могут предоставлять доступ к внутренним структурам данных, а другие нет. Для обеспечения универсальности в тестовый набор включается специальная часть, которая называется медиатором. Для каждого отдельного стимула медиатор осуществляет преобразование данных из модельного представления тестового набора в представление целевой системы, а для каждой реакции выполняет обратное преобразование. Таким образом, медиатор зависит от конкретной целевой системы. Поэтому, для того чтобы произвести тестирование новой целевой системы, необходимо заново разработать для нее медиаторы. Вся информация о ходе тестирования сохраняется в специальном файле, который называется тестовой трассой. По данным, которые содержатся в этом файле, генерируется отчет об ошибках и покрытии. Трасса хранится в формате XML, что позволяет легко разрабатывать средства для ее анализа. В инструменте CTesK имеются средства для автоматического измерения тестового покрытия. Для этого в описание стимула добавляется специальный блок coverage, в котором, исходя из модельного состояния и параметров вызова стимула, определяется, какую из ветвей функциональности целевой системы задействует данный стимул. Инструмент CTesK включает в себя утилиту, которая после завершения тестирования по информации, содержащейся в тестовой трассе, генерирует подробную статистику и отчет о тестовом покрытии. Стимулы и реакции на языке SeC описываются в виде спецификационных функций. Для стимула такая спецификационная функция включает в себя предусловие, постусловие и блок coverage. Для реакции - только предусловие и постусловие. Для каждой спецификационной функции разрабатывается медиатор, который оформляется в виде функции. Медиатор для стимула имеет два блока: блок call, который отвечает за передачу соответствующего стимула реализации и за получение результата, и блок state, который отвечает за обновление модельного состояния. Для реакции медиатор содержит только блок state, который выполняет аналогичную функцию. В инструменте CTesK термин тестовый сценарий используется для обозначения группы тестовых операций, обычно предназначаемых для тестирования логически связанных аспектов функциональности целевой системы.


    Если в ходе исполнения тестового сценария обнаруживается ошибка, то тестовый сценарий завершается. Такая мера является вынужденной, так как после возникновения ошибки состояния тестового набора и целевой системы оказываются не синхронизированными. Выполнение тестового сценария заключается в вызовах сценарных функций. Сценарная функция содержит исполняемый код и вызовы стимулов. В ходе выполнения сценарной функции собираются реакции от системы (см. Рис. 4). После завершения сценарной функции запускается механизм, называемый сериализацией. В процессе сериализации перебираются возможные порядки вызовов стимулов и наступления реакций (см. Рис. 5). Чтобы поведение целевой системы могло считаться корректным, требуется, чтобы хотя бы один из порядков удовлетворял требованиям в виде пред- и постусловий стимулов и реакций. Инструмент CTesK Рис. 4. Исполнение сценарной функции

    Рис. 5. Один из сценариев сериализации В процессе проверки одного из порядков сериализации тестовый сценарий может проходить через последовательность состояний модели. Такие промежуточные состояния называются нестационарными. Вызов сценарной функции должен обязательно закончиться в стационарном состоянии, иначе поведение целевой системы является некорректным. Кроме того, все допустимые последовательности сериализации, которые заканчиваются в стационарном состоянии, должны приводить к одному и тому же состоянию. Иначе выполнение тестового сценария будет прервано с сообщением об ошибке. В функциях, выполняющих сериализацию, используются методы сохранения состояния модели, восстановления состояния модели, а также определения, является ли данное состояние стационарным. Если имеется информация о частичном порядке вызовов стимулов и поступления реакций, то можно существенно сократить процесс сериализации по сравнению с полным перебором или даже свести ее к рассмотрению одного единственного варианта. В инструменте CTesK предусмотрены средства для разбиения множества модельных состояний на группы. Такое разбиение называется факторизацией состояний, а полученные обобщенные состояния - состояниями тестового сценария.


    На выбор факторизации накладывается следующее ограничение: после вызова определенной сценарной функции из определенного начального состояния тестового сценария тестовый сценарий всегда должен перейти в одно и то же конечное состояние. Пример факторизации модельных состояний схематично представлен на Рис. 6. Инструмент CTesK Рис. 6. Переходы в модельных состояниях и состояниях тестового сценария Для осуществления факторизации разработчик должен предоставить тестовому сценарию функцию, которая по текущему модельному состоянию возвращает состояние тестового сценария. При разработке тестового сценария разработчик определяет начальное модельное состояние и набор сценарных функций. Тестовый сценарий автоматически выбирает порядок вызовов сценарных функций. Такую функциональность осуществляет компонент под названием обходчик. Обходчик перебирает вызовы всех возможных сценарных функций во всех достижимых состояниях тестового сценария. Это осуществляется при помощи обхода графа, вершинам которого соответствуют состояния тестового сценария, а дугам - переходы по вызовам сценарных функций. Такой граф задается для тестового сценария неизбыточным образом путем указания начального состояния тестового сценария, набора сценарных функций и функции, определяющей состояние тестового сценария после выполнения сценарной функции. Инструмент CTesK включает в себя два обходчика: dfsm [], который реализует обход в глубину графа состояний тестового сценария, и ndfsm [], использующий жадный алгоритм для выбора ближайшей вершины, в которой применялись не все возможные воздействия. На практике второй обходчик оказывается более эффективным, поэтому он и использовался в разрабатываемом тестовом наборе. Для корректной работы обоих обходчиков граф состояний тестового сценария должен удовлетворять требованиям связности и детерминированности. Связность в данном случае может формулироваться следующим образом: из любого состояния тестового сценария, в которое можно попасть из начального состояния при помощи последовательности вызовов сценарных функций, можно вернуться в начальное состояние при помощи последовательности вызовов сценарных функций.


    Условие детерминированности означает, что при вызове одной и той же сценарной функции из одного и того же состояния тестового сценария мы всегда попадаем в одно и тоже конечное состояние тестового сценария. Обходчик в своей работе может пользоваться компонентом под названием итератор. Итератор используется, если в сценарной функции на разных этапах ее запуска необходимо вызывать стимулы с различными параметрами. Такую функциональность можно осуществить при помощи написания отдельной сценарной функции для каждой группы параметров, но в таком случае может понадобиться очень большое количество сценарных функций. В языке SeС используется специальная конструкция iterate для перебора параметров стимула. Синтаксис этой конструкции напоминает синтаксис оператора цикла for. Счетчик цикла для этой конструкции называется счетчиком итерации. Таким образом, когда обходчик встречает в коде сценарной функции такую синтаксическую конструкцию, он вызывает итератор. Итератор возвращает значения счетчиков итерации для вызова данной сценарной функции так, чтобы в каждом состоянии тестового сценария осуществлялся перебор всех возможных наборов счетчиков итерации. С использованием итератора обходчик вызывает все сценарные функции со всеми возможными значениями счетчиков итерации во всех достижимых состояниях тестового сценария. Такая автоматизация существенно упрощает разработку тестового сценария.

    Данный раздел является кратким введением

    Данный раздел является кратким введением в протокол Mobile IPv6. Как уже отмечалось, этот протокол является расширением протокола нового поколения IPv6, которое позволяет узлу, совершающему перемещения по сети Интернет, все время оставаться доступным по адресу, который он имел в первоначальной (домашней) сети. Такая возможность позволяет избежать потери соединений транспортного уровня при перемещениях межсетевого уровня модели TCP/IP, а также дает возможность другим узлам устанавливать соединение с мобильным узлом (Mobile Node) в то время, когда он находится за пределами домашней сети. Для простоты мы будем рассматривать случай, когда у мобильного узла есть только один домашний адрес. Поведение мобильного узла в случае нескольких домашних адресов можно считать аналогичным. Когда мобильный узел находится в домашней сети, никакой дополнительной функциональности от узлов, участвующих во взаимодействии, не требуется, и мобильный узел ведет себя как обычный IPv6-узел (см. Рис. 1). Когда мобильный узел переходит в сеть, которая обслуживается другими маршрутизаторами и имеет другие сетевые префиксы, без дополнительной поддержки со стороны Mobile IPv6 узел становится недоступным по своему домашнему адресу. Пакеты, отправляемые ему другими узлами, будут по-прежнему приходить в домашнюю сеть и не будут доходить до мобильного узла. Это приведет к потере установленных соединений. Рис. 1.Мобильный узел в домашней сети Для того чтобы этого не происходило, в протоколе Mobile IPv6 вводится специальный вид маршрутизаторов, которые называются домашними агентами (Home Agents). Домашний агент (см. Рис. 2) всегда находится в домашней сети и осуществляет переадресацию пакетов мобильному узлу в то время, когда последний находится за пределами домашней сети. Это осуществляется при помощи механизма, который называется двусторонним туннелированием (Bi-directional Tunneling). Рис. 2. Мобильный узел вне дома Структура IPv6-пакета подробно описана в RFC 2460 []. Однако здесь следует отметить, что в протоколе IPv6 в пакет могут добавляться заголовки расширения, которые содержат некоторую дополнительную информацию о том, как этот пакет должен обрабатываться конечным получателем или промежуточными узлами сети.
    Заголовки расширения связаны в односвязный список при помощи поля Next Header, которое содержится во всех заголовках уровня IP. Поле Next Header последнего заголовка расширения IPv6-пакета указывает на протокол транспортного уровня, сегмент которого передается в качестве полезной нагрузки. Каждый из заголовков расширения имеет фиксированную часть, которая всегда должна присутствовать в заголовке данного типа, и может содержать дополнительную часть в виде опций. Опции располагаются в конце заголовка и описываются в представлении TLV (Type-Length-Value). Когда мобильный узел попадает во внешнюю сеть, он формирует один или несколько временных адресов (Care-of Addresses) согласно механизму автоматической конфигурации адресов [], который используется во внешней сети. После этого мобильный узел выбирает один из своих временных адресов и сообщает его домашнему агенту; такой адрес называется основным временным адресом (Primary Care-of Address). Это осуществляется при помощи процедуры, которая называется регистрацией основного временного адреса (Primary Care-of Address Registration). Для этого мобильный узел отправляет домашнему агенту служебное сообщение Binding Update со своего основного временного адреса. В этом сообщении мобильный узел при помощи заголовка расширения Destination Options, содержащего опцию Home Address, указывает свой домашний адрес, для которого выполняется данная процедура. В ответ на сообщение Binding Update домашний агент отправляет служебное сообщение Binding Acknowledgement, которое либо свидетельствует о том, что сообщение Binding Update принято, либо указывает причину, по которой оно было отклонено. Для обеспечения безопасности такого обмена сообщениями используются средства протокола IPsec. Перед отправлением сообщения Binding Acknowledgement домашний агент запоминает связь между домашним адресом мобильного узла и его основным временным адресом в структуре данных, которая носит название кэш привязок (Binding Cache), а сама связь называется привязкой (binding). После установления привязки домашний агент начинает перехватывать пакеты, которые приходят на домашний адрес мобильного узла, и через IPv6-туннель пересылать их мобильному узлу на его основной временный адрес. Такой перехват пакетов, направленных мобильному узлу, осуществляется при помощи механизма, называемого Proxy Neighbor Discovery.


    Суть этого механизма заключается в том, что домашний агент отправляет необходимые сообщения Neighbor Advertisement, используемые в механизмах Address Resolution и Neighbor Unreachability Detection, от имени мобильного узла, но со своим адресом канального уровня. Таким образом, мобильный узел, находясь в чужой сети, через IPv6-туннель с домашним агентом, получает сообщения, которые отправляются ему в домашнюю сеть. Мобильный узел, в свою очередь, поддерживает структуру данных, которая называется Binding Update List. В этой структуре данных содержится информация обо всех привязках и об их состоянии. Перед отправлением пакета в сеть мобильный узел просматривает Binding Update List. Если там есть привязка, для которой зарегистрированный домашний адрес совпадает с адресом отправителя пакета (Source Address), то пакет отправляется не напрямую, а через IPv6-туннель с домашним агентом, указанным в привязке. Домашний агент, получив из IPv6-туннеля такой пакет, отправляет его из домашней сети по обычному маршруту. Таким образом, мобильный узел всегда остается доступным по своему домашнему адресу. Узел, с которым мобильный узел обменивается сообщениями, называется узлом-корреспондентом (Correspondent Node). Для обмена информацией с мобильным узлом при помощи двустороннего туннелирования от узла-корреспондента требуется лишь поддержка базовой функциональности IPv6. Дополнительной поддержки Mobile IPv6 от узла-корреспондента в этом случае не требуется. Однако двустороннее туннелирование создает дополнительный обмен сообщениями и дополнительную нагрузку на домашнего агента. Протокол Mobile IPv6 позволяет этого избежать и производить обмен информацией напрямую (не через домашнюю сеть), если узел-корреспондент поддерживает дополнительную функциональность Mobile IPv6, которая называется оптимизацией маршрута (Route Optimization). Узел-корреспондент, поддерживающий оптимизацию маршрута, так же как и домашний агент, имеет кэш привязок, в котором содержится информация о привязках с мобильными узлами и об их состояниях.


    У мобильного узла, в свою очередь, в структуре Binding Update List содержится информация о привязках с узлами-корреспондентами. Перед отправлением в сеть пакета, у которого адрес отправителя совпадает с домашним адресом, мобильный узел просматривает Binding Update List, и если находит привязку с узлом-корреспондентом, адрес которого совпадает с адресом получателя пакета, то пакет отправляется напрямую. При этом в качестве адреса отправителя пакета указывается временный адрес, а в сообщение добавляется заголовок расширения Destination Options, который содержит опцию Home Address с домашним адресом мобильного узла. Если же такой привязки нет, то мобильный узел ищет привязку с домашним агентом и отправляет пакет через туннель домашнему агенту, который пересылает его узлу-корреспонденту. При получении пакета с опцией Home Address узел-корреспондент ищет в кэше привязок запись, у которой временный адрес совпадает с адресом отправителя, указанным в пакете, а домашний адрес совпадает с адресом, указанным в опции Home Address. Если такая привязка находится, то узел-корреспондент меняет местами адрес отправителя пакета и адрес, указанный в опции Home Address; затем пакет передается на дальнейшую обработку. При отправлении пакета в сеть узел-корреспондент просматривает кэш привязок и ищет там привязку, у которой домашний адрес мобильного узла совпадает с адресом получателя пакета (Destination Address). Если такая привязка находится, то узел-корреспондент подставляет временный адрес, указанный в привязке, вместо адреса получателя, и в пакет добавляется заголовок расширения Routing Header Type 2, в котором указывается домашний адрес мобильного узла. При получении такого пакета мобильный узел проверяет, что адрес, указанный в заголовке Routing Header Type 2, является его домашним адресом, и меняет местами адрес получателя пакета с адресом, указанным в данном заголовке. Затем пакет передается на дальнейшую обработку. Такой механизм обмена сообщениями между мобильным узлом и узлом-корреспондентом (см.


    Рис. 2) является прозрачным для протоколов верхнего уровня и носит название оптимизации маршрута. Механизм установления привязки с узлом-корреспондентом аналогичен механизму установления привязки с домашним агентом. Для этого мобильный узел отправляет сообщение Binding Update узлу-корреспонденту, в котором содержатся домашний и временный адрес. В некоторых случаях узел-корреспондент должен отправить в ответ сообщение Binding Acknowledgement, подтверждающее установление привязки, хотя обычно этого не требуется. Для обеспечения безопасности механизма установления привязки с узлом-корреспондентом в сообщения Binding Update и Binding Acknowledgement добавляется опция Binding Authorization Data, которая содержит криптографическую хэш-сумму этих сообщений. Хэш-сумма вычисляется по алгоритму HMAC_SHA1 с использованием ключа управления привязкой (Kbm), который генерируется перед обменом сообщениями Binding Update и Binding Acknowledgement при помощи процедуры обратной маршрутизируемости (Return Routability Procedure). Данный раздел является кратким введением Рис. 3. Процедура обратной маршрутизируемости (return routability procedure) Процедура обратной маршрутизируемости выполняется с целью аутентификации узла-корреспондента. Для этого обмен сообщениями с узлом-корреспондентом производится по двум маршрутам: напрямую и через домашнюю сеть (см. Рис. 3). А именно, мобильный узел посылает со своего временного адреса сообщение Home Test Init через туннель с домашним агентом, а сообщение Care-of Test Init - напрямую. В ответ на эти сообщения узел-корреспондент посылает сообщения Home Test на домашний адрес и Care-of Test на временный адрес мобильного узла; эти сообщения содержат маркеры home keygen token и care-of keygen token соответственно. При помощи этих маркеров и генерируется ключ управления привязкой (Kbm). Протокол Mobile IPv6 включает в себя несколько вспомогательных служебных процедур обмена сообщениями для мобильного узла: Generic Movement Detection, Dynamic Home Agent Address Discovery и Mobile Prefix Discovery.Эти процедуры используют сообщения протокола ICMPv6. Процедура Generic Movement Detection позволяет отследить перемещение мобильного узла при помощи средств протокола Neighbor Discovery []. Для двух оставшихся процедур вводятся четыре новых типа сообщений протокола ICMPv6 []. Процедура Dynamic Home Agent Address Discovery позволяет узнать адрес домашнего агента в то время, когда мобильный узел находится за пределами домашней сети. Процедура Mobile Prefix Discovery позволяет мобильному узлу узнать об изменении префиксов в домашней сети в то время, когда мобильный узел находится за ее пределами, и, тем самым, принимать участие в смене префиксов домашней сети (Network Renumbering).

    Труды Института системного программирования

    Зацепин Д.В., Шнитман В.З.,
    Труды Института системного программирования РАН

    Проблемы тестирования мобильного узла и способы их решения

    Реализация функциональности мобильного узла интегрируется в IPv6-стек, и воздействовать на нее можно двумя способами: при помощи отправления сообщений с транспортного уровня и при помощи сообщений с канального уровня. Кроме того, в конкретных реализациях Mobile IPv6 могут предоставляться средства для чтения или изменения внутренних структур данных, описывающих состояние реализации. При сравнении двух различных реализаций эти интерфейсы могут существенно отличаться, так как стандарт не накладывает на них каких-либо ограничений. В некоторых реализациях Mobile IPv6 таких интерфейсов может вообще не быть, поэтому при разработке тестового набора было принято решение не использовать взаимодействие с целевой реализацией при помощи интерфейсов доступа к внутренним управляющим структурам данных. Более того, с целью построения универсального тестового набора, а также для упрощения синхронизации и анализа результатов, полученных в ходе исполнения сценарных функций, воздействия на целевую реализацию при помощи сообщений с транспортного уровня также не используются. Единственный оставшийся способ взаимодействия заключается в обмене сообщениями через сетевой интерфейс. Таким образом, в принятом подходе воздействиями на тестируемую реализацию, или стимулами являются сообщения со стороны тестирующего узла, а ответами реализации или реакциями - сообщения со стороны мобильного узла. Для поддержания постоянной достижимости мобильного узла по его домашнему адресу протокол Mobile IPv6 содержит ряд служебных процедур обмена сообщениями между мобильным узлом и домашним агентом или между мобильным узлом и узлом-корреспондентом, например, процедуру регистрации на домашнем агенте (Home Registration), процедуру регистрации на узле-корреспонденте (Correspondent Registration), процедуру обратной маршрутизируемости (Return Routability Procedure) и так далее. Заметим, что мобильный узел является инициатором выполнения большинства служебных процедур протокола Mobile IPv6. Это создает некоторые дополнительные сложности при организации тестирования.
    В соответствии с требованиями стандарта RFC 3775 мобильный узел должен инициировать такие процедуры либо после смены точки подключения, либо по истечении определенного временного интервала после какого-либо события, хотя некоторые служебные обмены сообщениями могут инициироваться мобильным узлом в любое время. Таким образом, единственное явное воздействие, которое может заставить мобильный узел начать служебные обмены сообщениями, - это смена точки подключения мобильного узла. Для упрощения схемы тестового стенда и возможности создания для реализации мобильного узла некоторых внештатных ситуаций было принято решение об имитации процесса смены точки его подключения. При этом фактической смены точки подключения мобильного узла не происходит. Тестовый стенд состоит из двух узлов, соединенных сегментом Ethernet. Один из узлов - тестируемый узел. На тестируемом узле статически настраивается и запускается целевая реализация мобильного узла. Второй узел является инструментальным. На этом узле функционирует тестовый набор, который имитирует для тестируемой реализации некоторую виртуальную сеть. Смена точки подключения мобильного узла имитируется при помощи вспомогательных сообщений Router Advertisement. Такие сообщения периодически отправляются тестовым сценарием от имени маршрутизатора в той сети, в которой мобильный узел находится в данный момент времени. Для имитации смены точки подключения мобильного узла тестовый сценарий прекращает отправление сообщений Router Advertisement от имени маршрутизатора в текущей сети и начинает отправление аналогичных сообщений от имени маршрутизатора в новой сети. Для реализации описанной выше модели тестирующий узел должен имитировать функциональность всех узлов, которые принимают участие в обмене сообщениями с мобильным узлом в модели виртуальной сети, т.е. отправлять все необходимые сообщения от имени узлов виртуальной сети. При этом некоторые из таких сообщений являются частью определенных процедур и должны отправляться синхронно в ответ на сообщения от мобильного узла. Не углубляясь в детали, поведение мобильного узла в целом можно охарактеризовать следующим образом.


    При смене точки подключения мобильный узел должен выполнить несколько служебных обменов сообщениями с домашним агентом и узлами-корреспондентами. В результате этих обменов будут установлены необходимые привязки, и мобильный узел станет доступным по своему домашнему адресу. После этого мобильный узел начинает служебные обмены сообщениями только перед истечением времени жизни привязки, домашнего адреса или временного адреса. Заметим, что после перехода в новую сеть мобильный узел может выполнять некоторые из процедур обмена сообщениями параллельно, что делает его поведение недетерминированным. Например, процедура регистрации на узле-корреспонденте может выполняться параллельно с процедурой Mobile Prefix Discovery. Кроме того, стандарт допускает недетерминированное поведение мобильного узла в рамках одной служебной процедуры. Например, перед регистрацией на узле-корреспонденте мобильный узел может выполнить процедуру обратной маршрутизируемости либо полностью, либо частично, а в некоторых случаях может ее не выполнять вообще. Некоторые сообщения тестируемая реализация должна отправлять через определенное время после поступления стимула. Из-за таких особенностей поведения мобильного узла возникают затруднения с определением группы сообщений, которые можно охарактеризовать как воздействие и отклик на него. Такая группа может охватывать несколько служебных процедур, и для нее будет сложно построить спецификации методом Design-by-Contract []. Для того чтобы избежать таких сложностей, используется механизм отложенных реакций инструмента CTesK, который позволяет не указывать в явном виде связь между стимулом и реакцией. Заметим также, что инструмент CTesK, который использовался для разработки тестового набора, позволяет проводить тестирование лишь с использованием стационарных модельных состояний. Однако мобильный узел не имеет стационарных состояний за исключением некоторых внештатных ситуаций. Для преодоления этого ограничения исследовалась возможность применения двух различных подходов. Первый подход заключался в использовании при работе обходчика состояний, близких к стационарным.


    Если для привязок и адресов использовать достаточно большие значения времени жизни, то состояние мобильного узла, после смены точки подключения и завершения служебных процедур, можно рассматривать как "приближенно" стационарное, которое пригодно для используемого метода тестирования. Слово "приближенно" используется потому, что такие состояния в действительности не являются стационарными. Для того чтобы мобильный узел находился в таком состоянии длительное время, он должен периодически получать от маршрутизатора сообщения Router Advertisement и выполнять процедуру Neighbor Unreachability Detection. Для обхода графа таких состояний можно ввести группу стимулов, которые производят смену точки подключения мобильного узла. Такие стимулы будут отличаться от отдельных сообщений, отправляемых тестовым сценарием, так как будут действовать на всю виртуальную сеть, а не на мобильный узел в отдельности. При воздействии таким стимулом состояние тестируемой реализации явно не изменяется, а изменяется лишь состояние виртуальной сети в целом. Но благодаря обмену сообщениями между узлами виртуальной сети впоследствии изменится и состояние тестируемой реализации. После воздействия одним из таких стимулов мобильный узел должен через некоторое время перейти в приближенно стационарное состояние, а значит, такие стимулы уже можно использовать в инструменте CTesK. Такой подход использовался в ранней версии тестового набора, который проверял только установление привязок с домашним агентом. Однако при расширении этого тестового набора возникли дополнительные трудности, и от использования таких приближенно стационарных состояний пришлось отказаться. Первая проблема заключается в том, что стандарт RFC 3775 не содержит исчерпывающего описания ограничений на то, какие сообщения мобильный узел может отправлять, находясь в определенном состоянии. Более того, стандарт указывает, что некоторые служебные процедуры, такие как формирование временного адреса и регистрация на узле-корреспонденте, мобильный узел может начать в любой момент времени. Вторая проблема возникает, когда при тестировании мобильного узла необходимо использовать небольшие значения времени жизни для привязок.


    Из- за недетерминированного поведения мобильного узла сложно предсказать время, когда привязка будет установлена, а значит и время, когда реализация мобильного узла должна отправить сообщение Binding Update для обновления активно используемой привязки, время жизни которой вскоре истечет. При использовании небольших значений времени жизни привязки такая реакция может возникнуть во время анализа результатов выполнения сценарной функции, что в свою очередь может привести к ошибке в работе обходчика инструмента CTesK. В результате из-за описанных выше проблем от использования стационарных модельных состояний целевой реализации пришлось отказаться. Основные причины использования стационарного тестирования заключаются в следующем:
  • Использование стационарных состояний позволяет проверить, что тестируемая реализация действительно выполнила все необходимые действия, которые были определены спецификацией.
  • Обходчик может работать лишь со стационарными состояниями. Если же исполнение сценарной функции завершается в нестационарном состоянии, то за время, которое необходимо обходчику для начала выполнения следующей сценарной функции, система может перейти в другое состояние. Это может привести к неполному обходу графа модельных состояний или вынесению вердикта о недетерминированном поведении целевой реализации. Второй подход заключается в отказе от стационарного тестирования. При этом необходимо искусственным образом имитировать работу обходчика для случая стационарного тестирования и следить за выполнением всех необходимых действий, которые определены спецификацией. Чтобы проверить, что тестируемая реализация действительно отправила все необходимые сообщения, используется список действий, которые должна произвести система. Действием является отправление целевой реализацией сообщения определенного типа с определенными значениями некоторых полей; значения других его полей могут быть произвольными. Для каждого действия отводится время, за которое система должна его произвести. Действие может быть отменено при получении какого-либо стимула, если оно еще не было выполнено тестируемой реализацией. В стандарте Mobile IPv6 [] требования по уровню обязательности выполнения классифицированы в соответствии с RFC 2119 [].


    В предлагаемом подходе для различных групп требований предусмотрены три группы действий:
  • Обязательные действия (obligated) - для требований, обязывающих мобильный узел отправить определенное сообщение (требования, выделенные словами MUST или SHOULD согласно RFC2119). Если тестируемая реализация отправляет такое сообщение в отведенный временной интервал, то сообщается о том, что ее поведение в данном случае удовлетворяет требованию спецификации, которое определяет данное действие. В противном случае выносится вердикт о том, что тестируемая реализация не выполнила данное требование. Для этой группы требований можно задавать альтернативные варианты действий. То есть задаются несколько возможных сообщений для одного требования, и если реализация отправляет одно из таких сообщений, условия требования считаются выполненными.
  • Запрещенные действия (prohibited) - для требований, запрещающих мобильному узлу отправлять определенное сообщение после наступления какого-либо события (требования, выделенные словами MUST NOT или SHOULD NOT). Если данное сообщение было отправлено в выделенный временной интервал, то выносится вердикт о том, что тестируемая реализация нарушила данное требование. В противном случае сообщается о том, что она выполнила требование, определяющее данное действие.
  • Допустимые действия (allowed) - для наблюдения за тем, что мобильный узел реализует некоторую необязательную функциональность протокола (требования, выделенные словом MAY). Если данное сообщение отправлено в отведенный интервал времени, то сообщается о том, что тестируемая реализация поддерживает необязательную функциональность, которая определяется данным требованием. В противном случае ничего не сообщается. Такой подход позволяет явно описывать требования, касающиеся отправления сообщений, и собирать детальную информацию о проверке таких требований, однако предполагает затрату больших усилий при разработке спецификаций, чем в случае со стационарными модельными состояниями. Для того чтобы обеспечить возможность использования стандартного обходчика, в качестве модельных состояний выделяются состояния виртуальной сети в целом, а не группы состояний модели целевой реализации, как это традиционно делается при использовании инструмента CTesK.


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


    Задача обходчика в таком случае заключается в осуществлении всех возможных переходов для мобильного узла всеми возможными способами. Под различными способами здесь понимается различное поведение узлов виртуальной сети после осуществления смены точки подключения мобильного узла в этой сети, которое определяется отправляемыми ими сообщениями. Для обеспечения полноты тестирования необходимо оказать воздействие всеми возможными стимулами во всех достижимых состояниях, включая начальное состояние. Однако приведение тестируемой реализации мобильного узла в начальное состояние только при помощи обмена сообщениями через сетевой интерфейс не представляется возможным. Это связано с тем, что даже после удаления привязки при возвращении в домашнюю сеть мобильный узел сохраняет некоторую информацию в структурах данных, описывающих его состояние, например, сохраняет адрес домашнего агента. На Рис. 7 изображен пример графа состояний тестового сценария для случая, когда виртуальная сеть состоит только из сегмента домашней сети и одного сегмента внешней сети, и определены только два способа перевода мобильного узла между этими сетями. В состояниях 0, 1 и 2 мобильный узел находится в домашней сети, в состояниях 3 и 4 - во внешней сети. Чтобы отличить способы перевода мобильного узла на рисунке используется разная толщина дуг графа. Проблемы тестирования мобильного узла и способы их решения Рис. 7.Пример графа состояний тестового сценария Этот пример показывает, что граф состояний тестового сценария не является сильно связным, так как нет дуги, которая возвращала бы тестовый сценарий в начальное состояние. Однако такая дуга необходима для осуществления обходчиком всех возможных переводов мобильного узла из всех достижимых состояний. Это одна из проблем, возникающих при использовании состояний, принятых в нашем подходе. Для гарантированного приведения тестируемой реализации в первоначальное состояние перед выполнением очередного тестового сценария используется перезагрузка целевого узла. Чтобы решить проблему с несвязностью графа состояний, можно было бы ввести стимул, осуществляющий перезагрузку системы, но это приводило бы к большому количеству перезагрузок во время тестирования и заняло бы больше времени на исполнение теста.


    Кроме того, сложно было бы отследить, чем именно вызвана перезагрузка: нестабильностью тестируемой реализации или воздействием такого стимула. Вместо введения стимула, осуществляющего перезагрузку целевой реализации, в предлагаемом подходе используются два приема: объединение состояний и обеспечение всех возможных воздействий. А именно, начальное состояние мобильного узла объединяется с состоянием "мобильный узел в домашней сети", в которое он попадает после возвращения в домашнюю сеть, при условии, что тестовый сценарий отправит все необходимые сообщения от имени узлов виртуальной сети. Для обеспечения же перебора всех возможных воздействий в начальном состоянии первоначальные тестовые сценарии разделяются на несколько подсценариев. Этот процесс можно описать следующим образом. Сначала, как и обычно, для проверки определенных групп требований стандарта разрабатываются конкретные тестовые сценарии. Затем после определения всех возможных стимулов, необходимых для обеспечения полноты тестирования, для каждого из этих первоначально разработанных тестовых сценариев создается несколько подсценариев, которые отличаются лишь тем, что на целевую реализацию, находящуюся в первоначальном состоянии, осуществляется воздействие одним конкретным стимулом, отличным от первых стимулов в других подсценариях. При этом общее количество создаваемых подсценариев равно количеству возможных стимулов для данного тестового сценария. Таким образом, на реализацию, находящуюся в первоначальном состоянии, осуществляется воздействие всеми возможными стимулами. На Рис. 8 изображены подсценарии, полученные из предыдущего примера при помощи описанных выше действий. Заметим, что у этого графа дуга из состояния 1 в состояние 3, соответствующая первому способу перевода мобильного узла из домашней сети во внешнюю сеть, обозначена пунктиром. Это означает, что обходчик не обязан осуществлять такое воздействие, так как может считать, что оно уже произведено при выполнении перевода мобильного узла из начального состояния первым способом.

    Структура тестового набора и этапы исполнения сценарной функции

    Как было указано в предыдущем разделе, для реализации предложенной модели тестирования, тестирующий узел должен имитировать функциональность всех узлов, которые принимают участие в обмене сообщениями с мобильным узлом в виртуальной сети. Имитация функциональности узлов виртуальной сети необходима на протяжении всего выполнения тестового сценария, в том числе и во время обработки результатов вызова сценарных функций. Такая необходимость вызвана тем, что при тестировании используются нестационарные модельные состояния, и большинство служебных сообщений, отправляемых мобильным узлом, является частью синхронных процедур обмена сообщениями с одним из узлов сети. Поэтому часть тестового набора, имитирующая данную функциональность, должна быть реализована вне тестового сценария. Схема виртуальной сети и набор ее узлов специфичны для каждого теста. С учетом этого наиболее простое и логичное решение заключается в том, чтобы обособить функциональность, свойственную каждому узлу. Для этого написаны отдельные демоны, каждый из которых полностью имитирует функциональность узла определенного типа: демон маршрутизатора, демон домашнего агента, демон узла-корреспондента. Демоны запускаются в отдельных процессах в начале тестового сценария и функционируют на протяжении всего его выполнения. Это позволяет мобильному узлу отправлять необходимые вспомогательные сообщения во время обработки стимулов и реакций, собранных тестовым сценарием между вызовами сценарных функций. При переходе исполнения от одной сценарной функции к другой изменяется лишь поведение демонов, которое заключается в отправлении или не отправлении синхронных и асинхронных сообщений мобильному узлу, меняются также некоторые поля в этих сообщениях. В процессе исполнения тестового сценария топология виртуальной сети и положение в ней демонов остаются неизменными. Все демоны подключаются к одному и тому же сетевому интерфейсу; это упрощает организацию маршрутизации и внештатного функционирования демонов, которое необходимо для проверки некоторых требований спецификации.
    Взаимодействие между демонами, там, где это необходимо, должно производится не через сетевой интерфейс, а с использованием других средств, так как все такие сообщения будет получать мобильный узел, если его сетевой интерфейс находится в режиме promiscuous. В предлагаемом тестовом наборе такое взаимодействие необходимо только между демонами узлов-корреспондентов и демонами домашних агентов. Для этого используется общий Binding Update List, в который домашние агенты записывают информацию об установленных привязках. Узлы-корреспонденты используют эту информацию для извлечения из туннеля входящих пакетов и туннелирования исходящих пакетов при обмене сообщениями с мобильным узлом в режиме двустороннего туннелирования. Общая картина выполнения тестового сценария выглядит следующим образом (см. Рис. 9). Перед инициализацией тестового сценария на тестируемом узле должна быть настроена и запущена целевая реализация. Перед началом тестирования запускается процесс под названием кэтчер, который получает и запоминает все сообщения, поступающие на сетевой интерфейс. Затем запускаются необходимые демоны с первоначальными значениями структур данных, которые определяют их поведение. После этого тестовый сценарий выжидает некоторый интервал времени, в течение которого при помощи обмена сообщениями с демонами тестируемая реализация выполняет автоматическую конфигурацию. Рис. 9. Схема исполнения тестового сценария Выполнение тестового сценария заключается в последовательном вызове сценарных функций. При вызове каждой сценарной функции соответствующим образом изменяются внутренние структуры данных, определяющие поведение демонов. Такое изменение состояния и поведения демонов оказывает воздействие на целевую реализацию мобильного узла и заставляет ее выйти из стационарного состояния. Затем тестовый сценарий некоторое время находится в состоянии ожидания, по истечении которого работа сценарной функции завершается, и тестовый сценарий получает от кэтчера пакеты, собранные за время ожидания и обработки результатов вызова предыдущей сценарной функции.Далее производится анализ полученных пакетов и регистрация соответствующих стимулов и реакций. Таким образом, стимулы, которые должны применяться в данной сценарной функции, задаются неявным образом через поведение демонов. После завершения сценария прекращается работа демонов и кэтчера.

    В последнее время мобильные устройства

    В последнее время мобильные устройства получают все большее распространение, и потребность постоянного доступа к сети Интернет таких устройств является естественной. Предполагается, что в недалеком будущем Интернет примет на вооружение протокол нового поколения IPv6 []. Для поддержки функций мобильности техническим комитетом по проектированию Интернет (Internet Engineering Task Force, IETF) разработано расширение этого нового протокола, получившее название Mobile IPv6, и выпущен документ RFC 3775 "Mobility Support in IPv6" [], который можно рассматривать в качестве будущего стандарта (процесс стандартизации этого расширения, как и самого протокола IPv6, на сегодняшний день еще не завершен). Функциональность Mobile IPv6 нацелена на поддержку целого ряда устройств, в частности, персональных компьютеров, ноутбуков, карманных компьютеров, мобильных телефонов, маршрутизаторов и точек доступа для беспроводных сетей. Очевидно, в перспективе ожидается появление большого количества реализаций Mobile IPv6 от различных производителей. Тестирование соответствия стандартам является одним из основных средств обеспечения совместимости реализаций различных производителей и позволяет решать вопросы повышения надежности и отказоустойчивости глобальной сети. В настоящее время отсутствие средств такого тестирования является одной из причин, препятствующих внедрению перспективных технологий в практику. В Институте системного программирования РАН (ИСП РАН) в течение ряда лет ведутся работы по исследованию и развитию методов формального моделирования телекоммуникационных протоколов. В частности, одним из направлений работ по проекту "Верификация функций безопасности и мобильности протоколов IP", который выполнялся при поддержке гранта РФФИ № 04-07-90308, была разработка тестового набора, обеспечивающего проверку соответствия реализаций стандарту Mobile IPv6. В качестве основы для построения такого тестового набора использовался опыт создания и внедрения разработанной в ИСП РАН методологии тестирования на основе формальных спецификаций UniTESK []. В статье рассматриваются некоторые особенности протокола Mobile IPv6, затрудняющие тестирование реализаций с помощью этой технологии, а также способы преодоления возникших трудностей в рамках ее ограничений.
    В качестве объекта тестирования принята реализация основного объекта протокола Mobile IPv6 - мобильного узла (узла, который может совершать перемещения между различными сегментами сети Интернет). Такой выбор обусловлен тем, что тестирование мобильного узла представляет наибольшую сложность, в частности, из-за того, что он является инициатором большинства служебных процедур обмена протокольными сообщениями. Статья содержит восемь разделов, включая введение и заключение. В следующих двух разделах кратко рассматриваются вопросы функционирования протокола Mobile IPv6, а также возможности инструмента CTesK [], реализующего технологию UniTESK. Четвертый раздел посвящен проблемам тестирования мобильного узла и способам их решения. В пятом разделе описываются структура разработанного тестового набора и этапы исполнения сценарных функций. В шестом разделе рассматриваются способы адаптации инструмента CTesK к предложенной схеме тестового набора. Седьмой раздел посвящен результатам применения разработанного тестового набора для тестирования одной из известных реализаций Mobile IPv6. В заключении приведены основные выводы и результаты работы.

    В статье описаны особенности тестового

    В статье описаны особенности тестового набора, разработанного для проверки соответствия реализаций мобильного узла спецификации протокола Mobile IPv6 []. Тестовый набор разрабатывался при помощи инструмента CTesK, который реализует технологию автоматического тестирования UniTESK. В ходе выполнения работы были выявлены некоторые особенности мобильного узла, которые затрудняют его тестирование с помощью технологии UniTESK, а именно:
  • Мобильный узел является инициатором большинства служебных обменов сообщениями. Некоторые из таких обменов сообщениями можно вызвать неявным образом, создав определенные условия при помощи вспомогательных сообщений. Другие обмены сообщениями мобильный узел может начинать в произвольные моменты времени. В обоих случаях на сообщения со стороны мобильного узла может потребоваться быстрый ответ со стороны тестирующего узла.
  • Спецификация протокола Mobile IPv6 не содержит исчерпывающего описания поведения мобильного узла, поэтому нельзя осуществить перебор всех возможных воздействий во всех состояниях.
  • Спецификация протокола Mobile IPv6 допускает недетерминированное поведение мобильного узла. Однако все эти особенности удалось успешно преодолеть, не выходя за рамки ограничений технологии UniTESK. При разработке тестового набора было обнаружено несколько сложных моментов, связанных с применением инструмента CTesK, а именно:
  • отсутствие у мобильного узла стационарных состояний;
  • сложность использования инвариантов типов данных из-за того, что их проверка производится только автоматически и не может контролироваться;
  • недостаточная информативность при задании формальных спецификаций в виде пред- и постусловий. Для преодоления этих трудностей потребовалось несколько отступить от прямого использования традиционного подхода к разработке тестового набора, задаваемого инструментом CTesK. Тем не менее, благодаря гибкости инструмента, удалось решить поставленную задачу его средствами. Единственное, от чего действительно пришлось отказаться, - это использование стандартных для CTesK средств генерации отчетов о тестировании. Широта и сложность предметной области требует для анализа результатов тестирования существенно большего объема информации, чем предоставляет инструмент. Поэтому был реализован сбор дополнительной информации о ходе тестирования средствами самого тестового набора и разработана система генерации отчета, предназначенная для конкретной задачи. В остальном, несмотря на все выявленные особенности, удалось без существенных трудностей использовать инструмент CTesK для тестирования реализаций мобильного узла.

    Тестирование софта - статьи

    Анализ результатов тестирования

    На рисунке показана статистическая выкладка, выведенная Coverage по окончании тестирования приложения. Анализ результатов тестирования Поле Calls определяет число вызовов функции Из полученной таблицы видна статистическая информация о том, какие строки и сколько раз исполнялись. К особенностям работы PureCoverage отнесем тестирование только тех исполняемых файлов, которые уже имеют отладочную информацию от компилятора. Соответственно, тестирование стандартных приложений и библиотек данным продуктом невозможно. Впрочем, и не нужно. Особые преимущества инструмент демонстрирует при совместном тестировании с Robot при функциональном тестировании, подсчитывая строки исходных текстов в момент воспроизведения скрипта. Тем самым на выходе тестировщик (или разработчик) получает информацию о стабильности функциональных компонент, плюс, область покрытия кода (область охвата). Анализ результатов тестирования Поле %Lines Hit показывает процентное отношение протестированных строк кода для отдельной функции. Анализ результатов тестирования Вид окна перехода на уровень работы с исходным текстом. Из него видно, что PureCoverage ведет нумерацию строки и подсчитывает число исполнений каждой. Не исполнившиеся фрагменты подсвечиваются красным цветом (палитра может регулироваться пользователем)

    API

    Это дополнительная возможность, предоставляемая Quantify по полному управлению процессом тестирования. API представляет собой набор функций, которые можно вызывать из тестируемого приложения по усмотрению разработчика. Для получения доступа к API необходимо выполнить ряд действий по подключению "puri.h" файла с определением функций и с включением "pure_api.c" файла в состав проекта. Единственное ограничение, накладываемое API — рекомендации по постановке точек останова после вызовов Quantify при исполнении приложения под отладчиком. Рассмотрим имеющиеся функции API Quantify:
  • QuantifyAddAnnotation. Позволяет задавать словесное описание, сопровождающее тестирование кода. Информация, заданная разработчиком этой функцией может быть извлечена из пункта "details" меню тестирования и доступна в LOG-файле. На ее основе, тестер может впоследствии использовать особые условия тестирования;
  • QuantifyClearData. Очищает все несохраненные данные;
  • QuantifyDisableRecordingData. Запрещает дальнейшую запись;
  • QuantifyIsRecordingData. Возвращает значение 1 или 0 в зависимости от того производится ли запись свойств или нет;
  • QuantifyIsRunning. Возвращает значение 1 или 0 в зависимости от того проходит тестируемое приложение исполнение в обычном режиме или под Quantify;
  • QuantifySaveData. Данная функция позволяет сохранять текущее состояние — делать снимок (snapshot);
  • QuantifySetThreadName. Функция позволяет разработчикам именовать потоки в произвольном именном поле. По умолчанию Quantify дает имена, наподобие "thread_1", что может не всегда положительно сказываться на читаемости получаемой информации;
  • QuantifyStartRecordingData. Начинает запись свойств. По умолчанию, данная функция автоматически вызывается Quantify при исполнении;
  • QuantifyStopRecordingData. Останавливает запись свойств.
  • Если модифицировать наше тестовое приложение так, чтобы оно использовало преимущества интерфейса API, то может получиться нечто нижеследующее int main(int argc, char* argv[])


    {
    int i; QuantifyAddAnnotation("Тестирование проводится под Quantify с использованием API");
    QuantifySetThreadName("Основной поток приложения");
    for(i=0;i<12;i++){
    QuantifySaveData();
    recursive();
    }
    return 0;
    } В листинге показано как можно использовать основные функции API для извлечения максимального статистического набора данных. Рисунки показывают слепки фрагментов экрана Quantify по окончании тестирования. API Рис. Данный рисунок демонстрирует вид окна после выполнения команды снятия слепка или вызова функции API QuantifySaveData() API Рис. Обратите внимание на поле аннотации API Рис. У Quantify отсутствуют сложности с русскими буквами

    Дерево вызовов "Call Graph"

    Следующий интереснейший способ анализа конструкции приложения — это просмотр дерева вызовов. Окно, показанное на 4 рисунке, показывает только фрагмент окна с диаграммой вызовов. Дерево вызовов Обратите внимание на количество и последовательность вызова различных модулей потока "main". Жирная линия показывает наиболее длительные ветви (содержащие либо часто вызываемые функции, либо функции, выполнявшиеся дольше остальных). Для демонстрации возможностей Quantify было сконструировано простое приложение, состоящее из функции "main" и двух дополнительных "recursive" и "outside" (см. листинг 1). Листинг 1. Пример тестируемого приложения, сконструированном в виде консольного приложения из Visual Studio 6.0. Язык реализации "С". #include "stdafx.h" //Создаем функцию-заглушку
    void outside (void)
    {
    static int v=0;
    v++;
    } //Создаем рекурсивную функцию, исполняющуюся 100 раз
    int recursive(void)
    {
    static int i=0;
    int oo;

    outside();//Вызываем функцию заглушку
    if(i==100){i=1;return 0;}//Обнуляем счетчик и выходим
    i++;
    recursive();
    } int main(int argc, char* argv[])
    {
    int i;
    for(i=0;i<100;i++)recursive();
    //Вызываем 100 раз рекурсивную функцию 100х100
    return 0;
    } Приложение простое по сути, но очень содержательное, так как эффективно демонстрирует основные возможности Quantify. В самом начале статьи мы выдвигали требование, по которому разработчикам не рекомендуется пользоваться рекурсивными функциями. Тестеры или разработчики, увидев диаграмму вызовов, выделят функцию, находящуюся в полукруге, что является признаком рекурсивного вызова (см. рисунок). Дерево вызовов В зависимости от того, на какой из ветвей дерева, находится курсор, выводится дополнительная статистическая информация о временном доступе к выделенной функции и к дочерним, идущим ниже. Следующий рисунок демонстрирует статистику по функции "recursive". Дерево вызовов Более подробно о статистике будет рассказано в следующем материале.

    Инструментальные средства поддержки процесса тестирования

    Новичков Александр, Костиков Александр

    Часть вторая — Инструментальные средства поддержки процесса тестирования


































    Данный инструмент представляется наиболее простым

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

    Организация тестирования

    Как при приемочном тестировании, так и при регрессионном разработчик должен предоставить не только работающий код, удовлетворяющий начальным требованиям, но и делающий это максимально безукоризненно с точки зрения производительности и устойчивости. В дополнение, разработчик должен максимально тщательно проверить исполнение всех строчек написанного им кода, во избежание дальнейших несуразностей, ведь одна из часто встречаемых ошибок — это копирование блоков программного кода в разные части программы, при том, что не все ветви первоначального блока были обработаны и протестированы. Подобные первоначальные ошибки сводят на "нет" дальнейшее функциональное тестирование, так как низкокачественный код не позволяет тестерам быстро писать скрипт, реализующий тестирование новой формы, так как она полна странностей. Доработка, доработка, доработка... Интересная деталь: процессоры работают быстро, шины работают быстро, память тоже быстро, а вот программное обеспечение из года в год, от версии к версии работает все медленнее и медленнее, потребляя при этом на порядок больше ресурсов, нежели чем предыдущие версии. Не спасают положение даже оптимизирующие компиляторы! Вторая не менее интересная деталь заключается в скорости разработки. Вы не обращали внимания на то, что аппаратные продукты (процессоры и прочее) обновляются гораздо чаще и выпускаются с меньшим числом ошибок? Проблема кроется здесь в разных подходах к реализации программного кода в хардверных и софтверных компаниях. В софтверных компаниях, обычно, нет строго организованного процесса тестирования, нет строгой регламентации процесса кодирования, а сам процесс кодирования является особым видом искусства. То есть налицо пренебрежение основополагающими принципами промышленного производства, регламентирующими роли и процессы, входные и выходные значения. Из нашего опыта работы с партнерами мы знаем не понаслышке о жестких, и даже, жестоких требованиях к программному обеспечению, прошиваемому в аппаратуру, скажем так, у одной компании производителя сотовых телефонов есть требование, в соответствии с которым сотовому телефону "разрешается" зависнуть один раз в год при непрерывной эксплуатации.
    И это, не говоря о том, что все пункты меню соответствуют начальным требованиям на 100%. Вот и получается, что при вопросе: "Согласны ли вы, чтобы ваш телефон работал на основе обычной операционной системы (Windows, Unix... или им подобными)?", Традиционно следует — "нет"! Поскольку этап тестирования кода приложения можно отнести к тестированию «прозрачного ящика», когда важен код приложения, то мы попробуем описать основные рекомендации по тестированию для данного этапа, которые почерпаны из личного опыта и основаны на здравом смысле и принципах разумной достаточности. То есть, мы сначала рассмотрим общие требования к качеству кода, а потом проведем ассоциацию с инструментами Rational, которые позволят решить проблемы данного участка. Следующие недостатки (относительные) программного кода могут потенциально привести приложение к состоянию зависания (входа в вечный цикл) или к состоянию нестабильности, проявляемой время от времени (в зависимости от внешних факторов):
  • Использование рекурсии. Всем известна практическая ценность рекурсии для большого числа задач, особенно математических, но рекурсия является и источником проблем, поскольку не все компиляторы и не во всех случаях способны правильно отрабатывать рекурсию. Посему очень часто в требованиях на разработку вводится требование на отсутствие рекурсивных функций. Quantify ведет статистический анализ по вызовам функций и она выдает имена всех рекурсивных функций.
  • Степень вложенности процедур (функций). В программе можно создать любое число функций (классов), вложить их друг в друга, а потом удивляться почему та или иная функция работает с не той производительностью. Если в требованиях на разработку ограничить полет фантазии разработчиков, то получится более быстрое приложение. Статистику по вложенности и скорости даст Quantify.
  • Соглашения о именах. Также оговаривается в требованиях на разработку. Необходимо ввести единый стиль наименования функций, во избежание появления дублей и непонятных имен, что тормозит развитие проекта.


    Quantify также реализует поиски рассогласовании в именах.
  • Использование указателей. Язык С и С++ трудно представить без указателей, даже невозможно. Но, являясь гибким и мощным механизмом обращения к памяти, они же являются минами замедленного действия, проявляющими себя в самые неподходящие моменты. Если проект не может существовать без указателей, то все ошибки, связанные с их неправильной работой позволит отловить Purify.
  • Не целевое использование переменных и идентификаторов. Сюда попадают и ошибки связанные с операциями чтения и записи файлов до их открытия или после закрытия. Это и присвоение в переменных до их инициализации. Это и наличие лишних переменных. Эти неточности также могут привести к нестабильности приложения. Purify обрабатывает данную категорию ошибок.
  • Не целевое использование блоков данных. Под эту категорию попадают ошибки связанные с распределением памяти, например, невозвращение блоков памяти после их использования. С точки зрения функциональности подобная ошибка не совсем ошибка, так как целостность приложения не нарушается и к сбоям не ведет. Побочный эффект — это замусоривание системы ненужными данными и быстрое "истекание" системных ресурсов. Данный вид отлавливается Purify.
  • Присутствие участков кода не исполнявшихся в течении определенного времени. Также потенциальная ошибка, так как не выполненный код может содержать ошибки. PureCoverage отлавливает данный вид ошибок.


  • Основные параметры вывода

  • Address. IP адрес модуля, в котором обнаружена ошибка или предупреждение;
  • Error Location. Описание модуля с ошибкой. В случае тестирования модуля с исходными текстами, то в данном поле можно просмотреть фрагмент кода, который вызвал появление ошибки;
  • Allocate Location. Разновидность "Error Location", показывает фрагмент кода, в котором был распределен блок памяти, работа с которым, привела к ошибке.
  • Работа с Purify возможна как при наличии исходных текстов и отладочной информации, так и без нее. В случае отсутствия debug-информации анализ ошибок ведется только по ip-адресам. Так же как и в случае с Quantify, возможен детальный просмотр функций из dll-библиотек. В этом случае наличие отладочной информации не является необходимостью.

    Основные свойства средств Purify, Quantify и PureCoverage

  • Интегрируются со средствами функционального тестирования Robot, TestManager и Visual Test;
    Интегрируются с ClearQuest, для документирования и отслеживания возникающих в процессе тестирования ошибок;
  • Выдают точную и детальную информацию о производительности приложения;
  • Используют технологию OCI — Object Code Insertion, что позволяет детально отслеживать и вылавливать ошибки не только в разрабатываемом модуле, но и во внешних библиотека;
  • Представляют комплексный дополнительный обзор данных по производительности, области охвата кода и по стабильности;
    Имеют гибкая настройка в соответствии с потребностям разработчиков и тестировщиков;
  • Позволяют многократно тестировать приложение (по ходу разработки), отслеживать изменения для каждой перекомпиляции, формируя тем самым данные для последующего анализа;
  • Интегрируются со средствами разработки (Visual Studio 6.0, Visual Studio .NET, Vis-ual Age For Java);
  • Тестируют компоненты ActiveX, COM/DCOM и ODBC;
  • Имеют интерфейс API, позволяющий дописывать разработчикам собственные для наиболее тщательного и эффективного тестирования.


  • Особенности запуска

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

    Параметры тестирования

    Перед исполнением тестируемого приложения, возможно, задать дополнительные настройки, которые смогут настроить Purify на эффективное тестирование. Запуск приложения производится точно также как и в случае с Quantify (по F5 или file->Run). Параметры настройки находятся пункте Settings, появившегося окна. Первая страница диалога представлена на рисунке Параметры тестирования Отметим основные особенности, которые качественно могут сказаться на результирующем отчете:
  • Report at exit. Данная группа позволяет получать более детальную информацию обо всех указателях и блоках памяти, которые не приводили к ошибкам памяти. По умолчанию, Purify выводит отчеты только по тем блокам, которые были распределены, но не были освобождены. В большинстве случаев такой подход оправдан, так как обычно разработчика интересуют именно ошибки. Остальные пункты активизируют по мере необходимости, когда нужно иметь общее представление о использовании памяти тестируемым приложением;
  • Error Suppretion. Группа определяет степенью детальности выводимой информации.
    По умолчанию, активирован пункт "Show first message only". В этом случае по окончании тестирования, Purify выводит сокращенный отчет по модулям, то есть, если в одном модуле найдено 10 утечек памяти, то информация об утечках на основном экране будет описывать только имя ошибки, число блоков и имя модуля. То есть мы получаем обобщенную информацию по ошибкам в блоке. В случае необходимости получения отдельного отчета по каждому отдельному блоку, отключаем данный пункт.
  • Call Stack Length. Определяет глубину стека;
  • Red Zone Length. Управляет числом байтов, которое встраивается в код тестируемого приложения при операциях связанных с распределением памяти. Увеличение числа способствует лучшему сбору информации, но существенно тормозит исполнение приложения;
  • Закладка PowerCheck позволит настроить уровень анализа каждого отдельно взятого модуля. Существует два способа инструментирования тестируемого приложения: Precise и Minimal. В первом случае проводится детальное инструментирование кода, но при этом модуль работает относительно медленно. Во втором случае, проводится краткое инструментирование, при котором Purify вносит в модуль меньше отладочной информации, и, как следствие, способна отловить меньшее число ошибок. Последний подход оправдан, когда приложение вызывает массу внешних библиотек от третьих фирм, которые не будут подвергаться правке.

    Quantify, Purify и PureCoverage

    Для осуществления всех функций по тестированию программных модулей, все три продукта используют специальную технологию Object Code Insertion, при которой бинарный файл, находящийся под тестированием, насыщается отладочной бинарной информацией, обеспечивающей сбор информации о ходе тестирования. Отметим общие черты для всех трех программных продуктов. Для сбора и обработки информации программам тестирования нужны два файла: исполняемый модуль и его исходный текст. Исполняемый модуль насыщается отладочным кодом, а наличие исходного текста позволяет разработчику легко переключаться между схематическим отображением и кодом тестируемого приложения. На код накладываются дополнительные особые ограничения, а именно: тестируемый код должен быть получен при помощи компиляции с опцией "Debug", то есть должен содержать отладочную информацию. В противном случае Quantify, Purify и PureCoverage не смогут правильно отображать (вообще не смогут отображать) имена функций внутренних модулей тестируемого приложения. Исключения могут составлять только вызовы внешних модулей из dll-библиотек, поскольку метод компоновки динамических библиотек позволяет узнавать имена функций. Отсюда можно сделать простой вывод: тестировать приложения можно даже не имея отладочной информации в модуле и при отсутствии исходных текстов, но в этом случае разработчик получает статистику исключительно по внешним вызовам. Все три продукта способны проводить особые виды тестирования, такие как тестирование сервисов NT\2000\XP. Программные продукты могут работать в трех режимах:
  • Независимом графическом. В этом случае каждое средство запускается индивидуально а тестирование осуществляется из него в графическом режиме;
  • Независимом командном. Данный режим характеризуется управлением ходом насыщения тестируемого модуля отладочной информации из командной строки;
  • Интегрированном. Этот режим позволяет разработчикам не выходя из привычной среды разработки (Visual Studio 6.0 или .NET) прозрачно вызывать инструменты Quantify, Purify и PureCoverage.
  • Из дополнительных возможностей всех инструментов хочется отметить наличие специального набора файлов автоматизации, разрешающих разработчикам еще на этапе разработки внедрять С-образные вызовы функций сбора информации по тестированию в свои приложения, получая при этом максимальный контроль над ними. Средства анализа, строенные в Quantify, Purify и PureCoverage позволяют удовлетворить детально контролировать все нюансы в исполнении тестируемого приложения. Здесь и сравнение запусков, и создание слепков в ходе тестирования и экспорт в Excel для построения точных графиков множественных запусков. Особо хочется отметить языковую "всеядность" продуктов. Поддерживаются следующие языки программирования:
  • Visual C\C++\C# в exe-модулях, dll-библиотеках, ActiveX-компонентах и COM-объектах;
  • Поддерживаются проекты на Visual Basic и Java Applets (с любой виртуальной машиной);
  • Дополнительно можно тестировать дополнительные модули к MS Word и Ms Excel.


  • Работа с фильтром

    Чтобы не загромождать пользовательский интерфейс лишними данными, в Purify предусмотрена система гибких фильтров. Система фильтров Purify способна регламентировать тип ошибок и предупреждений и ошибок (группировка производится по категориям) к программе, но и число исследуемых внешних модулей (чтобы разработчик мог концентрироваться только на ошибках собственного модуля). Таким образом возможно создание универсальных фильтров с осмысленными именами, которые ограничивают поток информации. Число создаваемых фильтров ничем не ограничено. Фильтры создаются и назначаются и модифицируются через верхнее меню (View->CreateFilter и View->FilterManager). По умолчанию Purify выводит все сообщения и предупреждения. Работа с фильтром Рисунок показывает внешний вид окна создания фильтра (View->CreateFilter). Здесь мы имеем возможность по выбору сообщений, которые нужно отфильтровывать. Пункт General — управляет именем фильтра и комментарием, его сопровождающим, Source — определяет местоположение исходных файлов, для которых необходимо вывести сообщения. Подход используется в том случае, когда происходит вызов одного модуля из другого, дабы ограничить количество информации в отчете. Следующий рисунок демонстрирует вид окна настроек фильтров. Здесь имеется возможность по активации\деактвации фильтров и модулей. Работа с фильтром Выше упоминалось, что Purify не ограничивает число фильтров. Следует понимать, что не ограничивается не только общее число фильтров, но и их количество на одно протестированное приложение. Ограничение по модулям, которое также можно выставить в данном диалоге, определяет число внешних модулей, предупреждения от которых появляются в отчете.

    Работа с PureCoverage

    По принципу работы PureCoverage слегка напоминает Quantify: также подсчитывает количество вызовов функций. Правда, получаемая статистика не столь исчерпывающая как в Quantify (в визуальном отношении), но для проверки области охвата кода вполне и вполне пригодна. Система отчетности представлена 4-я различными видами отчетов:
  • Coverage Browser. Основное окно просмотра, позволяет просматривать протестированное приложение по модулям или по файлам. Выдает статистику о наличии пропущенных строк;
  • Function List. Выдает отчет по функциям;
  • Annotated Source. Переход к режиму просмотра исходного текста;
  • Run Summary. Общая информация о протестированном приложении;


  • "Run Summary"

    Плавно переходим к следующей стадии тестирования, собственно, к сбору информации. По окончании процесса насыщения отладочным кодом модулей тестируемого приложения, Quantify переходит к его исполнению, производимым, обычным образом, за одним исключением: запись состояний тестируемого приложения продолжает проводиться в фоновом режиме. Рисунок 3 демонстрирует фрагмент окна "Summary", в котором производится запись состояния тестируемого приложения. Причем, что очень примечательно, тестирование производится не только для простого приложения, но и для много поточного. В последнем случае (см. рисунок 3), тестируется каждый поток отдельно. В любом случае, даже если приложение однопоточное, то имя основного (единственного) потока именуется как ".main_0", что представляется вполне логичным. Информационный график постепенно наполняется квадратами разного цвета, демонстрирующими текущее состояние тестируемого приложения. Отметим некоторые из них:
  • Running. Начало исполнения потока;
  • Waiting I\O. Ожидание действий по вводу\выводу;
  • Blocked. Блокирование исполнения потока;
  • Quantify. Ожидание вызова модуля Quantify;
  • Exited. Окончание исполнения потока.
  • Важный аспект при тестировании — получение статистической информации о количестве внешних библиотек, которые вызывало основное приложение, а также элементарное описание машины, на которой проводилось тестирование. Последнее особенно важно, так как бывает, что ошибка проявляется только на процессорах строго определенной серии или производителя. Все статистические аспекты решаются внутри окна "Summary". Следующие два примера показывают статистическую информацию:
    (1) Общий отчет — "Details": Program Name: C:\projects\aa\Debug\aa.exe
    Program Arguments:
    Working Directory: C:\projects\aa\Debug
    User Name: Alex
    Product Version: 2002.05.00 4113
    Host Name: ALEX-GOLDER
    Machine Type: Intel Pentium Pro Model 8 Stepping 10
    # Processors: 1
    Clock Rate: 847 MHz
    O/S Version: Windows NT 5.1.2600


    Physical Memory: 382 MBytes
    PID: 0xfbc
    Start Time: 24.04.2002 14:17:38
    Stop Time: 24.04.2002 14:17:52
    Elapsed Time: 13330 ms
    # Measured Cycles: 191748 (0 ms)
    # Timed Cycles: 2489329 (2 ms)
    Dataset Size (bytes): 0x4a0001. (2) "Log"
    Quantify for Windows,
    Copyright (C) 1993- 2001 Rational Software Corporation All rights reserved.
    Version 2002.05.00; Build: 4113;
    WinNT 5.1 2600 Uniprocessor Free
    Instrumenting:
    Far.exe 620032 bytes
    ADVAPI32.DLL 549888 bytes
    ADVAPI32.DLL 549888 bytes
    USER32.DLL 561152 bytes
    USER32.DLL 561152 bytes
    SHELL32.DLL 8322560 bytes
    SHELL32.DLL 8322560 bytes
    WINSPOOL.DRV 131584 bytes
    WINSPOOL.DRV 131584 bytes
    MPR.DLL 55808 bytes
    MPR.DLL 55808 bytes
    RPCRT4.DLL 463872 bytes
    RPCRT4.DLL 463872 bytes
    GDI32.DLL 250880 bytes
    GDI32.DLL 250880 bytes
    MSVCRT.DLL 322560 bytes
    MSVCRT.DLL 322560 bytes
    SHLWAPI.DLL 397824 bytes
    SHLWAPI.DLL 397824 bytes Для разработчика или тестера информация (информационный отчет), представленная выше, способна пролить свет на те статистические данные, которые сопровождали, а точнее, формировали среду тестирования.

    Сохранение данных и экспорт

    Традиционные операции над файлами присущи и программе Quantify. Дополнительные особенности заключаются в том, что сохранять данные можно как во встроенном формате (qfy), для каждого отдельного запуска, так и в текстовом виде, для последующего использования в текстовых редакторах, либо для дальнейшей обработки скриптовыми языками типа perl (более подробно смотрите об этом в разделах по ClearCase и ClearQuest). Quantify позволит переносить таблицы через буфер обмена в Microsoft Excel, что открывает безграничные возможности по множественному сравнению запусков, по построению графиков и различных форм. Все что необходимо сделать — это только скопировать данные из одной программы и поместить в другую известным способом.
    Purify позволяет сохранять результаты тестирования (file->save copy as) в четырех различных представлениях, позволяющих наиболее эффективным образом получить информацию о ходе тестирования. Рассмотрим варианты сохранения:
  • Purify Error unfiltered. Сохраняет данные о тестировании в виде "как есть" без фильтров;
  • Purify error filtered. Сохраняет данные о тестировании с примененными фильтрами;
  • Text expended. Сохраняет данные в текстовом виде о тестировании в расширенном представлении (с выводом найденных ошибок, с кратким описанием);
  • Text view. Сохраняет только упоминание о найденных ошибках, без дополнительного описания
  • При установленном MS Outlook, возможно отправление отчета почтой через пункт file->send из верхнего меню Purify.


    Данные из инструмента тестирования сохраняются в текстовом файле (как и в двух предыдущих случаях). Текстовый формат выдачи информации делает возможным включать различные обработчики отчетов основанные на скриптовых языках (например, при помощи Perl, можно "выудить" специфичные поля из текстового отчета и поместить их в средство документирования, получив отчет). Пример фрагмента отчета приведен ниже: CoverageData WinMain Function D:\xp\Rational\Coverage\Samples\hello.c D:\xp\Rational\Coverage\Samples\hello.exe 0 1 1 100.0 5 5 10 50.00 36 1 SourceLines D:\xp\Rational\Coverage\Samples\hello.c D:\xp\Rational\Coverage\Samples\hello.exe
    LineNumber LineCoverage
    18.1 0
    23.1 0
    26.1 0
    26.1 0
    27.1 0
    27.1 0 PureCoverage также как и Quantify может переносить табличные данные в Microsoft Excel.

    Сообщения об ошибках и предупреждениях

    Для того, чтобы иметь представление о возможностях продукта, опишем то, какие ошибки и потенциальные ошибки могут присутствовать в тестируемом приложение. Отметим разницу между ошибкой и потенциальной ошибкой и опишем:
  • Ошибка — свершившийся факт нестабильной или некорректной работы приложения, проводящий к неадекватным действиям приложения или системы. Примером подобной ошибки можно считать выход за пределы массива или попытка записи данных по 0 адресу;
  • Потенциальная ошибка — в приложении имеется фрагмент кода, который при нормальном исполнении не приводит к ошибкам. Ошибка возникает только в случае стечения обстоятельств, либо не проявляет себя никогда. К данной категории можно отнести такие особенности, как инициализация массива с ненулевого адреса, скажем, имеется массив на 100 байт, но каждый раз обращение к нему производится с 10 элемента. В этом случае Purify считает, что имеется потенциальная утечка памяти размером в 10 байт.
  • Естественно, что подобное поведение может быть вызвано спецификой приложения, например, так вести себя может текстовый редактор. Поэтому в Purify применятся деление информации на ошибки и потенциальные ошибки (которые можно воспринимать как специфику). Список ошибок и потенциальных ошибок достаточно объемен и постоянно пополняется. Кратко опишем основные сообщения, выводимые после тестирования:
  • Array Bounds Read Выход за пределы массива при чтении;
  • Array Bounds Write Выход за пределы массива при записи;
  • Late Detect Array Bounds Write Cообщение указывает, что программа записала значение перед началом или после конца распределенного блока памяти;
  • Beyond Stack Read Сообщение указывает, что функция в программе собирается читать вне текущего указателя вершины стека;
  • Freeing Freed Memory Попытка освобождения свободного блока памяти;
  • Freeing Invalid Memory Попытка освобождения некорректного блока памяти;
  • Freeing Mismatched Memory Сообщение указывает, что программа пробует;
  • Free Memory Read Попытка чтения уже освобожденного блока памяти;
  • Free Memory Write Попытка записи уже освобожденного блока памяти;
  • Invalid Handle Операции над неправильным дескриптором;


    ptr[1] = ‘r’;
    ptr[2] = ‘r’;
    ptr[3] = ‘o’;
    ptr[4] = ‘r’;
    for (int i=0; i <= 5; i++) {
    //Ошибка, при i=5 – выход за пределы массива
    cerr << "ptr[" << i << "] == " << ptr[i] << '\n';
    }
    delete[] ptr;
    return(0);
    } ABW: Array Bounds Write. Выход за пределы массива при записи Вероятность того, что приложение может неадекватно вести себя из-за данной ошибки более высоко, так как запись по адресу, превышающим размер блока, вызывает исключение. Отметим, что память можно определить статически, массовом, как показано в примере. А можно динамически (например, выделив блок памяти по ходу исполнения приложения). По умолчанию, Purify успешно справляется только с динамическим распределением, четко выводя сообщение об ошибке. В случае статического распределения, все зависит от размеров "кучи" и настроек компилятора. #include
    #include
    int main(int, char **)
    {
    char * ptr = new char[5];//Выделяем память под массив из 5символов
    for (int i=0; i <= 5; i++) {
    //Ошибка, при i=5 - выход за пределы массива
    ptr[i] = ‘!’;
    cerr << "ptr[" << i << "] == " << ptr[i] << '\n'; //ABW + ABR when i is 5
    }
    delete[] ptr;
    return(0);
    } ABWL: Late Detect Array Bounds Write. Cообщение указывает, что программа записала значение перед началом или после конца распределенного блока памяти #include
    #include
    int main(int, char **)
    {
    char * ptr = new char[5];//Выделяем память под массив из 5 символов
    for (int i=0; i <= 5; i++) {
    //Ошибка – попытка записи после блока выделенной памяти
    ptr[i] = ‘!’;
    cerr << "ptr[" << i << "] == " << ptr[i] << '\n';
    }
    delete[] ptr; //ABWL: ОШИБКА
    return(0);
    } BSR: Beyond Stack Read. Сообщение указывает, что функция в программе собирается читать вне текущего указателя вершины стека Категория: Stack Error #include
    #include


    #define A_NUM 100
    char * create_block(void)
    {
    char block[A_NUM];//Ошибка: массив должен быть статическим
    for (int i=0; i < A_NUM; i++) {
    block[i] = ‘!’;
    }
    return(block);//Ошибка: неизвестно, что возвращать
    } int main(int, char **)
    {
    char * block;
    block = create_block();
    for (int i=0; i < A_NUM; i++) {
    //BSR: нет гарантии, что элементы из "create_block" до сих пор находятся в стеке cerr << "element #" << i << " is " << block[i] << '\n';
    }
    return(0);
    } FFM: Freeing Freed Memory. Попытка освобождения свободного блока памяти. В большом приложении трудно отследить момент распределения блока и момент освобождения. Очень часто методы реализуются разными разработчиками, и, соответственно, возможна ситуация, когда распределенный блок памяти освободается дважды в разных участках приложения. Категория: Allocations and deallocations #include
    #include
    int main(int, char **)
    {
    char *ptr1 = new char;
    char *ptr2 = ptr1;//Ошибка: должен дублировать объект, а не копировать указатель
    *ptr1 = ‘a’;
    *ptr2 = ‘b’;
    cerr << "ptr1" << " is " << *ptr1 << '\n';
    cerr << "ptr2" << " is " << *ptr2 << '\n';
    delete ptr1;
    delete ptr2;//Ошибка – освобождение незанятой памяти
    return(0);
    } FIM: Freeing Invalid Memory. Попытка освобождения некорректного блока памяти. Разработчики часто путают простые статические значения и указатели, пытаясь освободить то, что не освобождается. Компилятор не всегда способен проанализировать и нейтрализовать данный вид ошибки. Категория: Allocations and deallocations #include int main(int, char **)
    {
    char a;
    delete[] &a;//FIM: в динамической памяти нет объектов для уничтожения
    return(0);
    } FMM: Freeing Mismatched Memory. Сообщение указывает, что программа пробует освобождать память с неправильным ВЫЗОВОМ API для того типа памяти Категория: Allocations and deallocations #include int main(int, char **)


    {
    HANDLE heap_first, heap_second;
    heap_first = HeapCreate(0, 1000, 0);
    heap_second = HeapCreate(0, 1000, 0);
    char *pointer = (char *) HeapAlloc(heap_first, 0, sizeof(int));
    HeapFree(heap_second, 0, pointer);
    //Ошибка – во второй куче не выделялась память HeapDestroy(heap_first);
    HeapDestroy(heap_second);
    return(0);
    } FMR: Free Memory Read. Попытка чтения уже освобожденного блока памяти Все та же проблема с указателем. Блок распределен, освобожден, а потом, в ответ на событие, по указателю начинают записываться (или читаться) данные. Категория: Invalid pointers #include
    #include
    int main(int, char **)
    {
    char *ptr = new char[2];
    ptr[0] = ‘!’;
    ptr[1] = ‘!’;
    delete[] ptr;//Ошибка – освобождение выделенной памяти
    for (int i=0; i < 2; i++) {
    //FMR: Ошибка- попытка чтения освобождённой памяти
    cerr << "element #" << i << " is " << ptr[i] << '\n';
    }
    return(0);
    } FMW: Free Memory Write. Попытка записи уже освобожденного блока памяти Категория: Invalid pointers #include
    #include
    int main(int, char **)
    {
    char *ptr = new char[2];
    ptr[0] = ‘!’;
    ptr[1] = ‘!’;
    delete[] ptr;//специально освобождаем выделенную память
    for (int i=0; i < 2; i++) {
    ptr[i] *= ‘A’; //FMR + FMW: память для *ptr уже освобождена
    cerr << "element #" << i << " is " << ptr[i] << '\n'; //FMR
    }
    return(0);
    } HAN: Invalid Handle. Операции над неправильным дескриптором Категория: Invalid handles #include
    #include
    #include
    int main(int, char **)
    {
    int i=8;
    (void) LocalUnlock((HLOCAL)i);//HAN: i – не является описателем объекта памяти
    return(0);
    } HIU: Handle In Use. Индикация утечки ресурсов. Неправильная индикация дескриптора. #include
    #include
    static long GetAlignment(void)
    {
    SYSTEM_INFO desc;
    GetSystemInfo(&desc);
    return(desc.dwAllocationGranularity);


    }
    int main(int, char **)
    {
    const long alignment = GetAlignment();
    HANDLE handleToFile = CreateFile("file.txt",
    GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL, NULL);
    if (handleToFile == INVALID_HANDLE_VALUE) { cerr << "Ошибка открытия, создания файла\n";
    return(1);
    }
    HANDLE handleToMap = CreateFileMapping(handleToFile, NULL, PAGE_READWRITE, 0, alignment, "mapping_file");
    if (handleToMap == INVALID_HANDLE_VALUE) {
    cerr << "Unable to create actual mapping\n";
    return(1);
    }
    char * ptr = (char *) MapViewOfFile(handleToMap, FILE_MAP_WRITE, 0, 0, alignment); if (ptr == NULL) {
    cerr << " Unable to map into address space\n";
    return(1);
    }
    strcpy(ptr, "hello\n");
    //HIU: handleToMap до сих пор доступен и описывает существующий объект
    return(0);
    } IPR: Invalid Pointer Read. Ошибка обращения к памяти, когда программа пытается произвести чтение из недоступной области Категория: Invalid pointers #include #include
    int main(int, char **)
    {
    char * pointer = (char *) 0xFFFFFFFF;
    //Ошибка - указатель на зарезервированную область памяти for (int i=0; i < 2; i++) {
    //IPR: обращение к зарезервированной части адресного пространства
    cerr << "pointer[" << i << "] == " << pointer[i] << '\n';
    }
    return(0);
    } IPW: Invalid Pointer Write. Ошибка обращения к памяти, когда программа пытается произвести запись из недоступной области Категория: Invalid pointers #include
    #include
    int main(int, char **)
    {
    char *pointer = (char *) 0xFFFFFFFF;
    //Ошибка - указатель на зарезервированную область памяти for (int i=0; i < 2; i++) {
    //IPW + IPR: обращение к зарезервированной части адресного пространства
    pointer[i] = ‘!’;
    cerr << "ptr[" << i << "] == " << ptr[i] << '\n';
    }
    return(0);
    } MAF: Memory Allocation Failure. Ошибка в запросе на распределение памяти.


    Возникает в случаях, когда производится попытка распределить слишком большой блок памяти, например, когда исчерпан файл подкачки. Категория: Allocations and deallocations #include
    #include
    #define BIG_BLOCK 3000000000 //размер блока
    int main(int, char **)
    {
    char *ptr = new char[BIG_BLOCK / sizeof(char)];
    //MAF: слишком большой размер для распределения
    if (ptr == 0) {
    cerr << "Failed to allocating, as expected\n";
    return (1);
    } else {
    cerr << "Got " << BIG_BLOCK << " bytes @" << (unsigned long)ptr << '\n';
    delete[] ptr;
    return(0);
    }
    } MLK: Memory Leak. Утечка памяти Распространенный вариант ошибки. Многие современные приложения грешат тем, что не отдают системе распределенные ресурсы по окончании своей работы. Категория: Memory leaks #include
    #include
    int main(int, char **)
    {
    (void) new int[1000];
    (void) new int[1000];
    //результат потери памяти
    return(0);
    } MPK: Potential Memory Leak. Потенциальная утечка памяти
    Иногда возникает ситуация, в которой необходимо провести инициализацию массива не с нулевого элемента. Purify считает это ошибкой. Но разработчик, пишущий пресловутый текстовый редактор может инициализировать блок памяти не с нулевого элемента. Категория: Memory leaks #include
    #include
    int main(int, char **)
    {
    static int *pointer = new int[100000];
    pointer += 100;//MPK: потеряли начало массива
    return(0);
    } NPR: Null Pointer Read. Попытка чтения с нулевого адреса
    Бич всех программ на С\С++. Очень часто себя ошибка проявляет при динамическом распределении памяти приложением, так как не все разработчики ставят условие на получение блока памяти, и возникает ситуация, когда система не может выдать блок указанного размера и возвращает ноль. По причине отсутствия условия разработчик как не в чем не бывало начинает проводить операции над блоком, адрес в памяти которого 0. Категория: Invalid pointers #include


    #include
    int
    main(int, char **)
    {
    char * pointer = (char *) 0x0; //указатель на нулевой адрес
    for (int i=0; i < 2; i++) {
    //NPR: попытка чтения с нулевого адреса
    cerr << "pointer[" << i << "] == " << pointer[i] << '\n';
    }
    return(0);
    } NPW: Null Pointer Write. Попытка записи в нулевой адрес Категория: Invalid pointers #include
    #include
    int
    main(int, char **)
    {
    char * pointer = (char *) 0x0; //указатель на нулевой адрес
    for (int i=0; i < 2; i++) {
    //NPW: ошибка доступа
    pointer[i]=’!’;
    cerr << "pointer[" << i << "] == " << pointer[i] << '\n';
    }
    return(0);
    } UMC: Uninitialized Memory Copy. Попытка копирования непроинициализированного блока памяти Категория: Unitialized memory #include
    #include
    #include int main(int, char **)
    {
    int * pointer = new int[10];
    int block[10];
    for (int i=0; i<10;i++)
    {
    pointer[i]=block[i]; //UMC предупреждение
    cerr<
    delete[] pointer;
    return(0);
    } UMR: Uninitialized Memory Read. Попытка чтения непроинициализированного блока памяти Категория: Unitialized memory #include
    #include
    int main(int, char **)
    {
    char *pointer = new char;
    cerr << "*pointer is " << *pointer << '\n';
    //UMR: pointer указывает на непроинициализированный элемент
    delete[] pointer;
    return(0);
    }

    Список вызовов функций "Function List"

    Одно из наиболее важных статистических окон. Здесь в табличном виде выводится статистическая информация по числу и времени работы каждой функции. Рисунок 6 демонстрирует окно с включенной сортировкой по числу вызовов каждой функции. В качестве дополнительной информации включен список формальных параметров вызовов функций. Подобную информацию можно получить только в том случае, когда тестируется модуль с отладочным кодом, к которому прилагается исходный текст. Единицы измерения длительности работы функций могут быть следующими:
  • Микросекунды;
  • Миллисекунды;
  • Секунды;
  • Машинные циклы.
  • На рисунке приведены цифры соответствующие машинным циклам. Список вызовов функций Полученная таблица вызовов анализируется тестером или разработчиком для выяснения узких мест в производительности приложения. К сожалению, для принятия решения о производительности приложения, а точнее, производительности отдельных его функций можно принимать только рассматривая данный вопрос в комплексном разрезе. А именно, принимается во внимание и число вызовов каждой функции, и среднее время доступа к функции и общее время работы функции, и, наконец, то использовались ли при компиляции определенные специфические настройки компилятора. Это комплексный подход, не предполагающий однозначного совета. Сначала рассмотрим описание столбцов в появившейся таблице. Хотя многие из пунктов и являются интуитивно понятными, все же попробуем дать им короткое описание:
  • Function. Наименование функции. Можно высвечивать число и тип формальных параметров вызова данной функции.
  • Calls. Число вызовов. Величина абсолютная.
  • Function Time. Общее время исполнения всех вызовов данной функции
  • Max F Time. Максимальное время функции
  • Module. Полный путь до модуля с функцией (бинарного)
  • Min F Time. Минимальное время работы функции
  • Source File. Полный путь до исходных текстов модуля.
  • По любому из предложенных полей можно провести сортировку в прямом и обратном порядке, а также наложить фильтр на определенные модули, например, для проверки только внутренних модулей или только внешних. Выделить из списка узкую функцию трудно, поскольку для правильного расчета нужно принимать во внимание и время работы функции и число вызовов.
    Причем, число вызовов не всегда может быть показателем медлительности функции (вызывается часто, а работает быстро). Трудно давать какие либо советы по оптимизации кода, тем более, что в этой редакции мы не ставили перед собой подобных целей. По теории оптимизации написаны громадные труды, к которым мы с радостью и отправляем читателей. Можно, конечно, дать общие рекомендации по улучшению производительности кода и его эффективности: Использовать конструкцию "I++" вместо "I=I+1", так как компиляторы транслируют первую конструкцию в более эффективный код. К сожалению, этот эффективность примера ограничена настройками используемого компилятора, и иногда бывает равнозначной по быстродействию; Использовать прием развертывания циклов. Такой же старый прием оптимизации работы относительно простых циклов. Эффект заключается в сокращении числа проверок условия счетчика, так при проверке выполняются очень медленные функции микропроцессора (функции перехода). То есть вместо кода: For(i=0;i<100;i++)sr=sr+1; Лучше писать: For(i=0;i<100;i+=2)
    {
    sr++;
    sr++;
    } Использовать тактику отказа от использования вычисляющих конструкций внутри простых циклов. То есть, если иметь подобный фрагмент кода: for (sr = 0; sr < 1000; sr++)
    {
    a[sr] = x * y;
    } то его лучше преобразовать в такой: mn= x * y;
    for (sr = 0; sr < 1000; sr++)
    {
    a[sr] = mn;
    } поскольку мы избавляемся от лишней операции умножения в простом цикле; Там где возможно при работе с многомерными массивами, обращаться с ними как с одномерными. То есть, если есть необходимость в копировании или инициализации, например, двумерного массива, то вместо кода: int sr[400][400];
    int j, i; for (i = 0; i < 400; i++)
    for (j = 0; j < 400; j++)
    sr[j][i] = 0; лучше использовать конструкцию, в которой нет вложенного цикла: int sr[400][400];
    int *p = &sr[0][0]; for (int i = 0; i < 400*400; i++)
    p[sr] = 0; // или *p++=0, что для большинства компиляторов одно и тоже Также при работе с циклами выгодно использовать слияния, когда несколько коротких однотипных циклов сливаются вместе.


    Подобный подход также дает прирост в производительности кода; В математических приложениях, требующих больших вычислений с плавающей точкой, или с большим количеством вызовов тригонометрических функций, удобно не производить все вычисления непосредственно, а использовать подготовленные значения в различных таблицах, обращаясь к ним как к индексам в массиве. Подход очень эффективен, но, к сожалению, как и многие эффективные подходы применим не всегда; Короткие функции в классах лучше оформлять встроенными (inline); В строковых операциях, в операциях копирования массивов лучше пользоваться не собственными функциями, а применять для этого стандартные библиотеки компилятора, так как эти функции, как правило, уже оптимизированы по быстродействию; Использовать команды SSL и MMX, поскольку в достаточно большом круге задач они способны дать ускорение работы приложений в разы. Под такие задачи попадают задачи по работе с матрицами и векторами (арифметические операции над матрицами и векторами); Использовать инструкции сдвига вместо умножений и делений там, где это позволяет делать логика программы. Например, инструкция S=S<<1 всегда эффективнее, чем S=S*2; Конечно, это далеко не полный список приемов оптимизации кода по производительности и качеству. Для этого есть масса других книг. Примеры здесь имеют чисто утилитарный подход: демонстрация возможностей Quantify в плане исследования временных характеристик кода. Используя все средства сбора и отображения, разработчик постепенно сможет использовать только эффективные конструкции, что поднимет производительность на недосягаемую ранее высоту. По любой функции можно вывести более детальный отчет (см. рисунок). Из него можно почерпнуть информацию о числе дочерних функций и то, откуда они были произведены. Следующий рисунок демонстрирует данную возможность. Список вызовов функций Переход к просмотру исходного текста. Если тестируемый модуль сопровождается исходным текстом, то в Quantify имеется возможность по переходу на уровень просмотра исходного текста.По контекстному меню можно осуществить данный переход. Вызывать функцию перехода имеет смысл только в том случае, когда Quantify работает в независимом режиме, в отрыве от среды разработки. Рисунок демонстрирует данный режим.

    Способы запуска

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


  • Сравнивание запусков "Compare Runs"

    В большинстве случаев требуется иметь не только сведения об отдельных запусках, но и сравнения разных запусков в различных комбинациях для прогнозирования и анализа. Ведь всегда интересно знать быстро работает исправленная функция или медленно, по сравнению с тем, что было до этого. Подобная аналитическая информация позволить иметь достаточно четкое представление о том находятся ли функции в прогрессирующем или в регрессирующем состоянии. Сравнивание запусков Для вызова модуля сравнения необходимо воспользоваться кнопкой (Compare Runs), выделив один из запусков, и указав на любой другой (каждый новый запуск отображается в истории запусков на левой части рабочего поля Quantify). Для осуществления не пустого сравнения, в пример, рассмотренный выше, намеренно были внесены изменения, увеличившие число вызовов функций. Данные были сохранены и перекомпилированы и снова исполнены в Quantify. Результат представлен на рисунке: Сравнивание запусков Сравнение запусков позволяет проводить сравнительный анализ между базовым запуском (base – том, с которого все началось) и новым (new). Результаты сравнения также остаются в проекте Quantify и сохраняются на протяжении жизненного цикла разработки проектов. Наравне со сравнением запуск можно воспользоваться суммированием, нажав на кнопку Сравнивание запусков . Эта функция вызывает простое суммирование чисел от двух запусков. Можно сравнивать и складывать также запуски вместе со слепками (snapshot), которые позволяют оформить текущее количественное состояние в работе приложения в виде отдельного запуска. В дальнейшем над ним можно провести любую логическую операцию. Ценность слепков проявляется тогда, когда необходимо узнать число вызовов каждой функции до свершения определенного события, например, до входа в определенный пункт меню в тестируемом приложении.

    Тестирование сервисов Windows NT//XP

    Одно из важных преимуществ перед конкурентами — возможность тестирования специальных приложений, таких как сервисы. собенность работы заключается в том, что сервис нельзя запускать из среды разработки. Инструменты тестирования понимают это, и предлагают свое решение, по которому необходимо насытить код откомпилированного сервиса (с отладочной информацией) отладочной информацией (не запустив при этом). Полученные в результате файл зарегистрировать в качестве сервиса. Сделать это можно как из GUI так и из командной строки. Мы рассмотрим последовательность шагов для командной строки, демонстрируя ее возможности (в примере, используется ссылка на Purify, но вместо него подставить имя любого средства тестирования):
  • Правильно настроить системные пути таким образом, чтобы из них были видны все директории Purify (особенно кеш: \Program Files\Rational\Purify), иначе процесс не пойдет на исполнение;
  • Откомпилированный сервис нужно запустить Purify из командной строки следующим образом: purify /Run=no /Out=service_pure.exe service.exe. Как видно из параметров, Purify инструментирует файл service.exe, помещая его копию вместе с OCI в service_pure.exe. Все происходит без запуска;
  • В ключе реестра \HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services необходимо поставить ссылку на кешированный файл (service_pure.exe);
  • Во вкладке сервисов активировать пункт Allow Service to Interact with Desktop, выбрать режим запуска "manual".
  • По окончании действий, в зависимости от типа операционной системы, необходимо перезапустить сервис или перезагрузить компьютер и запустить сервис. В момент старта сервиса инструмент тестирования запустится автоматически. Поскольку специальные программные средства могут не иметь графической оболочки, то для их детальной проработки рекомендуется использование функций API из инструментов тестирования для разработчиков.

    Приведем список программных продуктов, рекомендованных

    Приведем список программных продуктов, рекомендованных к использованию в тестировании на разных этапах. Вот их наименования и краткое описание: Rational Quantify. Профилирование производительности;
    Rational Purify. Отслеживание ошибок с распределением памяти;
    Rational PureCoverage. Отслеживание области покрытия кода;
    Rational TestManager. Планирование тестирования;
    Rational Robot. Выполнение функциональных тестов. Программные продукты будут описаны в порядке применения в проектах. Сначала рассмотрим средства тестирования для разработчиков (Quantify, Purify, PureCoverage). Данные средства неразрывно связаны с языком реализации и со средой разработки. Примеры, приведенные в книги ориентированы на язык программирования С++ и частично на С#. В связи с тем, что на момент написания была доступна только бета-версия Visual Stu-dio .NET, то мы в основном ориентировались на версию 6.0, лишь изредка демонстрируя возможности новой среды. Тоже касается и языка реализации C#. Примеры его использования также будут встречаться, но все же основное внимание будет уделено языку реализации С++, имеющем наивысший рейтинг среди языков для реализации крупных программных систем. К сожалению, за бортом остались Java и Basic, но мы надеемся, что разработчики воспримут все написанное здесь как модель реализации, подходы которой совместимы со многими языками программирования. В следующей части перейдем к функциональному и нагрузочному тестированию. Начнем рассмотрение функционального тестирования с его планирования, чьи данные неразрывно связаны с требованиями, полученными на этапе определения требований к системе. Test Manager, в этом разрезе является как средством планирования тестирования, так средством реализации различных видов тестирования, так и средством предоставления финальной, пост — тестовой, отчетности. Далее воспользуемся продуктом Robot, который осуществляет физическую запись скриптов тестирования, с их последующим воспроизведением. Ознакомимся с визуальными и ручными режимами составления тестов посредством Robot. Научимся составлять как функциональные скрипты так и варианты нагрузочных скриптов. Рассмотрим различные режимы работы продукта. Для полного понимания возможностей, опишем основные синтаксические нотации скриптовых языков SQA Basic. В завершении опишем связи средств тестирования с остальными программными продуктами Rational.
    Quantify, вставляя отладочный код в бинарный текст тестируемого модуля, замеряет временные интервалы, которые прошли между предыдущим и текущими запусками. Полученная информация отображается в нескольких видах: табличном, графическом, комбинированном. Статистическая информация от Quantify позволит узнать какие dll библиотеки участвовали в работе приложения, узнать список всех вызванных функций с их именами, формальными параметрами вызова и с статистическим анализатором, показывающим сколько каждая функция исполнялась. Гибкая система фильтров Quantify позволяет, не загромождая экран лишними выводами (например, системными вызовами), получать необходимую информацию либо только о внутренних, программных вызовах либо только о внешних, либо комбинируя оба подхода. Вооружившись полученной статистикой, разработчик без труда выявит узкие места в производительности тестируемого приложения и устранит их в кратчайшие сроки.


    Начать описание возможностей продукта Rational Purify хочется перефразированием одного очень известного изречения: "с точностью до миллиБАЙТА". Данное сравнение не случайно, ведь именно этот продукт направлен на разрешение всех проблем, связанных с утечками памяти. Ни для кого не секрет, что многие программные продукты ведут себя "не слишком скромно", замыкая на себя во время работы все системные ресурсы без большой на то необходимости. Подобная ситуация может возникнуть вследствие нежелания программистов доводить созданный код "до ума", но чаще подобное происходит не из за лени, а из-за невнимательности. Это понятно — современные темпы разработки ПО в условиях жесточайшего прессинга со стороны конкурентов не позволяют уделять слишком много времени оптимизации кода, ведь для этого необходимы и высокая квалификация, и наличие достаточного количества ресурсов проектного времени. Как мне видится, имея в своем распоряжении надежный инструмент, который бы сам в процессе работы над проектом указывал на все черные дыры в использовании памяти, разработчики начали бы его повсеместное внедрение, повысив надежность создаваемого ПО. Ведь и здесь не для кого не секрет, что в большинстве сложных проектов первоочередная задача, стоящая перед разработчиками заключается в замещении стандартного оператора "new" в С++, так как он не совсем адекватно себя ведет при распределении памяти. Вот на создании\написании собственных велосипедов и позволит сэкономить Purify. Рисунок демонстрирует внешний вид программы после проведения инструментирования (тестирования) программного модуля. Приведем список программных продуктов, рекомендованных Общие возможности по управлению Purify схожи с Quantify, за исключением специфики самого продукта. Здесь также можно тестировать многопоточные приложения, также можно делать "слепки" памяти во время тестирования приложения. Особенности использования данного приложения касаются спецификой отлавливаемых ошибок и способом выдачи информации. Информация выдается в виде списке, с наименованием найденной ошибки или предупреждения. При разворачивании списка с конкретной ошибкой выводится дополнительный набор данных, характеризующих ошибку.


    Основное назначение продукта — выявление участков кода, пропущенного при тестировании приложения — проверка области охвата кода. Очевидно, что при тестировании разработчику или тестировщику не удастся проверить работоспособность абсолютно всех функций. Также невозможно за один проход тестирования исполнить приложение с учетом всех условных ветвлений. По требованиям на разработку программного кода, программист должен предоставить для функционального тестирования стабильно работающее приложение или модуль, без утечек памяти и полностью протестированный. Понятие "полностью протестированный" определяет руководство компании в числовом, процентном значении. То есть, при оформлении требований указано, что область охвата кода 70%. Соответственно, по достижении данной цифры дальнейшие проверки кода можно считать нецелесообразными. Конечно, вопрос области охвата, очень сложный и неоднозначный. Единственным утешением может служить то, что 100% области охвата в крупных проектах не бывает. Из трех рассматриваемых инструментов тестирования PureCoverage можно считать наиболее простым, так как информация им предоставляемая — это просмотр исходного текста приложения, где указано сколько раз исполнилась та или иная строка в приложении.

    Запуск приложений

    Запуск приложений Рисунок 1 показывает действия после выбора "File->Run", в результате которого можно выбрать имя внешнего модуля и аргументы его вызова. В качестве параметров настройки можно выбрать метод вставки отладочного кода:
  • Line. Наилучший способ вставки отладочного кода. Замеряется время исполнения каждой строки тестируемого приложения.
  • Function. То же самое, что и для "line", но с замером для времени исполнения вызываемых функций.
  • Time. Осуществляет сбор временной информации и преобразует ее в машинные циклы.
  • По умолчанию Quantify собирает статистическую информацию в модуле тестируемого продукта и во всех внешних библиотеках. Запуск приложений Начало насыщения тестируемого приложения сопровождается появлением окна инструментирования, в котором построчно отображаются все модули, вызываемые основным. Данные модули, как говорилось выше, насыщаются отладочным кодом и помещаются в специальную директорию "cache" по адресу "\rational\quantify\cache". Отметим, что первоначальный запуск инструментирвания процесс длительный, но каждый последующий вызов сокращает общее время ожидания в силу того, что вся необходимая информация уже есть в Кеше. С точки зрения дисковой емкости, файл (кэшируемый) с отладочной информацией от Quantify вдвое длиннее своего собрата без отладочной информации.

    Тестирование софта - статьи

    Аннотация.

    В статье рассказывается о технической стороне разработки стандарта Linux Standard Base и связанной с ним инфраструктуре. Описывается использование базы данных для хранения части информации, входящей в стандарт. Обсуждается процесс генерации на основе этих данных как непосредственно текста стандарта, так и сопутствующих объектов - наборов элементарных тестов, заголовочных файлов, отвечающих стандарту LSB, и пр. Также рассматриваются задачи по развитию существующей инфраструктуры, которые планируется решить в рамках совместного проекта ИСП РАН и организации Free Standards Group, под эгидой которой проводится разработка стандарта LSB.

    Атомарные требования и тестовые наборы

    В настоящее время достаточно сложно получить информацию о существующих тестовых наборах для проверки соответствия стандарту LSB и о покрытии, которое они обеспечивают. Такую информацию изначально планировалось хранить в базе данных стандарта - для этого в таблице Interface есть поле Itested, показывающее, тестируется ли интерфейс тестовыми наборами, используемыми при сертификационном тестировании. Кроме того, есть таблица TestSuite, содержащая информацию о доступных тестовых наборах, а также таблицы TestInt и TestCmd, показывающие, какие интерфейсы и команды покрываются тестовыми наборами. Однако сейчас нет никакого способа заполнять эти таблицы автоматически - для их заполнения необходимо проводить анализ отчетов о тестировании либо непосредственно тестовых наборов и определять, на тестирование каких интерфейсов и команд нацелен каждый тест. Последний раз такая работа проводилась в середине 2002 года. С тех пор информация, касающаяся тестов и покрытия, в базе данных не обновлялась, и на текущий момент практически полностью устарела. Так, из 32721 интерфейса, включенных в LSB версии 3.1 (учитывая все архитектуры), только 184 помечены как протестированные (Itested='Yes') и еще для 1700 содержатся записи в таблице TestInt (что означает, что для 1700 интерфейсов хотя бы один из тестовых наборов содержит какой-то тест). С командами ситуация несколько лучше - из 141 команды, включенной в стандарт, для 90 существуют тесты. Кроме того, наличие всего одного поля Itested и записей в таблице TestInt для каждого интерфейса представляется недостаточным. Ведь в случае, если интерфейс является функцией, для него может существовать целый набор требований, и тестовые наборы могут проверять только часть из них. При существующей структуре определить полноту тестирования каждого конкретного интерфейса невозможно. Для обеспечения более полной информации о покрытии тестами интерфейсов планируется для каждого интерфейса хранить список атомарных требований, и осуществлять привязку тестов не к самим интерфейсам, а к этим атомарным требованиям.
    Рассматривается возможность хранить атомарные требования не непосредственно в базе данных, а в отдельных xml-файлах; база данных при этом будет содержать только ссылки на соответствующие xml-файлы. Такая организация представляется более предпочтительной, чем размещение всех требований в БД. Последнее увеличит размер БД в несколько раз, в то время для как большинства инструментов, работающих с БД (генераторы текста стандарта, генераторы библиотек-"заглушек" и т.п.) эти данные не нужны. Атомарные требования необходимо выделять из спецификации интерфейса. В LSB такие спецификации либо создаются вручную и хранятся в отдельных файлах формата sgml, либо вместо детального описания содержится ссылка на другой стандарт, где такое описание присутствует. Таким образом, для выделения атомарных требований необходимо анализировать описания интерфейсов, написанные человеком. Естественно, непосредственно анализ описаний автоматизировать трудно, однако планируется предоставить инструмент, который позволит упростить процесс занесения атомарных требований в базу данных. Задачу существенно усложняет наличие множества ссылок на другие документы - для выделения атомарных требований для всех интерфейсов необходимо иметь тексты всех этих стандартов (на данный момент в описаниях интерфейсов содержатся ссылки на 39 различных документов).

    База данных стандарта LSB

    Во многих случаях работа со стандартом упростилась бы при наличии описания объектов стандарта и их взаимосвязей в виде, более пригодном для обработки различными программными инструментами, чем обычный текст. Так, в примере из введения генерации заголовочных файлов, содержащих только стандартизованные функции, удобно было бы иметь готовые списки заголовочных файлов и функций, а также знать, какие функции описаны в каждом заголовочном файле. Free Standards Group дает возможность получить такую информацию для всех сущностей, определенных в стандарте LSB.

    Библиотеки-"заглушки" и заголовочные файлы

    Говорить о LSB-совместимости можно применительно не только к дистрибутивам Linux, но и к отдельным программ. Дистрибутив соответствует стандарту LSB, если он содержит все объекты, описываемые LSB, с характеристиками, указанными в стандарте. Программа же является LSB-совместимой, если она в своей работе использует только те объекты, которые описаны в LSB. Таким образом, любая LSB-совместимая программа будет работать в любом дистрибутиве, отвечающим требованиям этого стандарта (поскольку LSB стандартизует интерфейсы на бинарном уровне, а не на уровне исходного кода, то для запуска программы на разных дистрибутивах не требуется перекомпиляция). Однако, даже если программа пишется в дистрибутиве, отвечающим требованиям LSB, нельзя быть уверенным, что она будет LSB-совместимой - ведь соответствие дистрибутива стандарту не означает, что в нем нет каких-то дополнительных интерфейсов или команд, которые могут оказаться задействованы в разрабатываемой программе. Для проверки того факта, что программа является LSB-совместимой, рекомендуется производить тестовую сборку программы в так называемом LSB-совместимом окружении. Это окружение включает в себя библиотеки-"заглушки" и заголовочные файлы, для генерации которых предоставляются специальные скрипты. Скрипты создают исходный код для библиотек, описанных в LSB, включая туда все определенные в стандарте LSB интерфейсы с соответствующими сигнатурами. Однако генерируемый исходный код всех интерфейсов-функций не содержит ничего полезного - если функция не возвращает никаких значений, то тело функции просто пусто, в противном случае генерируется конструкция, возвращающая какое-нибудь значение требуемого типа. Таким образом, весь этот исходный код может быть скомпилирован, в результате чего и будут получены библиотеки-"заглушки" - эти библиотеки формально содержат все интерфейсы из стандарта LSB и не содержат никаких других интерфейсов, однако функции, содержащиеся в них, ничего не делают. Помимо библиотек-"заглушек" скрипты создают заголовочные файлы, содержащие описания тех и только тех объектов (типов, констант, функций), которые входят в LSB. При этом свойства этих объектов (размеры типов, значения констант, сигнатуры функций) полностью соответствуют стандарту. Таким образом, если программу удается скомпилировать в таком окружении (т.е. с использованием только библиотек-"заглушек" и сгенерированных заголовочных файлов), то можно гарантировать, что она использует только объекты, описанные в LSB. Однако проводить функциональное тестирование программы все-таки необходимо с использованием реальных библиотек. Поэтому для получения уверенности в том, что программа будет выполнять поставленные перед ней задачи на любой LSB-совместимой платформе, функциональное тестирование программы необходимо проводить на дистрибутиве, соответствующем стандарту LSB. Созданию LSB-совместимых приложений с использованием LSB-совместимого окружения посвящена отдельная книга [], выпущенная разработчиками стандарта и доступная в электронном виде на сайте Free Standards Group.

    Чистка базы данных стандарта LSB

    В процессе развития стандарта схема базы данных стандарта изменялась не один раз. К сожалению, не все изменения схемы сопровождались соответствующими корректными преобразованиями данных и инструментов, работающих с базой данных. В результате во многих таблицах появились поля, устаревшие с точки зрения структуры базы данных, но по-прежнему используемые частью скриптов. При этом были примеры несогласованности значений устаревших полей со значениями структур, пришедших им на смену. В результате скрипты, использовавшие устаревшие поля, генерировали некорректные данные. Проиллюстрируем сказанное на примере связи между интерфейсами и архитектурами. Первые версии стандарта LSB описывали бинарные интерфейсы только для одной архитектуры IA32, и схема базы данных не позволяла хранить информацию о различных архитектурах. Однако уже в LSB версии 1.2 появилась поддержка второй архитектуры (PPC32), и соответствующие изменения претерпела схема БД. Изначально в таблице Interface, хранящей данные об интерфейсах, было введено специальное поле Iarch, ссылающееся на запись в таблице Architecture. Однако связь между интерфейсами и архитектурами в действительности является связью "многие ко многим": один интерфейс может быть определен для нескольких архитектур. Для таких интерфейсов предлагалось вводить несколько записей в таблице Interface, по одному для каждой архитектуры (соответственно, эти записи различались значением поля Iarch и, возможно, значениями некоторых архитектурно-зависимых свойств). Практика быстро показала, что решение заводить несколько записей для каждого интерфейса было не очень удачным. При таком подходе во всех таблицах, ссылающихся на интерфейс, приходилось также заводить несколько записей, соответствующих различным записям в таблице интерфейсов. Поскольку большая часть информации об интерфейсе не зависит от архитектуры, во многих таблицах данные просто дублировались, что приводило к неоправданному росту объема данных. В результате для реализации связей "многие ко многим" между таблицей интерфейсов и таблицей архитектур была создана отдельная таблица ArchInt.
    После этого все связи между интерфейсами и архитектурами вносились в таблицу ArchInt, однако поле Iarch убрано не было - к этому моменту многие инструменты, работающие с базой данных, использовали это поле, а для их быстрой переработки не хватало ресурсов. Кроме того, в таблице Interface уже было более 5000 записей, созданных исключительно для реализации связи "многие ко многим" между интерфейсами и архитектурами. Они не были удалены, а в таблицу ArchInt не была занесена соответствующая им информация. Такой "частичный" переход к использованию таблицы ArchInt привел к тому, что часть информации о связи "многие ко многим" хранилась в этой таблице, а часть задавалась дублированием записей с изменением поля Iarch. Часть инструментов была переписана с учетом появления новой таблицы; при этом они учитывали как данные из ArchInt, так и поле Iarch (в основном это относится к скриптам, генерирующим текст стандарта - их корректность имеет наивысший приоритет). Вновь написанные инструменты опирались только на таблицу ArchInt, в то время как часть старых инструментов так и не была переписана и использовала только Iarch. Избавление от проблемы со связями между архитектурами и интерфейсами было произведено в ходе чистки схемы и данных базы данных стандарта LSB на первом этапе разработки новой тестовой инфраструктуры LSB, в четвертом квартале 2006 года. В частности, было удалено поле Iarch, все необходимые данные перенесены в таблицу ArchInt и произведены соответствующие изменения скриптов. Помимо этого, были произведены следующие действия по устранению нестыковок и неоднородностей:
  • Унификация названий полей и типов перечислений, имеющих одинаковую семантику в различных таблицах. Например, статус команд в стандарте хранился в поле Command.Cstatus, имевшем тип enum ('Included', 'Excluded', 'Builtin', 'Unknown'), а статус констант в стандарте хранился в поле Constant.Cstd, имевшем тип enum ('Yes', 'No', 'SrcOnly'). Теперь все поля, обозначающие статус какого-либо объекта в стандарте, имеют имена, оканчивающиеся на "stdstatus", и для всех таких полей используется одно и то же перечисление.
  • Удаление устаревших либо не используемых полей и таблиц.
  • Исправление в таблице Type некорректных значений некоторых полей, полученных при автоматическом заполнении базы.Например, для некоторых типов-перечислений объявление типа не было корректно обработано, и тип не был определен в таблице как перечисление, а ключевое слово "enum" просто присоединялось к имени типа.
  • Согласование типов полей-ссылок с типами полей, на которые они ссылаются.
  • Удаление дублирующихся индексов и добавление индексов, ускоряющих часто используемые запросы к базе данных (на основе анализа скриптов, работающих с БД). Появление дублирующихся индексов было вызвано особенностями архитектуры СУБД MySQL, которая при создании индекса вида (a,b,c) автоматически создает индексы (a,b) и (a).

    Генерация текста стандарта

    База данных активно используется при генерации текста стандарта LSB. Ведь основное содержание всех документов LSB - это перечень интерфейсов, которые должны присутствовать в системе, и их описаний. Интерфейсы разбиваются по библиотекам либо по классам; кроме того, внутри каждой библиотеки возможна дополнительная группировка, как было указано в предыдущем разделе. Также указывается, какие заголовочные файлы необходимо подключить для использования того или иного интерфейса, а для интерфейсов-функций приводится их сигнатура. Здесь снова стоит отметить, что стандарт LSB предназначен для обеспечения переносимости приложений на бинарном уровне, а не на уровне исходного кода. Для бинарной переносимости не важно, в каких заголовочных файлах определяются конкретные объекты - главное, чтобы эти объекты находились в заданных стандартом библиотеках. Поэтому в LSB описывается расположение объектов по заголовочным файлам, принятое в большинстве дистрибутивов Linux. Цель этого описания - помочь разработчикам определить местоположение нужного интерфейса в ходе написания программы; однако действительное место описания объекта в конкретном дистрибутиве может отличаться от приведенного в LSB, при этом требования стандарта не будут нарушены, если объект входит в предписанную библиотеку. Перечни интерфейсов с краткой информацией создаются специальными скриптами на основе сведений из базы данных стандарта LSB. Скрипты создают текст стандарта в формате sgml, используя в некоторых случаях заранее заготовленные шаблоны. На основе сгенерированных sgml-файлов создаются файлы в наиболее распространенных форматах - html, rtf, ps, pdf, а также файлы в текстовом формате (естественно, лишенного такого преимущества остальных форматов, как навигация по ссылкам внутри документа, что полезно при чтении стандарта). На основе соответствующих таблиц также создаются списки констант и определяемых пользователем типов (перечислений, структур и т.п.), сгруппированных по заголовочным файлам, в которых они описаны, список секций исполняемых файлов формата ELF и список тэгов файлов формата RPM. Что касается более детальных описаний интерфейсов (например, описания того, что делает функция), то здесь стандарт LSB в большинстве случаев ссылается на другие стандарты (такие, как POSIX, стандарт ISO/IEC 9899 для языка C и т.д.). Информация о том, где искать описание каждого конкретного объекта, также содержится в базе данных стандарта - для этого заведена отдельная таблица, содержащая все стандарты, на которые есть ссылки из LSB, и каждая запись в таблицах объектов ссылается на запись в таблице стандартов. Соответственно в тексте стандарта рядом с каждым интерфейсом указано, где искать его подробное описание. Описания некоторых интерфейсов содержится непосредственно в LSB, но их число очень невелико (на ноябрь 2006 г. - 454 из 32721 интерфейса, включенных в текст стандарта). Эти описания создаются вручную (также в формате sgml) и автоматически включаются в нужные места при генерации стандарта.

    Генерация зависящих от стандарта объектов

    Free Standards Group предоставляет средства для генерации не только самого текста стандарта, но и различных объектов, зависящих от этого стандарта. К таким объектам относятся:
  • библиотеки-"заглушки" ("stub libraries");
  • заголовочные файлы с описаниями объектов, определенных в стандарте LSB;
  • элементарные тесты для проверки соответствия дистрибутива либо приложения стандарту LSB. Рассмотрим эти объекты подробнее.

    Информация о дистрибутивах

    Особенностью стандарта LSB является ориентированность на основные дистрибутивы Linux - в стандарт включаются только те объекты, которые присутствуют в последних версиях большинства основных дистрибутивов. (Формально списка "основных дистрибутивов" нет - согласно официальной доктрине, в LSB вносится все то, что "является устоявшейся практикой в мире Linux"; однако в реальной жизни невозможно исследовать все дистрибутивы, и основное внимание уделяется RedHat, SuSe, Mandriva, Asianux, Debian и Ubuntu). Одним из важных аспектов разработки стандарта является отслеживание списка интерфейсов, команд и пр., используемых в различных версиях дистрибутивов. Если какой-то интерфейс либо команда присутствуют почти везде и востребованы разработчиками приложений, но еще не включены в LSB, то они объявляются кандидатами на включение в следующую версию стандарта. Ручная обработка списка интерфейсов и определение кандидатов на включение в стандарт - трудоемкая работа, и в настоящее время назрела потребность в ее автоматизации. Для упрощения работы с дистрибутивами планируется, прежде всего, добавить в базу данных таблицу, содержащую список дистрибутивов (включая различные версии одного и того же дистрибутива). Также планируется добавить таблицу с компонентами, содержащимися в этих дистрибутивах (glibc, zlib, Qt, Gtk и т.д.), и привязку записей из этих таблиц к объектам, описываемых стандартом - библиотекам, заголовочным файлам, интерфейсам и пр. Информацию о составе дистрибутивов (ассортимент и версии входящих в них компонентов, какие интерфейсы/библиотеки/заголовочные файлы содержатся в компонентах и т.п.) планируется заполнять автоматически на основе анализа установленного дистрибутива. В перспективе для занесения сведений об очередной версии дистрибутива в базу данных разработчику необходимо будет лишь запустить утилиты сбора информации о дистрибутиве (естественно, для обеспечения полноты сведений утилиты необходимо запускать на дистрибутиве, установленном в полной комплектации). При наличии информации об основных дистрибутивах и их составе можно будет автоматически определять объекты, являющиеся кандидатами на включение в стандарт, а также объекты, которые в ближайших версиях стандарта следует объявить устаревшими, а впоследствии удалить.

    Информация об архитектурах

    Стандарт LSB специфицирует интерфейсы операционной системы на бинарном уровне. Естественно, это делает стандарт зависимым от архитектуры аппаратного обеспечения - для каждой архитектуры необходима своя версия стандарта. Поэтому стандарт LSB содержит различные документы для различных аппаратных платформ - например, все, что относится к стандартной библиотеке C++, описывается в документах LSB-CXX-IA32 (для архитектуры IA32), LSB-CXX-AMD64 (для архитектуры AMD64) и т.д (в LSB версии 3.1 поддерживаются семь архитектур - AMD64, IA32, IA64, PPC32, PPC64, S390 и S390X). Кроме того, есть так называемая общая спецификация LSB (LSB Generic Specification), описывающая объекты, которые должны присутствовать во всех LSB-совместимых реализациях, независимо от аппаратной платформы. Для хранения списка аппаратных платформ в схеме базы данных предусмотрена таблица Architecture. Многие объекты, информация о которых содержится в базе данных, включены в спецификации для одних платформ и отсутствуют в спецификациях для других. Кроме того, каждый объект, описываемый стандартом, на каждой платформе может иметь какие-то специфические особенности. Поэтому между таблицами объектов и таблицей архитектур существуют связи "многие ко многим", которые реализуются посредством отдельных таблиц, содержащих пары "идентификатор объекта, идентификатор архитектуры". Эти таблицы также содержат те свойства объектов, которые могут иметь различные значения на различных архитектурах (например, значения констант). Среди идентификаторов архитектур есть одно выделенное значение - "All". Если объект приписан к архитектуре с этим идентификатором, то он должен присутствовать во всех архитектурах, поддерживаемых LSB, и при этом на всех архитектурах его свойства должны быть одинаковы (допускается одно исключение для версий интерфейсов - для интерфейса, приписанного к архитектуре All, можно завести отдельную запись-привязку к конкретной архитектуре, указав там значение версии, не меньшее, чем для архитектуры All). Безусловно, можно было бы обойтись и заведением необходимого количества записей для объекта (по одной записи для каждой поддерживаемой архитектуры), но идентификатор "All" имеет дополнительную смысловую нагрузку - именно объекты, привязанные к архитектуре All, попадают в общую спецификацию LSB. При отсутствии такого идентификатора для каждого объекта приходилось бы проверять, что он привязан ко всем поддерживаемым архитектурам и при этом обладает на каждой из них одними и теми же свойствами. Такие проверки существенно усложнили бы создание генераторов текста стандарта и связанных с ним объектов, о которых пойдет речь в следующих двух разделах.

    Информация об объектах, описанных в LSB

    Для стандарта LSB в качестве хранилища объектов, описываемых стандартом, основной информации о них и связей между ними используется так называемая база данных стандарта LSB (LSB Specification Database). Более точно, в этой базе данных содержится информация о следующих объектах:
  • библиотеки (таблица Library);
  • классы (таблица ClassInfo);
  • интерфейсы - LSB является бинарным стандартом и описывает интерфейсы, предоставляемые каждой библиотекой. Все интерфейсы хранятся в таблице Interface и могут иметь следующие типы:
  • Function (функции);
  • Data (данные);
  • Common ("общие интерфейсы", к которым относятся stdin, stdout и им подобные);
  • Alias (интерфейсы, являющиеся синонимами других интерфейсов);
  • заголовочные файлы (таблица Header);
  • константы (таблица Constant);
  • типы данных (таблица Type);
  • команды (таблица Command; под командами здесь понимаются как встроенные команды shell, так и различные утилиты);
  • секции исполняемых файлов формата ELF (таблицы ElfSections и SectionTypes);
  • теги rpm-файлов (таблица RpmTag). Информация об объектах, описанных в LSB Рис.1.ER-диаграмма сущностей, описываемых стандартом LSB. Также в базе данных хранятся связи между указанными объектами - каждая константа привязана к заголовочному файлу, в котором она объявляется, интерфейс - к библиотеке, в которой он содержится, либо к классу, если это метод класса, и т.д. ER-диаграмма существующей базы данных приведена на Рис. 1. Все сущности группируются в так называемые модули согласно своему назначению (например, модуль LSB_Сpp содержит все, относящееся к стандартной библиотеке C++, LSB_Toolkit_Qt3 - все, относящееся к библиотеке Qt3, и т.д.). Информация о форматах файлов ELF и RPM относится к модулю LSB_Core, однако база данных этого факта никак не отражает - об этом "знают" только скрипты, генерирующие текст стандарта. Связи типа "многие ко многим" реализуются посредством использования отдельных таблиц. Самой сложной структурой обладает взаимосвязь таблиц ClassInfo и Interface. Для ее реализации используется несколько вспомогательных таблиц, содержащих информацию о виртуальных таблицах класса (таблица Vtable; каждый класс может иметь одну либо две виртуальные таблицы) и о наследовании (таблица BaseTypes реализует обычное наследование, при описании множественного наследования используется также таблица VMIBaseTypes). Для удобства интерфейсы и классы, входящие в одну библиотеку, разбиваются на группы согласно их назначению.
    Информация о таких группах, на которые разбиваются библиотеки, содержится в таблице LibGroup. Так, например, библиотека librt (функции реального времени) разделена на три группы:
  • Shared Memory Objects (функции для работы с разделяемой памятью);
  • Clock (функции для работы с часами);
  • Timers (функции для работы с таймерами). Аналогично группируются константы и типы данных, описанные в одном заголовочном файле. Информация о таких группах содержится в таблице HeaderGroup. Так, например, файл rpc/rpc_msg.h, который содержит декларации типов и функций для работы с сообщениями, передаваемыми при удаленном вызове процедур (RPC, Remote Procedure Call), делится на следующие группы:
  • accepted_reply (типы, которые описывают ответ на rpc-запрос, принятый сервером);
  • rejected_reply (типы, которые описывают ответ на rpc-запрос, отвергнутый сервером);
  • reply_body (типы, описывающие тело ответа на rpc-запрос);
  • call_body (типы, описывающие тело rpc-запроса);
  • rpc_msg (типы, описывающие весь rpc-запрос);
  • base types (основные типы);
  • default HeaderGroup (сюда относится все, не вошедшее в перечисленные выше группы). Заметим, что типы одной группы могут быть составными типами, определяемыми через типы других групп. Например, типы из группы rpc_msg - это структуры, содержащие тип из call_body либо reply_body и некоторые дополнительные атрибуты.

    Элементарные тесты

    Одной из главных задач стандартизации является проведение сертификационного тестирования на соответствие реализации стандарту. LSB, как и большинство промышленных стандартов, имеет большой объем, и написать тесты для проведения сертификации вручную не представляется возможным. Необходима автоматизация процесса создания сертификационного тестового набора. Наличие базы данных стандарта позволяет сгенерировать на ее основе набор элементарных тестов, определяющих, присутствуют ли в системе все объекты, описанные в стандарте LSB, и соответствуют ли их характеристики (значения констант, сигнатуры функций и т.п.) тем, которые указаны в стандарте. В частности, генерируются следующие наборы тестов:
  • cmdchk - проверка того, что в системе присутствуют все описанные в LSB команды, и соответствующие исполняемые файлы находятся в директориях, определенных стандартом (хотя для многих команд имена директорий не указаны);
  • devchk - проверка наличия в системе всех заголовочных файлов, описанных в LSB, и наличия в этих файлах описаний всех требуемых стандартом констант и типов (функции данный тест не проверяет). Для констант проверяется, верно ли определены их значений, для типов, определяемых в заголовочных файлах, вычисляется размер и сравнивается с тем, который должен быть согласно LSB;
  • libchk - проверка наличия в системе всех библиотек, описанных в LSB, а также проверка содержимого этих библиотек на соответствие стандарту (тестируется наличие в библиотеке всех необходимых интерфейсов с сигнатурами, определенными в стандарте). Перечисленные выше тесты нацелены на проверку соответствия дистрибутивов Linux стандарту LSB. Кроме этого, существуют скрипты для генерации тестовых наборов, нацеленных на проверку соответствия стандарту отдельного приложения. К таким тестовым наборам относятся:
  • appchk - статический анализ файла на предмет использования им библиотек и интерфейсов, не входящих в LSB;
  • dynchk - проверка корректности использования приложением интерфейсов, определенных в LSB (разработка этих тестов еще не завершена);
  • elfchk - проверка корректности заданного elf-файла (используется в тестах appchk);
  • rpmchk - проверка корректности заданного rpm-файла (используется в тестах appchk). Подробнее о перечисленных выше тестовых наборах и использовании базы данных стандарта при их генерации можно узнать в []. Несмотря на то, что перечисленные выше тесты не осуществляют никаких сложных проверок, возможность их автоматической генерации существенно упрощает процесс проверки соответствия дистрибутивов и приложений требованиям LSB.
    Например, если бы разработчикам тестов было доступно только текстовое описание заголовочных файлов со всем содержимым (константами, типами и функциями), то даже для элементарной проверки, выполняемой тестовым набором devchk, им пришлось бы вручную извлекать из текста всю необходимую информацию. А этот процесс может занять довольно продолжительное время (стандарт LSB описывает более 6000 различных констант и более 8000 типов для 411 заголовочных файлов). Кроме того, при таких объемах информации неизбежны ошибки, связанные с человеческим фактором. Более сложные тесты на основе базы данных стандарта создать нельзя, поскольку она содержит далеко не всю информацию, присутствующую в тексте LSB. В частности, нельзя создать тестовый набор для проверки соответствия стандарту поведения функций, а именно такие тесты наиболее важны для сертификационного тестирования. Однако описание поведения функций на данный момент имеется только в виде текста, написанного человеком, и для автоматического создания тестов необходимо проводить формализацию этого текста (пример подхода к формализации приведен в [], там же имеется краткий обзор других существующих подходов). О планах по добавлению в базу данных стандарта LSB более детальной информации об интерфейсах и о других направлениях развития инфраструктуры LSB можно узнать в следующем разделе.

    Планы по развитию инфраструктуры LSB

    В настоящее время в ИСП РАН в рамках совместного проекта с Free Standards Group проводится разработка тестовой инфраструктуры, предназначенной для усовершенствования процесса сертификации на соответствие стандарту LSB. Помимо собственно процесса тестирования, проект затрагивает и многие смежные области, в том числе базу данных стандарта и работающие с ней инструменты.

    Поддержка версий LSB

    В настоящее время база данных содержит информацию только для текущей версии стандарта. Чтобы посмотреть, что входило в какую-либо из предыдущих версий, необходимо взять из CVS-репозитория FSG соответствующую версию базы данных. Также можно сгенерировать и текст стандарта определенной версии, однако для этого необходимо, помимо нужной версии базы данных, взять соответствующие версии скриптов, генерирующих текст стандарта - ведь со временем изменения вносятся как в схему базы данных, так и в сами скрипты. Аналогично, при необходимости сгенерировать объекты, описанные в разд. 4, необходимо взять нужные версии соответствующих скриптов. В рамках совместного проекта ИСП РАН и FSG планируется добавить в базу данных поддержку хранения информации для различных версий стандарта LSB, а также доработать все скрипты, чтобы они могли генерировать файлы, соответствующие определенной версии стандарта. Планируется воссоздать историю изменений стандарта LSB, начиная с версии 3.0. Что касается более ранних версий, то вопрос о предоставлении исторической информации для них остается открытым - в первые годы своего существования стандарт и построенная вокруг него инфраструктура переживали революционные изменения (переход к поддержке нескольких архитектур, разбиение на модули), и хранить историю в полном объеме было вряд ли целесообразно, а ее воссоздание может потребовать значительных усилий.

    в современном мире программного обеспечения,

    Стандарты играют существенную роль в современном мире программного обеспечения, внося единообразие в сложные программные комплексы. Наличие стандартов в области операционных систем позволяет разработчикам создавать приложения, взаимодействующие с операционной системой, не изучая при этом детали реализации объекта взаимодействия. Для написания программы, соответствующей какому-либо стандарту, разработчику необходимо изучить текст этого стандарта. Большинство стандартов попадают к пользователям в виде электронных документов либо напечатанных книг, и объем их достаточно велик. Однако во многих случаях процесс разработки упростится, если некоторые сведения из стандарта будут доступны в какой-то другой форме, более формальной, чем текст, и не требующей изучения человеком. Например, для гарантии того, что в программе будут использоваться только описанные стандартом функции, специфицированные в заданных заголовочных файлах, полезно иметь такие файлы, содержащие описания только стандартных функций. Использование только этих заголовочных файлов при создании программы гарантировало бы отсутствие обращений к функциям, не входящим в стандарт. Но ручное создание таких заголовочных файлов на основе текста стандарта - трудоемкое занятие (например, стандарт LSB [] содержит описание 18955 функций из 465 заголовочных файлов), и во многих случаях (особенно при разработке небольших приложений) разработчики предпочитают полагаться на свое знание текста стандарта. Вполне возможно, что у создателей стандарта имеется необходимая информация в виде, удобном для разработчика, однако процесс создания большинства стандартов скрыт от постороннего взора. Тексты многих стандартов доступны только за плату, а доступа к данным, которые (возможно) используются при создании текста стандарта, нет. Нетрудно видеть, что такое положение вещей противоречит идеологии open source, согласно которой пользователь должен иметь возможность получить не только готовый продукт, но и все необходимое для его самостоятельной сборки и внесения в него изменений. Конечно, требование открытости относится, прежде всего, к исходным кодам программ, однако многие сторонники open source переносят эту идеологию и на смежные области. И одним из примеров здесь является стандарт LSB, описывающий базовые интерфейсы операционной системы на бинарном уровне. Во Free Standards Group [], под эгидой которой проводится разработка LSB, используется специфический подход, открывающий всем желающим всю "кухню" по производству стандарта. Каждый может бесплатно получить все необходимое не только для генерации текста стандарта, но и для автоматического создания сопутствующих наборов программ и исходного кода. О том, как устроена инфраструктура, связанная со стандартом LSB, и какие существуют планы по ее развитию, и рассказывается в данной статье.

    Free Standards Group смогла успешно

    Free Standards Group смогла успешно перенести идеологию open source на процесс разработки стандарта LSB, создав инфраструктуру, предоставляющую каждому желающему возможность самостоятельно генерировать текст стандарта и многие связанные с ним объекты. Наличие базы данных стандарта позволяет предоставлять пользователям более удобные способы изучения сущностей, описанных в LSB, чем простое чтение текста. Существующий подход уже доказал свою состоятельность - с его применением создавались все версии стандарта, начиная с 1.0. Естественно, за это время как схема базы данных стандарта LSB, так и использующие эту базу данных скрипты претерпели существенные изменения, однако основные идеи, заложенные в основу используемого подхода, остаются неизменными. Тем не менее, в своем текущем состоянии инфраструктура LSB удовлетворяет не все потребности, возникающие у пользователей, и есть много предложений по ее дальнейшему развитию. Прежде всего, необходимо предоставлять пользователю информацию о дистрибутивах и о наличии в них тех или иных сущностей, описанных в LSB. Также важно предоставить более простые способы получения данных о предыдущих версиях стандарта. Для создания сертификационных наборов, обеспечивающих хорошее покрытие, представляется важным иметь в базе данных более детальную информацию об интерфейсах. Все эти задачи планируется реализовать в рамках совместного проекта ИСП РАН и Free Standards Group по развитию инфраструктуры LSB.

    Тестирование софта - статьи

    Аннотация

    В статье описывается подход к построению инфраструктуры использования программных стандартов. Предлагаемый подход основан на формализации стандартов и автоматизации построения тестов для проверки соответствия им из полученных формальных спецификаций. В рамках этого подхода предлагается технологическая поддержка для решения ряда возникающих инженерных и организационных задач, что позволяет использовать его для сложных промышленных стандартов программного обеспечения. Этот тезис иллюстрируется использованием описанного подхода для формализации ядра Базового стандарта Linux (Linux Standard Base). Данная работа лежит в рамках подходов к решению задачи по обеспечению развития надежных крупномасштабных программных систем, провозглашенной международным академическим сообществом одним из Больших Вызовов в информатике [,].

    Буфер трансляции адресов

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

    Другие подходы к построению тестов на соответствие стандартам

    Наиболее глубокие результаты в области формализации стандартов и методик разработки тестов на основе формальных спецификаций получены применительно к телекоммуникационным протоколам. Общий подход к формальному тестированию, представленный в работах [,,] (см. также выше), тоже был разработан в этой области. Этот подход имеет много общего с представленным в данной статье. Различия связаны, в основном, с необходимостью иметь дело со стандартами большего объема, такими как стандарты POSIX или LSB на интерфейсы библиотек операционных систем. Большой размер стандартов и соответствующих спецификаций приводит к практической невозможности использовать как полные тестовые наборы в терминах работы [], поскольку они бесконечны, так и выбор набора целей тестирования "на глаз", который является одним из традиционных шагов разработки тестов для телекоммуникационных протоколов. Вместо этого используется более систематическое выделение отдельных требований, понятие критерия тестового покрытия, выбор критерия, ориентированного на учет всех выявленных требований, и генерация тестов, нацеленная на достижение высоких показателей покрытия по выбранному критерию. Использование контрактных спецификаций также способствует большей практичности и масштабируемости нашего подхода. Программные контракты, с одной стороны, позволяют провести декомпозицию большой системы на более обозримые компоненты, что труднее сделать, используя автоматы или системы переходов, лежащие в основе традиционного формального тестирования. С другой стороны, пред- и постусловия лучше подходят для описания недетерминированного поведения, которое довольно часто вынужден фиксировать стандарт при наличии нескольких возможностей реализовать одну и ту же абстрактную функциональность. Статья [] представляет другую попытку формализации стандарта на примере ШЕЕ 1003.5 - POSIX Ada Language Interfaces (интерфейсы POSIX для языка Ada). В рамках описываемого в ней метода на базе требований стандарта сразу строились формальные описания проверяющих их тестов, без промежуточных спецификаций самих требований.
    Такой метод кажется нам принципиально мало отличающимся от традиционной ручной разработки тестов, при которой разработчик теста читает стандарт, придумывает на основе прочитанного тесты и записывает их в виде некоторых программ. Существует ряд аналогичных работ по разработке тестовых наборов для проверки соответствия стандартам интерфейсов операционных систем. Наиболее известные стандарты в этой области это IEEE Std 1003.1, или POSIX [], и Базовый стандарт Linux, или LSB []. Имеются и наборы тестов на соответствие этим стандартам - это сертификационные тесты POSIX от Open Group [], открытый проект Open POSIX Test Suite [], и официальные сертификационные тесты на соответствие LSB [] от Free Standards Group []. Все эти проекты используют схожие технологии выделения требований из текста стандарта и создания соответствующего каталога требований. После этого тесты разрабатываются вручную на языке С с применением подхода "один тест на одно требование". Они не используют формализацию требований и автоматическую генерацию тестов. Стоит отметить, что использование подхода "один тест на одно требование" создает предпосылки для укрупнения требований при их выделении, так как велик соблазн проверить как можно больше в одном тесте. К примеру, в тестах Open POSIX мы обнаружили требования, которые включают в себя десяток утверждений, которые в проекте Центра верификации ОС Linux выделяются в отдельные требования. Такое укрупнение требований может приводить к тому, что некоторые утверждения стандарта упускаются из виду и не проверяются, в то время как крупное интегральное требование, в которое они входят, считается протестированным. В результате построенный на основе таких требований отчет об их покрытии может искажать реальное положение дел, утверждая, что все требования стандарта проверены. Кроме того, автоматическая генерация тестов из спецификаций, используемая в нашем подходе, делает тестовый набор более управляемым, позволяя описывать сами требования стандарта в одном месте, а технику, используемую для их проверки, и тестовые данные - в другом.Это значительно облегчает внесение изменений в тесты при развитии стандарта или их адаптации под требования специфической предметной области.

    Формализация стандартов

    Основные трудности, возникающие при формализации индустриальных стандартов, связаны с их неформальной природой. Главные цели такого стандарта - зафиксировать консенсус ведущих производителей относительно функциональности рассматриваемых систем и обеспечить разработчиков реализаций стандарта и приложений, работающих на основе этих реализаций, справочной информацией и руководством по использованию описанных функций. Форма и язык стандарта выбираются так, чтобы достигать этих целей. Стандарты на программные интерфейсы чаще всего состоят из двух частей: обоснования (rationale), представляющего целостную картину основных концепций и функций в рамках данного стандарта, и справочника (reference), описывающего каждый элемент интерфейса отдельно. В дополнение к элементам интерфейса (типам данных, функциям, константам и глобальным переменным) справочник может описывать и более крупные компоненты: подсистемы, заголовочные файлы и пр. Отдельные разделы справочника могут ссылаться друг на друга и содержать части друг друга. Формализация стандарта включает несколько видов деятельности.
  • Декомпозиция стандарта. Так как количество интерфейсных элементов, описанных в стандарте, может быть очень велико, то на первом шаге они разбиваются на логически связанные группы. Такая группа обычно состоит из операций и типов данных, связанных с определенным набором близких функций, и замкнута относительно обращения операций. Например, операции открытия и закрытия файлов, создания и удаления объектов нужно помещать в одну группу. Вся дальнейшая работа производится в рамках таких групп.
  • Выделение требований. Следующая задача заключается в выделении всех требований стандарта к описываемым им элементам интерфейса, которые могут быть проверены. Так как одна и та же вещь может быть описана в нескольких местах, то все соответствующие разделы стандарта необходимо внимательно, фраза за фразой, прочитать и пометить все найденные требования и ограничения. Затем выделенные требования объединяются в непротиворечивый набор.
    Эта работа достаточно утомительная и тяжелая, но ни в коем случае не механическая. Ее сложность связана с преобразованием неформальных требований и ограничений в формальные, а, по словам М. Р. Шуры-Буры, переход от неформального к формальному существенно неформален. Утверждения и ограничения из разных частей стандарта могут быть несовместимыми, двусмысленными, представлять похожие, но не одинаковые идеи. Только текста стандарта часто не достаточно, чтобы прийти к непротиворечивой полной картине. Поэтому при выделении требований необходимо пользоваться общими знаниями о предметной области, книгами и статьями по тематике, знанием о используемых в существующих системах решениях, а также общаться с авторами стандарта, экспертами в области и опытными разработчиками. Некоторые аспекты специально не определяются стандартом точно, для того чтобы реализации различных производителей соответствовали ему. Занятный пример можно найти в текущей версии стандарта POSIX []. Описание функций fstatvfs() и statvfs() из sys/statvfs.h говорит: "Не специфицировано, имеют ли все члены структуры statvfs смысл для всех файловых систем" ("It is unspecified whether all members of the statvfs structure have meaningful values on all file systems"). Существует два возможных пути разрешения подобных ситуаций.
  • Можно ничего не проверять. В этом случае никаких требований из соответствующей части стандарта не извлекается.
  • Если существует небольшое количество возможных реализаций, то требования к ним могут быть представлены в виде параметризированных ограничений. При этом вводится конфигурационный параметр, разные значения которого соответствуют различным возможным реализациям и определяют конечный вид проверяемого требования. Пример из описания функции basename() в стандарте POSIX: "Если строка, на которую указывает параметр path, равна "//", то реализация может возвращать как '/', так и "//"" ("If the string pointed to by path is exactly "//", it is implementation-defined whether '/' or "//" is returned"). Выделение требований проводится до тех пор, пока весь текст стандарта, касающийся выбранной группы элементов интерфейса, не будет разбит на требования, которые можно проверить, и остальные фразы, не содержащие проверяемых утверждений.


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

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


    Ряд требований, касающихся целостности данных некоторых типов, может быть отражен в самом описании их структуры в спецификациях. При этом проверка соответствующих ограничений происходит при преобразовании из данных реализации в модельные данные. Если эти ограничения нарушаются, такое преобразование становится невозможным и спецификация не может с ними работать, но сообщение об их проверке и обнаруженном несоответствии появится в трассе теста. Некоторые требования можно проконтролировать только в результате многократных обращений к тестируемой функции и проверки каких-то свойств всей совокупности полученных при этом результатов. Эти требования часто неудобно оформлять в виде спецификаций - для них создается отдельный тестовый сценарий (см. ниже),
  • который производит все нужные обращения и проверяет необходимые свойства полученных результатов. Примером такой ситуации может служить требование к функции rand() генерировать при многократных обращениях последовательность случайных чисел, равномерно распределенных на некотором интервале. Для проверки этого свойства можно накопить данные о результатах ее вызовов и применить к их совокупности, например, критерий Колмогорова. Эта деятельность имеет два результата.
  • Формальные спецификации всех элементов интерфейса. Код спецификаций размечается, чтобы указать связь между описанными формальными ограничениями и соответствующими им требованиями из каталога требований.
  • Конфигурационная система стандарта. Эта система состоит из набора конфигурационных параметров, как объявленных в стандарте, так и дополнительно введенных разработчиками спецификаций, зависимостей между ними, а также связей между ними, элементами интерфейса и проверяемыми ограничениями. Некоторые конфигурационные опции, представленные как значения параметров, управляют набором проверяемых требований. Другие могут говорить о том, что определенная функциональность вообще отсутствует в системе, и соответствующие операции не должны вызываться. Третьи могут влиять на возможные коды ошибок, возвращаемые операциями. Пример первого случая можно увидеть в описании функции pthread_create() из стандарта POSIX: "Если макрос _POSIX_THREAD_CPUTIME определен, то новый поток должен иметь доступ к часам процессорного времени, и начальное значение этих часов должно быть выставлено в ноль" ("If _POSIX_THREAD_CPUTIME is defined, the new thread shall have a CPU-time clock accessible, and the initial value of this clock shall be set to zero").
  • Определение критериев покрытия. Последний вид деятельности в рамках формализации стандарта заключается в определении критериев покрытия, которые могут быть использованы для измерения адекватности тестирования реализации на соответствие стандарту.Базовый критерий - это необходимость покрытия всех требований стандарта, применимых к текущей конфигурации тестируемой системы. Более строгие критерии могут определять дополнительные ситуации для тестирования. Все эти критерии базируются на спецификациях, представляющих требования стандарта. Критерии покрытия сложным образом связаны с конфигурационными параметрами. Возможность покрытия определенной ситуации может зависеть от текущих значений конфигурационных параметров, и эта зависимость должна быть зафиксирована, чтобы избежать многочисленных трудностей при анализе результатов тестирования. Результатом этой работы является набор критериев покрытия, тесно связанный с конфигурационной системой. Процесс формализации стандарта и последующей разработки тестов на основе ее результатов проиллюстрирован на .

    Формальная спецификация подсистемы управления памятью

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

    Генерация тестовых программ для подсистемы управления памятью микропроцессора

    ,
    Труды Института системного программирования РАН

    Использование формальных методов для обеспечения соблюдения программных стандартов

    , , , , , Препринт Института системного программирования РАН (ИСП РАН)

    Используемый метод генерации тестовых программ

    В предлагаемом подходе построение тестовых программ осуществляется автоматически на основе формальной спецификации подсистемы управления памятью. Цель генерации задается с помощью критерия тестового покрытия, выделяющего набор тестовых ситуаций для инструкций, работающих с памятью. Тестовые программы строятся путем целенаправленного перебора всевозможных сочетаний тестовых ситуаций для цепочек инструкций ограниченной длины. Общее описание используемого метода генерации тестовых программ доступно в работе . Идея метода основана на предположении, что поведение микропроцессора зависит от множества выполняемых инструкций (состояние конвейера), зависимостей между ними (через регистры или память) и событий, возникающих при выполнении инструкций (исключения, попадания/промахи в кэш и т.п.). Генератору на вход подаются формальные спецификации инструкций, в нашем случае инструкций, работающих с памятью (типа загрузки и сохранения). Кроме того генератору даются описания тестовых ситуаций и возможных зависимостей между инструкциями, а также параметры управления генерацией, например, длина генерируемых цепочек инструкций. Тестовая программа представляет собой последовательность тестовых вариантов. Каждый тестовый вариант содержит тестовое воздействие — специально подготовленную цепочку инструкций, предназначенную для создания определенной ситуации в работе микропроцессора. Тестовое воздействие предваряется инициализирующими инструкциями и может завершаться тестовым оракулом — инструкциями, проверяющими корректность состояния микропроцессора после выполнения тестового воздействия. Таким образом, структуру тестовой программы можно описать с помощью формулы test = {⟨initi, actioni, posti⟩}i=1,n, где initi — это инициализирующие инструкции, actioni — тестовое воздействие, posti — тестовый оракул. Ниже приведен фрагмент тестовой программы для микропроцессора с системой команд MIPS64 , который включает один тестовый вариант. /////////////// Инициализирующие инструкции /////////////// // Инициализация регистров инструкции 1: // загрузка виртуального адреса в базовый регистр a0 // a0[base]=0xffffffff81af1590 ori a0, zero, 0xffff dsll a0, a0, 16 ori a0, a0, 0xffff dsll a0, a0, 16 ori a0, a0, 0xa1af dsll a0, a0, 16 ori a0, a0, 0x1590 // Инициализация памяти для инструкции 1 ori a1, zero, 0xdead sw a1, 0(a0) // Инициализация регистров инструкции 2: // загрузка виртуального адреса в базовый регистр t1 // t1[base]=0xffffffff81c49598 ... // Инициализация памяти для инструкции 2 ... // Инициализация кэша L1 для инструкции 1: // помещение данных в кэш L1 lw t0, 0(a0) // Инициализация кэша L1 для инструкции 2: // вытеснение данных из кэша L1 ... ////////////////// Тестовое воздействие /////////////////// // Зависимость между инструкциями 1 и 2: L1IndexEqual=false lw v0, 0(a0) // L1Hit=true sw a2, 0(t1) // L1Hit=false ///////////////////// Тестовый оракул ///////////////////// // Тестовый оракул для инструкции 1 ori t1, zero, 0xdead // Ошибочное завершение при несоответствии результата bne v0, t1, error_found nop // Тестовый оракул для инструкции 2 ... Тестовое воздействие состоит из двух инструкций: lw, осуществляющей загрузку слова, и sw, которая сохраняет слово по указанному адресу. Для наглядности тестовые ситуации для этих инструкций затрагивают только кэш-память первого уровня (L1): первая инструкция вызывает попадание в кэш, вторая — промах. Несколько слов о вспомогательных инструкциях, используемых в примере. Инструкция ori осуществляет побитовое ИЛИ значения второго регистра с 16 битным значением, заданного в третьем операнде, результат записывается в первый регистр; инструкция dsll сдвигает значение второго регистра влево на заданное число разрядов и сохраняет результат в первом регистре; bne — это инструкция условного перехода (переход осуществляется в случае неравенства значений регистров).

    Кэш-память

    Кэш-память представляет собой промежуточный буфер с быстрым доступом, содержащий наиболее часто используемые данные, которые хранятся в менее быстродействующих устройствах. Кэш-память современных микропроцессоров имеет несколько уровней (обычно их два — L1 и L2). Кэш Li буферизует обращения к кэшу Li+1; кэш последнего уровня является самым крупным, и данные в него подгружаются непосредственно из оперативной памяти. Общая идея работы кэш-памяти следующая. Когда происходит обращение к основной памяти, контроллер кэша проверяет, есть ли требуемые данные в буфере. Если данные в нем есть (попадание в кэш), они берутся из кэша. В противном случае (промах в кэш), один из блоков данных, содержащихся в буфере, замещается запрашиваемыми данными из основной памяти. Какой именно блок будет замещен, определяется стратегией замещения. В общем случае кэш-память состоит из некоторого числа множеств, обозначим это число S = 2s; каждое множество состоит из E строк; а каждая строка состоит из блока данных размера B = 2b, тэга — старших разрядов физического адреса, используемых для вычисления признака попадания, — и бит управляющей информации. В зависимости от значений S и E различают три типа кэш-памяти:
  • кэш-память прямого отображения (E = 1);
  • полностью ассоциативная кэш-память (S = 1);
  • частично ассоциативная кэш-память (E > 1 и S > 1). Величина E называется уровнем ассоциативности кэш-памяти. Пусть физический адрес имеет разрядность m. В большинстве случаев при обращении к кэш-памяти с параметрами ⟨S = 2s, B = 2b, E⟩ физический адрес интерпретируется следующим образом: биты [0, …, b-1] определяют позицию байта внутри блока данных, [b, …, b+s–1] — номер множества и, наконец, [b+s, …, m] — тэг. Для определения, имеет место попадание или промах, по физическому адресу вычисляется номер множества, после чего для каждой строки этого множества осуществляется сравнение тэга адреса, по которому осуществляется обращение, с тэгом, хранящимся в строке. Если для одной из строк сравнение истинно, значит, имеет место кэш-попадание, и в блоке данных этой строки на соответствующей позиции находятся требуемые данные. Отметим, что кэш-память может поддерживать различные стратегии обновления содержимого буферов разных уровней (в случае многоуровневой организации), а также стратегии записи в оперативную память (сквозная или отложенная запись). Будем называть такие стратегии политиками кэширования.

    Подготовка тестового воздействия

    Рассмотрим подробнее, как генератор конструирует цепочки инициализирующих инструкций, осуществляющих подготовку тестовых воздействий. Для каждой тестовой ситуации описывается способ ее подготовки — последовательность инструкций, которая инициализирует нужные регистры и память. На первый взгляд, задача является тривиальной — генератору достаточно последовательно подготовить тестовые ситуации для инструкций, входящих в состав тестового воздействия. Для подсистемы управления памятью такой способ не работает. Для инструкций, работающих с памятью, подготовка тестовых ситуаций состоит из двух основных частей: инициализация буфера трансляции адресов и инициализация кэш-памяти. Инструкции подготовки буфера трансляции адресов могут изменить состояние кэш-памяти, поэтому при построении программы подготовки тестового воздействия целесообразно сначала осуществлять подготовку буфера трансляции адресов для всех инструкций тестового воздействия, а затем — подготовку кэш-памяти (подготовка кэш-памяти не затрагивает TLB, если использовать адреса из неотображаемых сегментов адресного пространства). В общем случае, разработчик тестов разбивает программы подготовки тестовых ситуаций для тестируемых инструкций на несколько фрагментов. Каждый фрагмент отвечает за инициализацию определенного модуля микропроцессора. Разработчик упорядочивает фрагменты таким образом, чтобы каждый следующий фрагмент не влиял на модули, подготовленные предыдущими фрагментами.

    Подсистема управления памятью

    Подсистемой управления памятью (MMU, Memory Management Unit) называется подсистема компьютера, отвечающая за обработку обращений к памяти. К ее функциям относятся: трансляция виртуальных адресов в физические, защита памяти, управление кэш-памятью и др. . Вообще говоря, отдельные компоненты подсистемы управления памятью могут располагаться вне интегральной схемы микропроцессора, однако в данной статье рассматриваются только те составляющие MMU, которые являются частью микропроцессора: буфер трансляции адресов (TLB, Translation Lookaside Buffer) и устройства кэш-памяти.

    Практическая апробация подхода

    Мы применили описанный подход для тестирования подсистемы управления памятью MIPS64-совместимого микропроцессора. В качестве тестовых воздействий использовались всевозможные пары, составленные из четырех инструкций: lb (загрузка байта), ld (загрузка двойного слова), sb (сохранение байта) и sd (сохранение двойного слова). Структура используемых тестовых ситуаций и зависимостей была близка к описанной в статье, но были дополнительно учтены управляющие биты в TLB и наличие микро-TLB — промежуточного буфера, содержащего записи, по которым были последние обращения. В результате тестирования было найдено несколько критических ошибок в подсистеме управления кэш-памятью, которые не были обнаружены с помощью тестовых программ, сгенерированных случайно.

    Применения описанного подхода

    Представленный выше подход был успешно использован для формализации и создания наборов тестов для частей стандартов на протоколы IPv6 и IPMP2 [,]. Более масштабным применением этого подхода стал проект Центра верификации ОС Linux [] по формализации Базового стандарта Linux (Linux Standard Base, LSB) [] и разработке тестового набора для проверки соответствия ему. Цель первой фазы проекта - создать формальные спецификации и соответствующий тестовый набор для 1532 функций основных библиотек Linux, перечисленных в разделах III и IV стандарта LSB 3.1 (они описывают базовые библиотеки и библиотеки утилит). Стандарт LSB задает требования ко многим из этих функций, ссылаясь на уже существующие стандарты. Непосредственно в LSB описывается лишь 15% интерфейсов, большинство - 60% функций - определяются в стандарте Single UNIX Specification [], который включает текущую версию POSIX, а остальные - в таких спецификациях как X/Open Curses [], System V Interface Definition [] и ISO/IEC 9899 (стандарт языка С) []. LSB не включает в себя все эти стандарты, а ссылается лишь на их части, касающиеся описания требований для отдельных функций. Первая фаза проекта завершится в конце 2006 года. К этому времени планируется сделать все ее результаты доступными в виде открытого кода. В составе этих результатов будет следующее.
  • Дополнения к стандарту LSB Core 3.1 в виде формальных спецификаций его требований на расширении языка С (SeC - Specification Extension of С). Такие спецификации выражают требования стандарта в формальном виде, позволяя прояснить нечеткие места в его тексте и сделать явными все подразумеваемые в нем ограничения. Они послужат основой для разработки тестового набора для проверки соответствия стандарту LSB.
  • Список замечаний к текстам LSB и связанных стандартов, наработанных в ходе формализации. Это указания на нечеткие, двусмысленные, противоречивые или ошибочные места в стандартах. Они будут направляться разработчикам стандартов для внесения изменений в их будущие версии.
  • Тестовый набор для проверки соответствия поведения системных интерфейсов конкретной реализации Linux требованиям стандарта LSB Core 3.1.
    Он будет представлять собой набор программ, которые в результате своего выполнения строят HTML-отчеты о проверенных требованиях и найденных несоответствиях.
    Тестовый набор будет иметь систему конфигурации, которая позволитнастраивать тесты на особенности конкретной тестируемой системы, допускаемые стандартом, а также установить параметры, определяющие состав тестируемых модулей и глубину тестирования. В начале проекта 1532 функции LSB были разбиты на 147 групп функционально связанных операций, которые в свою очередь группируются в большие подсистемы в соответствии с общей функциональностью - потоки, межпроцессное взаимодействие, файловая система, управление динамической памятью, математические функции, и т.д. За первые 3 месяца работы были проспецифицированы и снабжены базовыми тестовыми сценариями 320 функций из 41 группы. В процессе формализации было выделено около 2150 отдельных требований стандарта к этим функций. При этом к некоторым функциям предъявляется всего лишь несколько требований, в то время как к другим - несколько десятков. На основе достигнутой производительности можно утверждать, что первая фаза проекта потребует около 15 человеко-лет. Чтобы начать работать в этом проекте, опытному разработчику программного обеспечения (не обычному тестировщику!) требуется около месяца для обучения особенностям технологии и изучения сопутствующих процессов. Полученные предварительные результаты позволяют надеяться, что рассмотренный подход может успешно применяться для проектов такого масштаба.

    Разработка тестов на соответствие стандарту

    В основе процесса разработки тестов на соответствие стандарту лежит технология UniTesK, разработанная в ИСП РАН на базе многолетнего опыта проектов по тестированию сложного промышленного ПО. Эта технология использует подход к формальному тестированию, разработанный в работах Верно (Bernot) [], Бринксмы (Brinksma) и Тритманса (Tretmans) [,] и описанный в телекоммуникационных стандартах ISO 9646 [] и ITU-T Z.500 []. Основные положения этого подхода можно сформулировать следующим образом ( иллюстрирует связи между упоминаемыми ниже понятиями).
  • Требования стандарта представлены в виде модели, описанной с помощью некоторого формализма. Эта модель называется спецификацией.
  • Предполагается, что программная система, чьё соответствие стандарту мы пытаемся установить - тестируемая система, - может быть адекватно смоделирована с помощью этого же формализма. Соответствующую нашей системе модель мы называем реализацией. Мы не знаем её деталей, но предполагаем, что она существует. Адекватность моделирования в данном случае означает, что мы не можем наблюдать никаких различий между реальным поведением тестируемой системы и модельным поведением реализации.
  • Тот факт, что тестовая система соответствует стандарту, моделируется посредством отношения соответствия (conformance relation, implementation relation) между реализацией и спецификацией.
  • Модельные тесты строятся на основе спецификации или извлекаются из нее. Модельный тест - это модель в том же самом формализме, способная взаимодействовать с другими моделями и выдающая булевский вердикт как результат этого взаимодействия. Реализация проходит модельный тест, если он выдаёт вердикт "истина" в результате взаимодействия с ней. Имеет смысл строить только значимые тесты, т.е. те, что проходятся любой реализацией, соответствующей спецификации. Тест, не являющийся значимым, может отвергнуть совершенно корректную реализацию. Результатом извлечения тестов является набор модельных тестов или модельный тестовый набор. Желательно, чтобы он был полным тестовым набором - таким, что реализация проходит все тесты из него тогда и только тогда, когда она соответствует спецификации.
  • Модельные тесты транслируются в тестовые программы, взаимодействующие с тестируемой системой.
    Примерами таких критериев являются покрытие ветвей в коде постусловия и покрытие дизъюнктов из дизъюнктивной нормальной формы условий этих же ветвлений. Есть возможность вводить произвольные критерии, описывая дополнительные ситуации, в которых работу системы необходимо проверить.
  • Тестовый сценарий создается для достижения определённого покрытия заданной группы операций. Такой сценарий определяет конечный автомат, моделирующий поведение тестируемого компонента таким образом, что выполнение всех его переходов гарантирует покрытие 100% ситуаций в соответствии с выбранным критерием. Автомат задается при помощи функции вычисления состояния и набора допустимых действий в произвольном состоянии. Этот набор действий зависит от данных состояния. Каждое действие соответствует вызову одной из операций с некоторыми аргументами.
  • Сами тесты или последовательности тестовых воздействий генерируются при выполнении тестового сценария, во время которого автоматически строится некоторый путь, покрывающий все переходы описываемого им автомата. Построение тестового набора для проверки соответствия практически используемым стандартам ПО приводит к необходимости разработки конфигурационной системы тестов. Эта система включает конфигурационную систему стандарта (см. выше) и дополнительные параметры, управляющие работой тестов. Разные значения этих параметров соответствуют различным критериям покрытия, разным наборам тестовых сценариев и т.п. Хорошая конфигурационная система делает тестовый набор применимым в любых условиях, где может функционировать реализация рассматриваемого стандарта.

    Сравнение с существующими подходами

    Нам известен только один подход к автоматизированному построению тестовых программ для MMU, использующий формальные спецификации подсистемы, — DeepTrans, разработка исследовательской лаборатории IBM в г. Хайфе (IBM Haifa Research Lab) . Эта методика нацелена на тестирование части подсистемы, отвечающей за преобразование виртуальных адресов. В DeepTrans используется язык спецификации, специально созданный для моделирования механизмов трансляции адресов. В спецификации процесс преобразования адреса представляется в виде ориентированного ациклического графа. Вершины графа соответствуют отдельным стадиям трансляции адреса, ребра — переходам между стадиями. Из каждой вершины может выходить несколько дуг, каждая из которых помечена своим условием перехода. Подход DeepTrans предполагает ручную разработку шаблонов тестовых программ, но в шаблонах можно указывать тестовые ситуации, связанные с трансляцией адресов, извлеченные из спецификации. На основе шаблонов генератор Genesys-Pro путем разрешения ограничений строит набор тестовых программ [6, 7, 8]. Сравнение DeepTrans (в паре с генератором Genesys-Pro) с используемым нами подходом (MicroTESK) приводится в таблице 1. Таблица 1. Сравнение DeepTrans с MicroTESK Сравниваемая характеристика DeepTrans (Genesys-Pro) MicroTESK
    Поддержка пользовательских шаблонов тестовых программ Genesys-Pro предоставляет развитый язык описания шаблонов тестовых программ MicroTESK позволяет описывать лишь сравнительно простые шаблоны тестовых программ
    Поддержка автоматической генерации тестовых шаблонов Автоматическая генерация тестовых шаблонов не поддерживается Поддерживается автоматическая генерация несложных тестовых шаблонов
    Поддержка описания тестовых ситуаций Поддерживается Поддерживается
    Поддержка описания зависимостей по адресам Специальной поддержки нет, зависимости вручную описываются в тестовом шаблоне Поддерживается описание сложных зависимостей по адресам
    Поддержка моделирования подсистемы управления памятью DeepTrans предоставляет специальный декларативный язык описания механизмов трансляции В MicroTESK реализована библиотека классов на языке Java, моделирующая устройства MMU
    Поддержка генерации самопроверяющих тестов Поддерживается Поддерживается
    Как видно из сравнения, в DeepTrans упор сделан на ручную разработку шаблонов тестовых программ, но при этом разработчику тестов предоставляются развитые средства описания шаблонов. В нашем подходе тестовые шаблоны генерируются автоматически, систематическим образом. Кроме того, в нашем подходе поддерживается описание сложных зависимостей по адресам.

    Тестирование подсистемы управления памятью

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

    Типы тестовых ситуаций для подсистемы управления памятью

    Под тестовой ситуацией для инструкции понимается ограничение на значения входных операндов и состояние микропроцессора перед началом выполнения инструкции. Множество возможных тестовых ситуаций выявляется на основе анализа описания архитектуры микропроцессора. При тестировании MMU структура тестовых ситуаций определяется функциональностью подсистемы, связанной с обработкой запросов к памяти. Например, на первом этапе обработки определяется сегмент адресного пространства, к которому относится виртуальный адрес: если сегмент является отображаемым, трансляция адреса осуществляется с помощью TLB; в противном случае, вычисление физического адреса производится в обход буфера трансляции адресов. Для данного этапа определены две тестовые ситуации: Mapped = true и Mapped = false. Далее, если виртуальный адрес является отображаемым, возможны две альтернативные ситуации: TLBHit = true, когда страница виртуальной памяти находится в TLB (попадание), и TLBHit = false, когда страницы в буфере нет (промах). Выделение ситуаций можно продолжить до тех пор, пока данные из памяти не будут загружены в регистр или, наоборот, сохранены из регистра в память (в зависимости от операции). Для подсистемы управления памятью можно выделить следующие типы элементарных ситуаций (ситуаций для отдельных этапов обработки), на основе композиций которых строятся результирующие тестовые ситуации:
  • Mapped — отображаемый/неотображаемый сегмент виртуальной памяти;
  • Cached — кэшируемый/некэшируемый сегмент виртуальной памяти;
  • TLBHit — попадание/промах в TLB;
  • Valid — бит достоверности секции TLB;
  • L1Hit — попадание/промах в кэш-память L1;
  • L2Hit — попадание/промах в кэш-память L2. На рисунке 1 элементарные ситуации структурированы в виде дерева (пунктирная дуга соответствует значению false, сплошная — значению true). Результирующая тестовая ситуация для инструкции соответствует пути от корня до листовой вершины. Типы тестовых ситуаций для подсистемы управления памятью
    Рисунок 1. Иерархии тестовых ситуаций Отметим, что выделение тестовых ситуаций можно проводить на разных уровнях абстракции, например, тестовую ситуацию, связанную с промахом или попаданием в буфер трансляции адресов в MIPS64-совместимом микропроцессоре, можно детализировать на основе выражения, вычисляющего признак попадания в строку TLB : (TLB[i]R = va63..62) and
    ((TLB[i]VPN2 and not (TLB[i]Mask)) = (vaSEGBITS-1..13 and not (TLB[i]Mask))) and
    (TLB[i]G or (TLB[i]ASID = EntryHiASID)) Уровень детализации может быть разным. При тщательном тестировании рассматриваются все комбинации значений истинности элементарных условий (exclusive condition coverage) или только те комбинации, когда значение истинности одного условия влияет на значение истинности всего выражения (MC/DC, Modified Condition/Decision Coverage) .

    Типы зависимостей для подсистемы управления памятью

    Зависимости между инструкциями бывают двух основных типов: зависимости по регистрам и зависимости по адресам. Зависимости по регистрам выражаются с помощью равенств номеров регистров, использующихся в качестве операндов инструкций тестового воздействия. Зависимости по адресам связаны с устройством подсистемы управления памятью. Примерами зависимостей по адресам являются совпадение виртуальных адресов, совпадение номеров страниц виртуальной памяти, совпадение физических адресов, совпадение используемых множеств кэш-памяти и др. Многоуровневая организация памяти приводит к сложной, многоуровневой структуре зависимостей. Зависимости по адресам можно условно разбить на зависимости по виртуальным адресам и зависимости по физическим. Зависимости по виртуальным адресам связаны со структурой буфера трансляции адресов. Зависимости по физическим адресам определяются организацией кэш-памяти. Мы используем следующие типы зависимостей для буферов, входящих в состав MMU:
  • IndexEqual — совпадение используемых множеств;
  • EntryEqual — совпадение строк внутри множества;
  • BlockEqual — совпадение данных внутри строки;
  • EntryReplace — обращение к данным, строка с которыми была вытеснена из буфера предшествующей инструкцией;
  • BlockEqual — совпадение данных внутри строки. В целом для подсистемы управления памятью можно выделить следующие типы элементарных зависимостей:
  • VAEqual — совпадение/несовпадение виртуальных адресов;
  • VPNEqual — совпадение/несовпадение номеров страниц виртуальной памяти;
  • PAEqual — совпадение/несовпадение физических адресов;
  • PFNEqual — совпадение/несовпадение номеров страниц физической памяти;
  • L1IndexEqual — совпадение/несовпадение множеств кэш-памяти L1;
  • L2IndexEqual — совпадение/несовпадение множеств кэш-памяти L2;
  • L1Replace — совпадение/несовпадение тэга кэш-памяти L1 с тэгом данных, которые были вытеснены из кэш-памяти предшествующей инструкцией;
  • L2Replace — совпадение/несовпадение тэга кэш-памяти L2 с тэгом данных, которые были вытеснен из кэш-памяти предшествующей инструкцией. В силу иерархической организации памяти зависимости по адресам имеют многоуровневую структуру. Фрагмент дерева возможных зависимостей представлен на рисунке 2 (пунктирная дуга соответствует значению false, сплошная — значению true). Результирующая зависимость между инструкциями соответствует пути от корня до листовой вершины. Типы зависимостей для подсистемы управления памятью
    Рисунок 2. Фрагмент иерархии зависимостей по адресам

    Виртуальная память

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

    Память современных компьютеров представляет собой

    Память современных компьютеров представляет собой сложную иерархию запоминающих устройств различных объемов, стоимости и времени доступа. Помимо регистров и оперативной памяти в микропроцессорах имеется, по крайней мере, одноуровневая, а чаще двухуровневая кэш-память; для ускорения преобразования виртуальных адресов в физические используются буферы трансляции адресов. Логически связанный набор модулей микропроцессора, отвечающих за организацию работы с памятью, называется подсистемой управления памятью. Подсистема управления памятью является ключевым компонентом микропроцессора, и, естественно, к корректности ее функционирования предъявляются очень высокие требования. Поскольку подсистема имеет сложную, многоуровневую организацию, число различных ситуаций, возможных при ее работе, огромно, что не позволяет проверить ее вручную. Эффективное тестирование подсистемы управления памятью возможно только при использовании методов автоматизации разработки тестов. В существующей практике тестирования часто полагаются на случай — генерация тестов (как правило, в форме программ на языке ассемблера тестируемого микропроцессора) осуществляется автоматически, но случайным образом. Инженер, отвечающий за тестирование, может задавать распределение вероятностей появления тех или иных инструкций в тестовых программах и указывать события, возникающие при их выполнении. Такой подход позволяет обнаруживать многие ошибки, но не является систематическим и, соответственно, не гарантирует полноты тестирования. В данной работе рассматривается подход к автоматизации генерации тестовых программ для подсистемы управления памятью. Подход является дополнением к “случайным тестам” и позволяет обнаруживать сложные, нетривиальные ошибки в моделях микропроцессоров. В основе предлагаемой методики лежат формальные спецификации устройств, входящих в подсистему управления памятью, спецификации инструкций, работающих с памятью, и описание тестового покрытия на уровне отдельных инструкций. Для генерации тестовых программ используются комбинаторные техники — тестовые воздействия на микропроцессор строятся как цепочки инструкций небольшой длины, составленные из различных сочетаний тестовых ситуаций. Оставшаяся часть статьи организована следующим образом. Во втором, следующем за введением, разделе кратко описывается устройство подсистемы управления памятью современных микропроцессоров. Третий раздел рассматривает используемый подход к генерации тестовых программ. В нем также обсуждается специфика тестирования подсистемы управления памятью. Четвертый раздел представляет собой сравнение предлагаемой методики с существующими подходами. В пятом разделе рассказывается об опыте практического использования методики. Наконец, шестой раздел содержит заключение.
    Экономическое и социальное развитие общества требует для своей поддержки все более сложное программное обеспечение (ПО). Масштабность современных программных систем приводит к тому, что такие системы строятся из многих компонентов, разрабатываемых в разных организациях огромным количеством людей. Для построения работоспособных систем на основе такого подхода необходимо уметь решать задачи обеспечения совместимости между различными их компонентами и их надежности в целом. Лучшее решение, которое было найдено на этом пути - выработка и соблюдение стандартов на программные интерфейсы между отдельными компонентами. Идея, лежащая в основе использования интерфейсных стандартов, проста - настаивая на их соблюдении, мы обеспечиваем компонентам, созданным разными разработчиками, возможность взаимодействовать через стандартизованные интерфейсы. Таким образом, их совместимость обеспечивается без введения чересчур жестких ограничений на возможные реализации, что ограничило бы как творчество отдельных разработчиков, так и инновационный потенциал компаний-поставщиков ПО. Этот подход хорошо работает, если стандарт определяет функциональность, реализуемую фиксируемым им интерфейсом, достаточно точно и недвусмысленно. Однако, тексты многих современных стандартов, описывающих требования к программным интерфейсам, далеко не так точны. Это объясняется историей их появления. Обычно такие стандарты разрабатываются под давлением требований рынка и должны принимать во внимание противоречивые интересы многих поставщиков программного обеспечения, интерфейс которого предполагается стандартизовать. В таких условиях только базовая функциональность может быть определена непротиворечивым образом, а для сложных и специфических функций каждая группа разработчиков предлагает свое собственное решение. Поскольку многие поставщики ПО уже вложили деньги в имеющиеся у них решения, им очень тяжело отказаться от этих инвестиций в пользу варианта одного из конкурентов, который при этом остается в выигрыше. Чаще всего группа по выработке стандарта приходит к компромиссу, при котором основные поставщики должны внести примерно одинаковые по объему изменения в ПО каждого из них, а в тех случаях, где необходима серьезная переработка, стандарт осознанно делается двусмысленным, чтобы любое из уже имеющихся решений могло декларировать соответствие ему. Это позволяет поставщикам ПО потратить приемлемые для них деньги на обеспечение соответствия своих систем стандарту, но в то же время подрывает столь желаемую совместимость различных его реализаций.

    Тестирование подсистемы управления памятью микропроцессора

    Тестирование подсистемы управления памятью микропроцессора является нетривиальной задачей, которую практически невозможно решить без применения методов автоматизации. В статье был рассмотрен подход к автоматической генерации тестовых программ для MMU. В отличие от распространенных на практике методов, предлагаемый подход имеет высокий уровень автоматизации и является систематичным. Сгенерированные тестовые программы могут содержать встроенные проверки (тестовые оракулы), что делает их пригодными для тестирования не только моделей микропроцессоров, написанных на языках описания аппаратуры, но и готовых микросхем.
    Внедрение и обеспечение соблюдения стандартов на программные интерфейсы связаны с большим количеством сложных проблем, как технического характера, так и экономических и социальных. Тем не менее, все эксперты сходятся на том, что эта деятельность необходима для стабильного развития производства программного обеспечения. Формализация стандартов уже неоднократно предлагалась в качестве возможного решения множества технических задач, связанных с ней. Однако формализация требует огромных усилий, что позволяет усомниться в применимости подходов на ее основе к реально используемым программным стандартам, достаточно объемным и сложным. Устранить эти сомнения помогут только практические примеры применения подобных методов к таким стандартам. Упомянутый выше проект [] по формализации LSB похоже, является одной из первых попыток формализации значительной части используемого на практике стандарта высокой сложности и позволяет почувствовать все ее выгоды и недостатки. Мы считаем, что представленный в этой статье подход позволит успешно справляться с подобными задачами. Аргументами в пользу этого мнения являются стабильное продвижение описанного проекта, история применений технологических составляющих данного подхода на практике ([,]) и лежащие в его основе базовые принципы хорошего проектирования больших систем [,]. Тот факт, что многие инженерные и организационные вопросы также находят отражение в данном подходе, позволяет надеяться на получение действительно полезных результатов, избежав участи многих проектов по использованию передовых методик разработки ПО на практике. Описанный проект является частью проводимых международным сообществом исследований подходов к решению проблемы обеспечения развития надежных крупномасштабных программных систем, одного из Больших Вызовов в информатике [,]. Тони Xoap (Tony Hoar) предложил вести работы по этой проблеме в двух направлениях: разрабатывать методы и инструменты, в перспективе способные помочь в ее решении, и накапливать примеры использования подобных методов на программных системах реалистичных размеров и сложности ("challenge codes").

    Тестирование софта - статьи

    Инженерный калькулятор

    Весь набор функций этого приложения можно разделить на две логические группы: логика инженерных операций и взаимодействие с операционной системой (выделение и освобождение ресурсов, использование системных компонент, взаимодействие с буфером обмена системы и т.п.).
    Из требований к приложению выделим поддержку 5-ти операционных систем с 4 основными языками локализации и выполнение, скажем, 50 инженерных функций, каждая из которых однозначно покрывается одним тестом. Тесты, безусловно, включают проверку на контрольных примерах, проверку на граничные значения и т.п. Кроме того, приложение позволяет выполнять, к примеру, 5 функций по взаимодействию с системой (запуск приложения, выход из приложения, сохранение результатов в файл, работа с буфером и т.п.).
    Полное покрытие требований задаёт набор из 5*4*(50+5)=1100 тестовых прогонов. Так как нужно выполнить все доступные операции приложения под всеми поддерживаемыми операционными системами и языковыми локализациями.
    Пример, как вы понимаете, подобран не случайно. Попробуем наглядно показать стратегию тестирования в действии.
    Вернёмся к разбивке функциональности приложения по логическим группам. Есть набор инженерной функциональности и есть системное взаимодействие.
    Очевидно, что работа и правильность результатов вычислений, реализованная внутри приложения и не использующая внешние компоненты не зависит от локализации системы. Более того, если приложение успешно запущено под поддерживаемой операционной системой и основная функциональность по работе с инженерными функциями работает, то имея успешный прогон всех инженерных тестов с анализом контрольных результатов, можно говорить о работоспособности инженерных функций под всеми поддерживаемыми операционными системами и локализациями.
    Таким образом, гарантировать работоспособность инженерных операций калькулятора можно прогоном 50 тестов под одним окружением.
    Взаимодействие с системой, запуск, остановку приложения, работу с буфером, запись результатов в файл, в нашем примере обеспечивает 5 тестов. Имеем 5 версий операционной системы и 4 локализации, что даёт 20 комбинаций и 20*5=100 тестовых прогонов.
    Таким образом, для данного примера, полный охват функциональности определяется 50+100=150 тестовых прогонов, из которых 50 тестов инженерных функций выполняются под одной конфигурацией и 5 тестов системных функций под 20 тестовыми окружениями.

    Объём задач

    Попробуем описать задачу на языке тестовых сценариев, то есть будем применять для оценки трудоёмкости задачи или её части количество тестов, необходимых для проверки работоспособности функционала.
    Клиент: 50 тестов на работу с данными (ввод форм, расчёт данных на основе данных хранимых в словарях, поиск данных, редактирование словарей и т.п.), 10 тестов на работу с печатными формами (формирование периодов выборок, выбор типов отчётов, печать или экспорт в предопределенный список форматов и т.д.). Пусть для тестирования работы с системой требуется, как и в предыдущем случае, ещё 5 тестов. По аналогии с предыдущим примером можем получить следующую картинку: имея 2 окружения получаем (5+10)*2=30 тестовых прогонов для проверки функциональности связанной с самой операционной системой (включая функционал печати и экспорта во время которого создаются новые файлы в рамках файловой системы). Будем считать, что 50 тестовых прогонов реализующих проверку логики работы с данными, можно выполнять под одним окружением. Итог — 80 тестовых прогонов для тестирования клиента системы.
    Объединим в рамках рассматриваемого примера тестирование функциональности сервера приложений и базы данных. Пусть сервер приложения реализует 20 команд по обработке данных и пользовательских сессий (без учёта работы с системными пулами соединений, функций сжатия передаваемого по сети трафика и т.п.). Сервер баз данных реализует 10 системных операций по архивации данных, построению статистики использования отчётов и ещё несколько подобных операций. Общий смысл заключается в том, что мы имеем конечный набор тестируемых операций, и так как конфигурации определены заранее, можем говорить о конечном наборе тестов, которые необходимо выполнить, что бы проверить работоспособность серверного функционала системы. Итог — 30 тестов на серверной стороне. Заметим, что в данном примере мы не затрагиваем нагрузочную составляющую тестирования: речь идёт только о функциональном тестировании.

    Определение

    Стратегия тестирования — это план проведения работ по тестированию системы или её модуля, учитывающий специфику функциональности и зависимости с другими компонентами системы и платформы. Стратегия определяет типы тестов, которые нужно выполнять для данного функционала системы, включает описание необходимых подходов с точки зрения целей тестирования и может задавать описания или требования к необходимым для проведения тестирования инструментам и инфраструктуре.
    Получается немного страшновато? Попробуем разбить на более детальные части используя, к примеру, разбивку по вопросам, на которые отвечает стратегия тестирования.
    Стратегия отвечает на вопросы:
  • Как, каким образом тестирование даст ответ, что данный функционал работает?
  • Что нужно сделать и чем пользоваться из инструментальных средств, для достижения целей тестирования?
  • Когда определённый функционал будет тестироваться и соответственно когда ожидать получения результатов?
  • В качестве дополнительной задачи, которая решается в процессе понимания стратегии тестирования, можно рассматривать задачу минимизации затрат на тестирование. Ниже на примере мы разберём более конкретный случай, сейчас же просто ограничимся указанием этого факта.
    Как видим, Стратегия тестирования, как артефакт, органично вписывается в План Тестирования. Шаблоны планов тестирования, предлагаемые различными методологиями, зачастую прямо включают одним из разделов описание стратегии тестировании или же включают описание стратегии в пункты плана отвечающие за тестирование конкретных частей функционала. К примеру, шаблон Rational Unified Process определяет стратегию тестирования как самый большой раздел плана тестирования.
    Что идеологически более правильно: выделять стратегию для всего проекта, или разрабатывать конкретные подходы для каждой области тестирования, зависит от конкретного проекта. В современных распределённых системах, с несколькими типами рабочих мест и системными агентами, которые также выступают пользователями системы, логичнее выделять стратегию для каждого набора тестируемого функционала: модуля или подсистемы. Для более простых приложений можно предлагать тестирование всего приложение в рамках одной Стратегии.
    Заказчик, зачастую, хочет контролировать процесс тестирования и видеть понимание задачи тестирования исполнителями по проекту. Для него стратегия тестирования это менее детальный документ-видение (vision) того, как будет тестироваться система в процессе разработки.

    Практика

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

    Распределённая система

    Подход в разработке стратегии тестирования для распределённой системы во многом совпадает с разработкой стратегии тестирования для обычного калькулятора. К примеру, аналогично с предыдущим примером, нам нужно выделить основные области, которые могут тестироваться отдельно друг от друга. Для тестирования сложных систем, также полезно выделять не только, так сказать, оперативные шаги по тестированию (то есть что, как и где будет тестироваться), но и проводить анализ тактических шагов по тестированию с учётом развития системы во времени.
    Вернёмся к стратегии тестирования сложной распределённой системы. Проведём ориентировочную разбивку функциональности на тестовые области, с тем, чтобы понять как спланировать тестирование и минимизировать затраты.
    Пусть система имеет "толстого" клиента, сервер приложения и сервер базы данных, которые могут функционировать на разных физических платформах. Основная логика ввода/вывода, валидации значений и построения печатных форм сосредоточена на клиенте, сервер приложений обеспечивает презентационный уровень и необходимую сервисную логику (автоматическая архивация данных, оповещение пользователей о внештатных ситуациях и т.д.), а база данных обеспечивает кроме непосредственного хранения данных определённую часть обработки данных реализуемую, к примеру, в виде пакетов функций. Ничего особенно сложного выбрано не было и пусть описание не пугает определённой утрированностью — мы всего лишь обрисовали задачу для тестирования.
    Для того, чтобы чрезмерно не усложнять задачу, сузим область тестовых окружений за счёт фиксированных конфигураций для сервера БД (зачастую в промышленной эксплуатации используется выделенный сервер или кластерное решение для работы баз данных) и сервера приложений. Клиентское приложение обеспечивает работу под 2-мя операционными системами и жестко фиксированной конфигурацией компонентов ОС (к примеру, на все пользовательские машины регулярно устанавливаются все обновления и service packs + набор ПО на клиентских машинах строго регламентирован внутренней IT политикой).

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

    В данном случае, мы разобрались с тем, что именно нужно тестировать, определили требования к тестам, провели простейший анализ условий тестирования и зависимостей. По сути своей, определили стратегию: "как" и "что" тестировать. Разрабатывать тесты можно в параллель к разработке стратегии, в любом случае дизайн и разработку тестов стратегия не заменяет.
    Полное покрытие требований определяет 1100 тестовых прогонов. Разбор функциональности и определение необходимого набора тестирования даёт на выходе из разработки стратегии тестирования 150 тестовых прогонов.
    Для данного конкретного примера, разработка стратегии тестирования даёт прямой выигрыш в затратах на тестирование в 1100/150=7,(3) раза. Как видим, имеет смысл. Попробуем рассмотреть шаги разработки стратегии тестирования для более сложного приложения — распределённой клиент-сервер системы.

    Терминология

    СТРАТЕГИЯ - искусство руководства; общий план ведения этой работы, исходя из сложившейся действительности на данном этапе развития.
    Есть другой вариант, с военным уклоном:
    Наука о ведении войны, искусство ведения войны. Общий план ведения войны, боевых операций.
    Я привожу оба определения, хотя первое достаточно точно описывает с чём, собственно, мы имеем дело. И хотя наши будни можно рассматривать как ведение войны за качество продукта, как мы увидим ниже ничего в стратегии тестирования как раз и нет.

    Вступление

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

    в общем случае даёт разработка

    Что в общем случае даёт разработка стратегии тестирования? Разбор задачи тестирования на составляющие, выделение тестовых областей и в конечном итоге более полное понимание задачи тестирования в конкретном проекте. Как мы видели на примере тестирования инженерного калькулятора, понимание задачи позволяет разделять функциональность тестируемого приложения или системы на области, которые могут тестироваться автономно, что позволяет снизить (и порой достаточно существенно!) затраты на тестирование.
    Вернёмся к определению понятия Стратегия: "общий план ведения работы, исходя из сложившейся действительности на данном этапе развития" — применимо к тестированию, стратегия есть понимание "что", "где" (на каком окружении) и "когда" будет тестироваться. Ответ на вопрос "как" нам может дать анализ требований к системе и дизайн тестов. Согласитесь, имея перед собой план развития системы и функционала, можно достаточно уверенно планировать задачи по тестированию, готовить необходимые данные и тестовые окружения. В любой момент времени, руководитель, полагаюсь на стратегию, знает где он находится и куда двигается дальше. Планируя тестирование, руководитель отдела или ведущий тестировщик не начинает разбираться с системой, а занимается непосредственно планированием, выделением ресурсов и сроков на конкретные задачи.

    Зависимости от артефактов проекта

    Более интересным с точки зрение разработки стратегии тестирования в данном примере, будет фактор развития системы в целом: создание новых модулей в клиентском приложении или ввод в систему новых серверных агентов.
    Что следует учитывать разрабатывая стратегию тестирования для сложных распределённых или клиент-серверных систем. Одним из основных факторов, влияющих на стратегию тестирования (после выделения тестовых областей и понимания тестовых задач в каждой из областей), является анализ план-графика появления новой функциональности в системе, а зачастую и плана разработки дизайнов и спецификаций к модулям и компонентам системы (от этого зависит выполнение задачи дизайна тестов). Чёткий план проекта, разбитый по задачам планирования, проектирования, дизайна и имплементации даёт менеджеру тестирования фундамент, необходимый для решения задачи, которую ставит вопрос "когда", то есть вопрос связанный с планированием тестирования. Хотя временные оценки и оценка трудоёмкости задачи логически выходят за рамки определения стратегии и относятся к непосредственному планированию тестирования, именно понимание приложения/системы в разрезе его развития с течением проекта, является стратегической составляющей плана тестирования.
    Таким образом, разработка стратегии тестирования для небольших приложений и достаточно серьёзных систем схожа на стадии выделения тестовых областей и понимания зависимостей функциональности приложения от внешних модулей и компонентов. Различаться же стратегия в зависимости от типа тестируемого приложения, может в части понимания развития системы со временем, так как большие системы зачастую разрабатываются в несколько этапов и характеризуются большим по сравнению с десктоп-приложениями набором функционала, который требует перетестирования в каждой новой версии. Понимание что именно должно тестироваться, как, каким образом конкретный функционал будет тестироваться, что является результатом удовлетворяющим целям тестирования и есть зачастую результатом разработки Стратегии тестирования.

    Тестирование софта - статьи

    Анализ документации и разбиение тестируемых интерфейсов на группы

    Прежде, чем приступать к разработке тестов для набора программных интерфейсов, необходимо изучить описание этих интерфейсов в документации, выяснить, что именно предстоит тестировать. При этом стоит разбить интерфейсы на группы, каждая из которых реализует определённую часть функциональности тестируемой системы. Стоит избегать ситуаций, когда, например, для проверки интерфейсов из группы А требуются интерфейсы из группы В ("А зависит от В"), и при этом для тестирования группы В требуются интерфейсы из А (циклическая зависимость групп интерфейсов).
    Иногда разбиение интерфейсов на группы уже проведено в документации. Например, документация по библиотеке Glib [10] состоит из таких разделов, как "Arrays", "Unicode Manipulation", "Memory Allocation" и т.д. Интерфейсы, описанные в каждом таком разделе, как правило, принадлежат к одной функциональной группе.
    Анализ документации и разбиение тестируемых интерфейсов на группы
    Рисунок 3. Разработка тестов с помощью системы T2C.
    При разработке тестов для группы интерфейсов создаётся один или более T2C-файлов. На этом этапе удобно создать и необходимую структуру каталогов для тестового набора.
    Ниже предполагается, что документация по тестируемым интерфейсам является набором html-документов.

    Автоматизация разработки TET-совместимых тестов в GTK+-Verification Test Suite (GTKVTS)

    Подход, использующийся в GTK+-2.0 Verification Test Suite (GTKVTS) при разработке TET-совместимых тестов, позволяет преодолеть некоторые из описанных выше недостатков TET [8].
    Во-первых, в GTKVTS используются т.н. параметризуемые тесты. То есть разработчик пишет шаблон кода тестов на обычном С, отмечая в нём особым образом места, куда впоследствии будут вставлены значения параметров теста. Для каждого такого шаблона может быть задано несколько наборов параметров. Параметром может быть практически что угодно, не только параметр тестируемой функции или её ожидаемое возвращаемое значение. Иногда удобно вынести в качестве параметров типы используемых данных (наподобие template в С++) или даже сам вызов тестируемой функции и т.д.
    По шаблону кода теста для каждого набора параметров генератор С-кода GTKVTS создаёт отдельную функцию на языке С (см. Рис. 1).
    Автоматизация разработки TET-совместимых тестов в GTK+-Verification Test Suite (GTKVTS)
    Рисунок 1. Генерация С-кода тестов по шаблону. "<%0%>" и "<%1%>" в шаблоне кода теста — места для вставки параметров.
    Во-вторых, при генерации кода тестов на С средства GTKVTS автоматически вставляют в этот код определения данных, необходимых для работы теста в среде TET, так что разработчику не приходится заботиться об этом самому. Помимо этого, автоматически создаются и make-файлы, необходимые для сборки тестов, а также файлы сценариев TET, что тоже удобно.
    В GTKVTS также сделана попытка связать выполняемые в тестах проверки с соответствующими фрагментами текста стандарта: в комментариях перед каждым тестом разработчик должен указать текст проверяемых в данном тесте требований. К сожалению, в самом тесте при выполнении проверок этот текст никак не используется, а по трассе теста сложно понять, какие именно требования проверялись и какие из них нарушаются в тестируемой системе.
    К менее существенным недостаткам средств GTKVTS стоит также отнести отсутствие поддержки отладки теста вне TET, а также специализированность инструментальных средств для использования при разработке тестов только для библиотек из стека GTK+.

    Check

    Система Check [3] предназначена в первую очередь для модульного тестирования ПО в процессе его разработки. Тем не менее, Check можно использовать и для тестирования программных интерфейсов на соответствие стандартам.
    Check предоставляет разработчику тестов набор макросов и функций для выполнения проверок в тестах, для объединения тестов в наборы, управления выводом результатов и т.д.
    Тест представляет собой код на языке программирования Си, заключённый между макросами START_TEST и END_TEST. Проверки требований в тестах выполняются с использованием функций fail_unless(проверяющее_выражение, "текст, описывающий ошибку") и fail_if(проверяющее_выражение, "текст, описывающий ошибку").
    Как для каждого теста, так и для набора тестов можно задать функции, выполняющие инициализацию и освобождение используемых ресурсов (т.н. checked и unchecked fixtures)
    К достоинствам системы Check стоит отнести:
  • возможность выполнения каждого теста в отдельном процессе, т.е. изоляция тестов друг от друга и от самой среды Check;
  • автоматическая обработка исключительных ситуаций в тестах;
  • возможность задания максимально допустимого времени выполнения теста;
  • специальные средства для проверки ситуаций, когда выполнение тестируемой функции приводит к посылке сигнала;
  • интеграция системы сборки и запуска тестов с autoconf и automake — широко используемыми средствами автоматизации процесса сборки и установки ПО [4].
    Система Check имеет, тем не менее, некоторые недостатки, из-за которых использование её не всегда оправдано.
  • С помощью Check сложно разрабатывать параметризуемые тесты. Часто возникает ситуация, когда некоторую функцию необходимо проверить при разных наборах значений аргументов, а код теста при этом почти не меняется. Логично было бы передавать эти наборы значений аргументов в данный тест как параметры. Но в Check в качестве параметра тесту явно можно передать только его номер, что не очень удобно.
  • Проверки, выполняемые в тесте, никак не привязаны к тем местам в документации по тестируемым функциям, где сформулированы соответствующие требования.
  • Для добавления нового теста в набор необходимо перекомпилировать и код всех остальных тестов из этого набора, что не всегда удобно.
  • Не поддерживается вывод стандартных кодов результата теста, определённых в стандарте на тестирование соответствия POSIX [5].

    CUnit

    Система CUnit [6] может использоваться в тех же случаях, что и Check [3], но обладает в целом более скромными возможностями.
    Основным недостатком CUnit по сравнению с Check является то, что все тесты, a также система их запуска и сбора результатов, в данном случае выполняются в одном и том же процессе. Т.е. сбой какого-либо из тестов может привести к повреждению области памяти, используемой другими тестами или самой средой CUnit.
    Также в отличие от Check в CUnit нет защиты от "зависания" тестов (нельзя задать максимально допустимое время работы теста).
    CUnit имеет и некоторые преимущества по сравнению с Check.
  • Поддержка т.н. fatal и non-fatal assertions. В первом случае, если проверка выявила нарушение требования, выполнение теста прекращается и остальные проверки в нём, таким образом, не выполняются (в Check всё происходит именно так). Если же нарушение требования выявлено в non-fatal assertion, выполнение теста продолжается. Возможно, остальные проверки в данном тесте позволят в таком случае дать разработчику более полную информацию о том, что происходит в тестируемой системе. Это может быть полезно при выяснении причины обнаруженного сбоя.
  • Набор специальных функций и макросов, облегчающих выполнение часто используемых проверок: равенство и неравенство целых чисел, чисел с плавающей точкой, строк и т.д.
  • Поддержка вывода отчётов о выполнении тестов в разных форматах, в том числе пригодных для отображения в web-браузере (xml+xslt).
    Тем не менее, указанные в предыдущем разделе недостатки системы Check в полной мере относятся и к CUnit. Test Environment Toolkit (TET), о котором речь пойдёт ниже, свободен от некоторых из этих недостатков.

    Достоинства и недостатки существующих решений

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

    MANUAL
    Check
    CUnit
    TET
    GTKVTS
    Параметризация тестов




    +
    Прослеживаемость требований стандарта





    Запуск тестов в отдельном процессе

    +



    Автоматическая обработка исключительных ситуаций
    +
    +

    +
    +
    Ограничение времени работы теста

    +

    +
    +
    Иерархическая организация пакетов
    +




    Удобство отладки тестов
    +
    +
    +


    Переносимость тестов

    +
    +
    +

    Использование стандартных видов вердиктов [5]



    +
    +
    Таблица 1. Сравнение существующих подходов.

    Генерация кода тестов, make-файлов и сценариев TET

    Когда подготовлены тесты в T2C-формате и создан каталог требований, следует запустить генератор кода (T2C Code Generator), который создаст С-файлы с кодом тестов, make-файлы для их сборки, сценарии TET и т.д.

    MANUAL

    Тестовые системы, обеспечивающие тщательное тестирование, как правило, требуют для своей работы целый ряд сервисов нижележащей операционной системы. Поэтому в случаях, когда объектом тестирования является сама операционная система, для запуска таких тестов требуется относительная стабильность объекта тестирования.
    Для смягчения этой проблемы, а также для минимизации непреднамеренного воздействия тестовой системы на целевую систему часто применяется распределенная архитектура построения тестов, при которой для выполнения большей части задач используется вспомогательная инструментальная машина, а на тестируемой системе работает только небольшой тестовый агент. Но даже в таком случае, для взаимодействия тестового агента с инструментальной машиной необходима работоспособность отдельных компонентов тестируемой системы.
    По этой причине, прежде чем приступать к тщательному тестированию программный интерфейсов целевой системы, необходимо удостовериться в работоспособности ее ключевых компонентов.
    В проекте по тестированию POSIX-совместимой операционной системы реального времени для встраиваемых устройств, в отделе технологий программирования ИСП РАН был разработан подход MANUAL, предназначенный для разработки тестов, проверяющих базовую функциональность операционной системы. Эти тесты проверяли работоспособность ключевых компонентов операционной системы перед началом тщательного тестирования тестовой системой, разработанной при поддержке инструментов CTESK.
    Тесты MANUAL представляют собой код на языке программирования Си, использующий макросы и функции библиотеки поддержки MANUAL. Каждое тестовое испытание записывается в виде отдельной функции, начинающейся с макроса TC_START(“имя тестового испытания”) и заканчивающейся макросом TC_END(). Тело теста состоит из трех частей:
  • подготовке окружения и данных;
  • непосредственно тестового воздействия и проверки его результатов;
  • освобождения ресурсов.
    Проверка корректности работы тестируемой системы проводится при помощи функции tc_assert(проверяющее_выражение, “текст, описывающий ошибку”).
    Если проверяющее выражение оказывается равным false, то система считает, что произошла ошибка в тестируемой системе и выводит сообщение, описывающее ее. Кроме того, система автоматически отлавливает в ходе выполнения теста исключительные ситуации, что приравнивается к обнаружению ошибки.

    Система MANUAL поддерживает иерархическую композицию тестовых испытаний в пакеты. Для запуска тестов предусмотрено два режима: автоматический и интерактивный. В автоматическом режиме система выполняет указанный набор тестов или пакетов и сохраняет журнал их выполнения. В интерактивном режиме пользователю предоставляется возможность навигации по дереву пакетов вплоть до индивидуального теста и возможность запустить выбранный тест или пакет на исполнение.

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

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

    Общие сведения.

    Система T2C ("Template-to-Code"), позволяет автоматизировать разработку параметризованных тестов, работающих как в среде TET, так и вне её.
    Код тестов на языке С создается на основе T2C-файла, в котором расположены шаблоны тестов и наборы параметров для них (схема та же, что и для GTKVTS — см. Рис. 1). Фрагмент T2C-файла показан ниже.
    Общие сведения.
    Рисунок 2. Фрагмент t2c-файла.
    Тесты, которые будут созданы по шаблону, показанному на Рис. 2, имеют два параметра: TYPE и INDEX. В первом из сгенерированных тестов в качестве TYPE будет использоваться int, в качестве INDEX — 6, во втором — double и 999, соответственно.
    Также, как и в GTKVTS, в код теста на С автоматически добавляются все необходимые определения данных и функций для выполнения этого теста в среде TET, а также генерируются make-файл и файл сценария TET.
    Таким образом, инструменты T2C сохраняют основные достоинства системы GTKVTS, но при этом они поддерживают рекомендации по разработке тестов соответствия, сформулированные, в частности, в [9]:
  • составление каталога элементарных требований к тестируемым программным интерфейсам;
  • привязка проверок, выполняемых в тестах, к соответствующим местам в тексте стандарта;
  • оценка качества тестирования в терминах покрытия элементарных требований.
    В T2C также сделаны следующие усовершенствования по сравнению с GTKVTS.
  • Разработчику тестов предоставляется набор высокоуровневых программных интерфейсов (T2C API), с помощью которых и выполняются проверки в тестах. При этом, если проверка показывает, что некоторое требование стандарта нарушено, в трассу теста (журнал TET) выводится, помимо всего прочего, текст этого требования.
  • Есть возможность создания независимой версии теста на чистом C/C++, не использующей средства TET. Это может быть полезно для отладки самого теста или для более тщательного исследования поведения тестируемой системы в случае отладки ошибки в реализации.
  • Заготовки для T2C-файлов создаются автоматически по размеченному тексту стандарта.
  • В T2C-файле можно указать код, необходимый для инициализации и освобождения ресурсов, общих для всех тестов, код которых будет создан по этому файлу, а также для освобождения ресурсов, выделенных в данном тесте.
  • Поддерживается выполнение тестов в отдельных процессах.
  • Есть возможность задать максимальное допустимое время работы теста. Это полезно в тех случаях, когда какие-то из тестов могут "зависнуть".

    Подготовка каталога требований

    По размеченной документации с помощью инструмента ReqTools, входящего в состав T2C, в web-браузере создаётся каталог требований для данной группы интерфейсов. Этот каталог используется при работе теста: если проверка в тесте показывает, что некоторое требование нарушено, из каталога загружается формулировка этого требования с соответствующим идентификатором и выводится в трассу теста.

    Применение T при разработке тестов для LSB Desktop

    Система T2C использовалась (и используется в настоящее время) при разработке тестов для интерфейсных операций ("интерфейсов") библиотек Linux, входящих в стандарт Linux Standard Base (LSB). C её помощью подготовлены тесты базовой функциональности для следующих библиотек:
  • Glib (libglib-2.0);
  • GModule (libgmodule-2.0);
  • ATK (libatk-1.0);
  • Fontconfig (libfontconfig).
    В Таблице 2 представлены результаты тестирования. Описания найденных ошибок опубликованы на http://linuxtesting.ru/results/impl_reports.
    Библиотека
    Версия
    Всего интерфейсов
    Протестировано
    Найдено ошибок в реализации
    libatk-1.0
    1.19.6
    222
    222 (100%)
    11
    libglib-2.0
    2.14.0
    847
    832 (98%)
    13
    libgmodule-2.0
    2.14.0
    8
    8 (100%)
    2
    libfontconfig
    2.4.2
    160
    133 (83%)
    11
    Всего

    1237
    1195 (97%)
    37
    Таблица 2. Результаты тестирования LSB библиотек тестами T2C.
    Примечание 1. В столбце "Версия" указывается последняя версия соответствующей библиотеки на момент публикации тестового набора. Количество найденных ошибок указано именно для этой версии. По обнаруженным ошибкам идёт активная работа совместно с разработчиками соответствующих библиотек, так что в более поздних версиях некоторые или даже все из этих ошибок могут быть исправлены.
    Примечание 2. В столбце "Всего интерфейсов" указано общее количество входящих в LSB интерфейсных операций ("интерфейсов") библиотеки, включая и недокументированные. Практически все документированные интерфейсы были протестированы.
    Средние затраты на полный цикл разработки теста (от разметки и анализа требований до отлаженного кода теста) для одного интерфейса составили порядка 0,5-1 человеко-дня.
    Отметим, что интерфейсы в документации по данным библиотекам далеко не всегда описаны подробно. В среднем для каждого интерфейса было выделено 2-3 элементарных требования.
    Сведения о количестве выделенных элементарных требований к тестируемым интерфейсам и о покрытии этих требований представлены в Таблице 3.

    Библиотека

    Выделено требований

    Проверено требований

    Покрытие по требованиям, %

    libatk-1.0

    515

    497

    96%

    libglib-2.0

    2461

    2290

    93%

    libgmodule-2.0

    21

    17

    80%

    libfontconfig

    236

    177

    75%

    Всего

    3233

    2981

    92%

    Таблица 3. Выделенные и проверяемые требования для LSB библиотек.

    Процесс разработки тестов с помощью системы T

    В этом разделе рассматриваются основные стадии процесса разработки тестов с помощью T2C.

    Разметка требований в документации

    На данном этапе в документации необходимо выделить элементарные требования к тестируемым интерфейсам и присвоить каждому требованию уникальный идентификатор [9]. При необходимости текст требований можно переформулировать для улучшения читаемости.
    Разметка требований выполняется в html-редакторе KompoZer (www.kompozer.net) с использованием инструмента ReqMarkup, разработанного в рамках проекта OLVER [11] и затем интегрированного в систему T2C.

    Разработка тестов в T-формате

    Этот этап самый важный в процессе создания тестов. На данном шаге разработчику нужно заполнить заготовку T2C-файла, добавляя туда шаблоны кода тестов и наборы параметров для них. Также, если необходимо, в специальных секциях файла следует указывать код инициализации и освобождения ресурсов, общих для всех тестов, код которых будет сгенерирован по этому файлу.
    При редактировании T2C-файла может оказаться полезным компонент T2C Editor — plug-in для Eclipse IDE. Он обеспечивает удобную навигацию по секциям T2C файла, предоставляет средства для работы с параметрами тестов и т.д.

    Сборка, запуск и отладка тестов

    На данном этапе нужно провести сборку тестового набора, используя make-файлы, сгенерированные на предыдущем шаге. После чего можно запускать готовые тесты в среде TET или отлаживать какие-либо из них вне TET.

    Создание шаблона T-файла

    Когда выделены и размечены все требования для интерфейсов из данной функциональной группы, с помощью ReqMarkup автоматически создаётся заготовка T2C-файла.

    T: технология автоматизированной

    , ,
    Труды Института системного программирования РАН Аннотация. В статье обсуждается задача автоматизации разработки тестов базовой функциональности программных интерфейсов (API). Рассматриваются существующие решения по разработке таких тестов и описываются разработанные в ИСП РАН технология и инструментальные средства T2C, нацеленные на создание тестов "среднего" уровня по качеству тестирования и объему соответствующих трудозатрат. Приводится статистика результатов использования T2C в рамках проекта по тестированию интерфейсов системных библиотек Linux, описываемых стандартом LSB.

    TET (Test Environment Toolkit)

    Система TETware (TET, Test Environment Toolkit) достаточно широко используется для тестирования различных программных интерфейсов. Средства TET позволяют запускать разные тесты единым образом и получать отчёт о работе тестов в едином формате [7]. Информация о выполнении теста, включая его результат и сообщения, которые в нём выводятся, попадает в так называемый журнал TET.
    Основными компонентами TET являются:
  • test case controller (tcc) — этот компонент управляет запуском тестов и сбором выводимой ими информации;
  • интерфейс прикладного программирования (TET API), который нужно использовать в тестах для того, чтобы их можно было выполнять в среде TET. TET API существует для различных языков программирования, в том числе и для С/С++.
    Наиболее существенными достоинствами TET, на наш взгляд, являются следующие:
  • единая среда для запуска тестов;
  • обработка исключительных ситуаций в тестах (например, segmentation fault) средствами test case controller;
  • общие для всех тестов допустимые коды результата, соответствующие стандарту [5]: PASS, FAIL, UNRESOLVED и т.д., плюс возможность определить дополнительные коды результата тестов;
  • возможность добавлять новые тесты в набор без перекомпиляции существующих тестов (использование т.н. сценариев TET)
  • единый формат отчёта о выполнении тестов (журнала TET).
    Эти преимущества TET облегчают анализ результатов работы тестов. В частности, программные средства для обработки журнала TET при построении статистики по результатам тестов могут не учитывать специфику проведённых тестов.
    С другой стороны, средства TET, в основном, автоматизируют выполнение тестов и сбор результатов их работы. TET не предоставляет ни инструментов для автоматизации разработки тестов, ни API для выполнения проверок в тестах. Соответственно, есть несколько причин, из-за которых применение TET "в чистом виде" (без использования каких-либо "надстроек" над ним) не очень удобно.
  • Отсутствие средств для связи проверок, проводимых в тестах, с текстом соответствующего стандарта.

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

  • Разработчику тестов необходимо добавить в код определения нужных для запуска в среде TET специфических функций, структур и т.д., что вполне можно было бы сделать и автоматически.

  • Тесты, запускаемые с помощью test case controller, не всегда удобно отлаживать. Как для поиска ошибок в тесте, так и для исследования поведения тестируемой системы в случае сбоя полезно исключить влияние средств TET на работу теста, что существенно упрощает использование программ-отладчиков (например, gdb).

    Ниже речь пойдёт как раз о "надстройках" над TET (системы GTKVTS и T2C), в которых перечисленные выше недостатки в той или иной степени преодолены.

    Хорошо известно, что проверить корректность

    Хорошо известно, что проверить корректность работы любой серьезной программной системы во всех ситуациях, которые могут встретиться в ходе ее реальной эксплуатации, невозможно. Поэтому тестирование программного обеспечения неизбежно связано с поиском компромисса между тщательностью проверок и ограничениями на имеющиеся ресурсы.
    Поиск оптимального решения зависит от множества факторов, определяющих специфику конкретного проекта. В данной работе мы рассмотрим разработку тестов, предназначенных для тестирования на соответствие требованиям стандарта программных интерфейсов на языке программирования Си. Такая постановка задачи предопределяет значение целого ряда факторов.
  • Необходимость сопровождения тестов вместе с развитием стандарта.
  • Наличие стандарта подразумевает существование достаточно качественного описания требований к функциональности тестируемой системы. Хотя для стандартов, находящихся в стадии активного развития, такое предположение не всегда оказывается верным.
  • Потребность в простоте анализа обнаруживаемых ошибок, так как во многих случаях ошибки в реализации будут анализировать не разработчики тестов, а представители сторонних организаций, проверяющих свои продукты на соответствие требованиям стандарта.
    Эти особенности формируют ключевые требования к результирующему тестовому набору, и тем самым, к подходу по его разработке. Но из них не следует никаких ограничений на возможные решения конфликта между детальностью тестирования и имеющимися ресурсами.
    Наиболее распространенным решением при разработке тестов на соответствие является выбор тестирования базовой функциональности, под которым понимается тестирование, обеспечивающее проверку корректности функционирования системы на нескольких основных вариантах её использования, включающих в себя и некоторые ошибочные ситуации. Распространенность такого выбора объясняется тем, что тестирование базовой функциональности позволяет с минимальными затратами выявить большинство наиболее грубых отклонений от стандарта.


    Существуют и альтернативные решения. В случаях, когда в короткие сроки требуется обеспечить покрытие очень большого количества функций, выбор падает на менее тщательное тестирование, например, такое, которое нацелено на проверку только минимальной работоспособности каждой функции. Одна из технологий разработки тестов такого вида — технология Azov — представлена в настоящем сборнике.
    В ситуациях, когда обеспечение точного соответствия стандарту является крайне важным и подкрепляется наличием достаточного количества ресурсов, выбирается более тщательное тестирование, например, такое, которое нацелено на проверку всех выделенных классов тестовых ситуаций для каждого отдельного аспекта функциональности каждой интерфейсной функции. Примером инструментов, поддерживающих разработку столь детальных тестов, которые при этом еще и остаются удобными для дальнейшего сопровождения и развития, является набор инструментов CTESK [1]. Такие особенности CTESK как:
  • наличие средств для формального описания требований к тестируемой системе;
  • поддержка автоматической генерации последовательностей тестовых воздействий при помощи динамического построения модели поведения тестируемой системы;
  • широкий набор возможностей для задания метрик качества тестирования в терминах модели требований с автоматизацией сбора информации о достигнутом покрытии;
    помогают организовать систематический перебор всех тестовых ситуаций и упростить анализ тестового покрытия.
    Набор инструментов T2C, о котором пойдет речь в настоящей статье, нацелен на разработку тестов базовой функциональности. Превосходя возможности технологии Azov и уступая инструментам CTESK по уровню тщательности тестирования получаемых тестов, инструменты T2C позволяют эффективно достигать середины в балансе между качеством тестирования и ресурсами, необходимыми для построения соответствующих тестов. При этом инструменты T2C поддерживают основные рекомендации по работе с требованиями при разработке тестов на соответствие, такие как составление каталога элементарных требований стандарта, обеспечение прослеживаемости требований стандарта в тестах и измерение качества тестирования в терминах покрытия элементарных требований.
    Данная статья построена следующим образом. В первом разделе мы рассмотрим ряд подходов, решающих близкие задачи, а также обсудим их достоинства и недостатки применительно к стоящим перед нами целям. Далее мы представим основные особенности инструментов T2C и поддерживаемого ими процесса разработки. Результаты будут проиллюстрированы на опыте применения данного подхода при разработке тестов на соответствие требованиям стандарта LSB [2] для ряда библиотек из стека GTK+ и fontconfig. В заключении мы обсудим возможные направления дальнейшего развития подхода и его интеграции с другими инструментами семейства UniTESK.

    Задача тестирования программных интерфейсов на

    Задача тестирования программных интерфейсов на соответствие документации (в том числе стандартам) является важным звеном в обеспечении качества и интероперабельности программных систем. Для решения этой задачи разрабатываются различные методики и инструменты, позволяющие систематизировать и автоматизировать работу. При этом всегда возникает выбор баланса между качеством тестирования и затратами на разработку соответствующих тестов. Решение здесь обычно принимается директивно из достаточно субъективных соображений. Тем временем, с выбором целевого качества тестирования связан и выбор оптимальной технологии и инструментов, так как для различных уровней цены и качества необходимы разные подходы. Так, например, для глубокого тестирования важных интерфейсов хорошо зарекомендовала себя технология UniTESK [12], которая, однако, требует и соответствующих высоких затрат как на освоение технологии, так и на разработку самих тестов.
    В данной работе рассмотрена технология T2C, нацеленная на эффективную разработку тестов "среднего уровня" для проверки базовой функциональности программных интерфейсов. В данном случае понятие "средний уровень" соответствует наиболее распространенным представлениям о качестве производственного тестирования, наблюдаемом в большинстве изученных авторами тестовых наборов (например, сертификационные тесты Open Group, сертификационные тесты LSB, тесты OpenPosix и Linux Test Project). T2C позволяет повысить эффективность разработки таких тестов за счет использования следующих основных возможностей, минимизирующих ручной труд на создание окружения и дублирование кода, не принципиального для специфической логики конкретного теста:
  • Автоматическая генерация заготовок тестов на основе каталога требований.
  • Использование именованных параметров в коде тестов, для каждого набора значений которых создается соответствующий экземпляр теста.
  • Высокоуровневое API для использования в коде тестов для осуществления проверок и трассировки.


  • Генерация независимых отдельных тестов в виде самодостаточных простых программ на C/C++, что в практическом плане существенно упрощает отладку тестов и реализации (в отличие от отладки в полной среде запуска TET или подобной).
    Среда выполнения тестов, созданных по технологии T2C, опирается на широко распространенные средства TETware, что позволяет легко интегрировать тесты в существующие тестовые наборы и окружения управления тестированием и анализа результатов. Кроме того, важной особенностью технологии T2C является систематизированная работа с каталогом требований к проверяемым интерфейсам и обеспечение связи отдельных требований с проверками в тестах и соответствующими сообщениями в отчете о результатах тестирования.
    Технология T2C была успешно применена в Институте системного программирования РАН в проекте [13] по разработке сертификационных тестов для проверки соответствия библиотек Linux стандарту LSB. В работе представлена статистика по разработанным тестам, найденным ошибкам и трудозатратам, которая позволяет сделать вывод об эффективности данной технологии для заданного сегмента качества разработки тестов программных интерфейсов различных модулей и библиотек. В настоящее время реализована инструментальная поддержка Си и Си++, но нет принципиальных проблем и для других современных языков программирования общего назначения, например C# или Java. Стоит отметить, что важным фактором для успешного применения T2C является наличие достаточно устоявшегося текста требований, так как этап анализа и составления каталога требований в условиях частых изменений и плохого качества описаний может составлять существенную часть трудозатрат.
    В дальнейшем в T2C предполагается реализовать средства для автоматизации работы с множествами значений параметров тестов. Будет повышена и степень интеграции инструментов T2C со средой разработки Eclipse. Также предполагается исследовать возможности интеграции систем T2C и CTESK.

    Тестирование софта - статьи

    Как на практике?

    Получив первый удачный результат парной работы, я попытался посмотреть со стороны на свою работу как ведущего тестировщика или менеджера нашей команды. Что я делаю когда ко мне, как к человеку наиболее погружённому в проект, возникает вопрос от кого из тестировщиков? Я подьезжаю к нему на кресле и втыкаюсь носом в монитор и головой в вопрос. Опять таки — мы начинаем работать в паре.

    Как построили работу?

    Основываясь на опыте работы в паре в бытность разработчиком, и на подсмотренные у ХР-команды «манёвры», мы менялись раз в 15 минут. Попеременно «штурмуя» и «пилотируя» устаёшь меньше — это могу сказать с уверенностью. Внимание не рассеивается. Постоянное общение не даёт «залипать» в трудных моментах. Штурман идёт по чек-листу и периодически направляет пилота — делаем раз, два, три, потом смотрим: здесь, здесь, здесь… Потом меняемся.

    На чём экономим?

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

    Парное тестирование — возьмём от ХР лучшее

    , автор проекта "Тестер", Совершенно верно, парное тестирование, то есть работа двух тестировщиков в паре над одной задачей и буквально за одной машиной. Работа в паре присуща экстремальному программированию, собственно видя как работают в паре наши ХР-шники (я говорю о ХР-team компании в которой я работаю на данный момент) я и решил опробовать этот подход со своими коллегами по отделу. Наблюдая практическое применение ХР при решении разноплановых задач и ту лёгкость в управлении ресурсами которую даёт ХР-команде применения основ методологии ХР, я решил взять некоторые аспекты для работы команды тестирования. Возьмём от ХР лучшее.

    Первые результаты.

    Обед у нас ровно посредине рабочего дня: работаем с 9-00 до 13-00, потом час обеда и ещё с 14-00 до 18-00 (восем часов + час обеда). К 12-00 мы полностью прошли по первому тестовому окружению. При этом не особо уставая, делая обычные перерывы на кофе, кроме того, я периодически прерывался на менеджерские задачи (катание на кресле к другим тестерам). Уточню, что до 12-00 мы успели выполнить и «вольную программу» — то есть в процессе работы возникали идеи, которые отклоняли курс от чек-листа, но мы реализовывали и их, не откладывая на потом. Обычно мы при прохождении по чек-листу, стараемся записывать идеи на «вольную программу» на листик, чтобы не сбиваться с ритма. При работе в паре, проблемы с отклонениями от ритма нет — мы ведём друг друга и спокойно возвращаемся к чек-листу, потому что штурман занят именно этим — направлением работы по тестированию и отслеживаением, в каком месте мы остановились. К обеду мы не только выполнили обычный план разделённый надвое, но и намного его опередили — процентов на 25 (3 часа вместо ориентироваочных 4-ёх). Прикнув по времени, что есть ещё полный рабочий час, мы приянлись за второе тестовое окружение. После перерыва на обед, мы смогли выделить время на актуализацию чек-листа, выполнению нескольких утомительных тасков (проверке локализации — привет японии!) и вернуться к тестированию. До вечера мы спокойно выполнили обучную норму двух тестировщиков и успели закрыть несколько сопутствующих тасков (к примеру, у штурмана на бекграунде ставилось тестовое окружение — работа не требующая пристального внимания, но нежелательная во время непосредственного тестирования).

    Поставим эксперимент.

    Сдвигаются два монитора — на одном открываются рабочие документы: чек-лист и release notes по последней версии продукта и наш клиент багтрекера, на второй машине запускается vmWare с тестовым окружением. Суть эксперимента — на полноценный проход по чек-листу нашего приложения под тестом (эксперимент ставился на среднем по сложности проекте) одному тестировщику требуется полный рабочий день, кроме того остаётся полчаса на свободный поиск (если к концу рабочего дня остаётся время и желание погонять приложение в вольном режиме). Посмотрим какое время потратят два тестера на эту задачу.

    Психологический эффект.

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

    Зачем нам парное тестирование?

    Первый раз я усадил двух тестеров за одну машину, когда нужно было вводить в курс дел по одному из наших проектов нового игрока нашей test-team. К слову сказать, проект вёлся нами уже несколько месяцев, функции команды тестирования по отношению к данному продукту заключались в финальном тестировании перед очередным релизом. Проект очень небольшой, документации по нему нет. Проводили то, что мы называем «профессиональным» тестированием, то есть тестированием, в котором тестировщик выполняет все функции эксперта по данной системе и владеет полным набором информации по проекту. Проект растёт, функции реализуются, назрела необходимоть формализации процесса тестирования, пусть даже и в столь небольшом проекте. Будем писать чек-листы. Новый игрок усаживается рядом с одним из опытных тестировщиков, ему же в руки вручается недружелюбный МАК (будем и с Мак ОС знакомиться и со специфичным Маковским приложением). Начинается пояснение: что за продукт, зачем, как он работает, то есть по-сути устно излагается product vision. Почему бы не положить это на "бумагу"? Открывается Excel на соседней машинке. Более опытный "штурмует" — то есть выполняет функции штурмана, поясняет и сам же записывает, пока обучаемый вникает в незнакомые кнопочки и покоряет однокнопочную мышь. Совмещая функции ознакомления и документирования начал создавать чек-лист по нашему продукту. Получается, что новый игрок не зная всех функций системы задаёт много вопросов, которые просто и понятно ложатся в основу чек-листа, а штурман обладая всеми знаниями по системе со своей стороны дополняет чек-лист ожидаемым поведением системы. Составление чек-листа, как один из аспектов тестирвоания приложения в паре даётся гораздо легче. Штурман — тот кто видит чек-лист и приложение на более высоком уровне, управляет уровнем детализации чек-листа и следит за покрытием всех тестовых сценариев, а непосредственно пилот — тестер в руках которого тестовое окружение и приложение под тестом, углубляется в тестирование как таковое, не овлекаясь на писателький труд. В данном случае парная работа была просто необходима — нужен момент передачи знаний по проекту, а кроме того, удалось сосредоточить нового игрока исключительно на проекте в новой незнакомой ему ОС.

    Тестирование софта - статьи

    Аннотация

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

    Как эффективность обходчика влияет на тестирование

    Используемый в тестах, построенных по технологии UniTESK, обходчик позволяет осуществить полный перебор возможных тестовых ситуаций путем вызова всевозможных тестовых воздействий во всех состояниях графа, заданного в неизбыточном виде. Таким образом, могут быть получены последовательности тестовых воздействий, которые практически никогда не проверяются при ручном построении тестов. Может показаться, что для разработки хорошего теста достаточно указать набор сценарных функций и определить функцию вычисления состояния, и после этого обходчик все сделает сам. В общем случае это неверно. Неправильно выбранный метод обхода, неправильные ограничения на количество состояний, неправильное определение состояния графа: все это может привести к невозможности получения результата в условиях ограниченности ресурсов. Возникает следующая проблема: ошибка может проявиться в последовательности тестовых воздействий, состоящей из сотни переходов по дугам графа, но выбранный механизм обхода будет не в состоянии обеспечить достижения подобной ситуации, по причине того, что он неудачно использует полный перебор. Пример. Если человек находится на улице у дома номер 1, и хочет дойти к дому номер 91, то он может пойти прямо и дойти до цели. Действуя иначе, он может поворачивать на любом перекрестке, стараясь не проходить по два раза по одному и тому же кварталу. По дороге он увидит много интересного, но к цели придет с колоссальным запозданием. Из этого следует, что строить обход графа, заданного в неизбыточном виде, нужно аккуратно. При неосмотрительном использовании перебора всех возможных вариантов в сложных ситуациях могут возникнуть трудности. Поэтому детали реализации алгоритма обходчика имеют большое значение для эффективности тестирования больших систем. Очевидно, что построить обход всех дуг графа можно различными путями, необязательно эквивалентными по эффективности использования памяти и времени. Приведем несколько примеров. "Цепной" пример. Описываемая в этом примере ситуация возникает в тех случаях, когда состоянием системы является целое число, и доступны два вида тестовых воздействий: увеличивающее и уменьшающее значение состояния на единицу. Как эффективность обходчика влияет на тестирование Рисунок 1.Граф состояний и переходов для "цепного" примера. В этом случае возможен обход вида (перечисляются состояния в порядке их посещения) 0, 1, 0, 1, 2, 1, 0, …, 0, 1, 2, …, (n-2), (n-1), (n-2), …, 1, 0. Также возможен обход вида 0, 1, 2, …, (n-2), (n-1), (n-2), …, 1, 0. В обоих случаях проходятся все дуги во всех состояниях графа, однако в первом случае количество тестовых воздействий, проделанных при обходе, равно 2n(n+1)/2 = n(n+1), тогда как во втором оно составляет только 2n. Из приведенного примера вытекает следующая рекомендация по работе обходчика: в случае наличия непройденных дуг из текущего состояния графа, обходчику рекомендуется первой вызывать операцию, соответствующую одной из этих непройденных дуг.
    То есть, не рекомендуется переходить к вызову сценарной функции B, пока не исчерпаны возможные вызовы сценарной функции A. При этом порядок задания сценарных функций, определяющий последовательность их вызова обходчиком, должен тщательно обдумываться разработчиком тестов для определения максимально эффективного обхода. Примеры с потоками. Приведенные ниже примеры выбраны из-за высокой степени связности графов, а также относительно малого количества состояний и дуг, что позволяет понять существо проблемы, не рассматривая сложные случаи. О том, что такое поток (thread) и обработчик завершения потока (cleanup handler) можно прочесть в [] и [] соответственно. Простой пример с потоками. Граф состояний и переходов простой модели системы управления потоками, описываемой в стандарте POSIX [], изображен на Рисунке 2. В этой модели разрешается иметь не более одного потока одновременно. Как эффективность обходчика влияет на тестирование Рисунок 2. Граф состояний и переходов для простого примера работы с потоками. Здесь первая цифра в состоянии обозначает количество активных потоков в системе, а вторая - количество зарегистрированных обработчиков завершения потоков. В POSIX есть операции создания нового потока (обозначим ее как C от create), помещения функции в стек обработчиков завершения данного потока (U, от push) и выталкивания ее из этого стека (O, pop), а также операция уничтожения данного потока (K, kill). В нашей простой модели потоку разрешается иметь не более двух обработчиков завершения. В рассматриваемом случае, даже если обходчик следует рекомендации по использованию сценарных функций, существует, по крайней мере, два возможных маршрута обхода с разной эффективностью. Первый вариант (13 переходов): [0, 0], [1, 0], [1, 1], [1, 2], [1, 1], [1, 0], [0, 0], [1, 0], [1, 1], [0, 0], [1, 0], [1, 1], [2, 1], [0, 0]. Он соответствует последовательности использования сценарных функций CUOK. Обходчик пытается в каждом состоянии вызывать функции в указанном порядке. Второй вариант (15 переходов): [0, 0], [1, 0], [0, 0], [1, 0], [1, 1], [0, 0], [1, 0], [1, 1], [1, 0], [1, 1], [1, 2], [0, 0], [1, 0], [1, 1], [1, 2], [1, 1].


    Он соответствует последовательности сценарных функций CKUO. Сложный пример с потоками. Рассмотрим более сложную модель той же системы, допуская уже два активных потока. Как эффективность обходчика влияет на тестирование Рисунок 3. Граф состояний и переходов более сложной модели потоков. В данном случае первая цифра в состоянии обозначает количество активных потоков. Далее идут несколько цифр, отражающих количество обработчиков завершения для соответствующих потоков. В системе имеются те же операции, что и раньше, но на этот раз разрешим потоку иметь не более одного обработчика завершения. В данном случае, даже если рекомендация по последовательному использованию сценариев выполняется, существует, по крайней мере, два возможных пути, с разной эффективностью. Первый вариант (25 переходов, соответствует порядку CKUO): [0], [1, 0], [2, 0, 0], [1, 0], [0], [1, 0], [1, 1], [2, 1, 0], [1, 1], [0], [1, 0], [2, 0, 0], [2, 1, 0], [2, 1, 1], [1, 1], [2, 1, 0], [2, 0, 0], [2, 0, 1], [1, 0], [2, 0, 0], [2, 0, 1], [2, 1, 1], [2, 1, 0], [2, 1, 1], [2, 0, 1], [2, 0, 0]. Второй вариант (27 переходов, соответствует порядку OKUC): [0], [1, 0], [0], [1, 0], [1, 1], [1, 0], [2, 0, 0], [1, 0], [1, 1], [0], [1, 0], [2, 0, 0], [2, 1, 0], [2, 0, 0], [2, 0, 1], [2, 0, 0], [2, 1, 0], [1, 1], [2, 1, 0], [1, 1], [2, 1, 0], [2, 1, 1], [2, 1, 0], [2, 1, 1], [2, 0, 1], [1, 0], [2, 0, 0], [2, 0, 1], [2, 1, 1], [1, 1]. Следствие из рассмотренных примеров. Разница в эффективности, показанная в данных примерах невелика, что обусловлено простотой модели (малое количество состояний и дуг), а также оптимальностью последовательного использования сценариев. Разница достигается исключительно за счет изменения расположения сценариев в обойме обходчика. Это указывает на необходимость анализа работы обходчика и применения результатов этого анализа при разработке модели, а также тестовых сценариев.

    Краткое описание обходчиков UniTESK

    В настоящий момент CTesK [], один из инструментов, поддерживающих разработку тестов по технологии UniTESK, включает два обходчика, реализующих различные алгоритмы построения обхода графов и имеющих различные ограничения на вид графов, с которыми они могут работать. Первый обходчик, dfsm [,], реализует алгоритм генерации тестовой последовательности по неизбыточному описанию графа сценария при помощи построения его обхода в глубину и предназначен только для работы с детерминированными графами. Обход в глубину означает, что обходчик хранит цепочку из тех вершин, не все дуги которых пройдены. Попав в одну из таких вершин, он каждый раз доходит до конца цепочки, прежде чем пройти по еще не пройденной дуге. Тем самым, обходчик dfsm не следует рекомендации, сформулированной выше. Для вершин, где все дуги пройдены, этот обходчик хранит одну дугу, позволяющую (быть может, после прохождения еще каких-то дуг) попасть на цепочку вершин, имеющих еще не пройденные дуги. Этот алгоритм прекрасно проявил себя в многочисленных проектах [] по тестированию различных видов программного обеспечения при помощи технологии UniTESK. Однако требование детерминированности графа сценария требует от разработчиков тестов дополнительных усилий, направленных на избавление от недетерминизма графа, динамически извлекаемого из наблюдений за поведением тестируемой системы. Причем такой недетерминизм появляется достаточно часто, и не только из-за недетерминированности самой тестируемой системы, но и из-за неаккуратного абстрагирования в графе сценария от деталей реализации. В то же время, в большинстве случаев только небольшая часть дуг графа соответствует недетерминированным переходам, и существует детерминированный остовный подграф, который мог бы быть использован для построения обхода графа. Задача построения алгоритмов, строящих обход недетерминированных графов, заданных неизбыточным образом, была успешно решена []. Но предложенный в работе [] алгоритм является полновесным средством работы с существенно недетерминированными графами. В то же время, в большинстве случаев можно обойтись более простым решением, обладающим более узкой областью применимости, которое реализовано во втором обходчике CTesK, ndfsm []. ndfsm реализует жадный алгоритм построения обхода дуг графа, опираясь при этом на предположение о наличии детерминированного полного остовного подграфа, т.е. детерминированного подграфа, в который входят все вершины исходного графа. Жадный алгоритм, оказавшись в вершине, где все дуги уже пройдены, пытается найти ближайшую к ней вершину, где еще есть непройденная дуга, и попасть в нее. Значит, ndfsm действует в соответствии с высказанной рекомендацией. Наличие детерминированного остова позволяет обходчику всегда находить путь, ведущий в нужную вершину.

    Обход дерева

    Сравним эффективность обходчиков при обходе полного бинарного дерева высотой N, с операциями "опуститься на уровень ниже влево", "опуститься на уровень ниже вправо" и "подняться на уровень выше". Теоретическая оценка длины обхода снизу равна количеству дуг 2N+2-4. Эта оценка может быть достигнута, например, при обходе в симметричном порядке []. В Таблице 2 приведены длины обходов и время их выполнения для разных обходчиков.

    N dfsm, длина dfsm, время ndfsm, длина ndfsm, время мин. длина
    9 4088 6 2044 3 2044
    10 8184 25 4092 12 4092
    11 16376 100 8188 45 8188
    12 32760 427 16380 19016380
    Таблица 2. Длина и время обхода дерева для разных N. Обход дерева любопытен тем, что на нем при использовании обходчика ndfsm достигается минимальная длина обхода. Обходчик dfsm делает ровно в два раза больше проходов по дугам.

    Обход полного графа и его модификаций

    В данном разделе будут рассмотрены экспериментальные данные по построению обхода для следующих видов графов.
  • Полный ориентированный граф с N вершинами, обозначаемый KN.
  • Граф KM(KN), представляющий собой соединенные с помощью KM M полных графов KN. Вершины этих M графов пронумерованы числами от 0 до (N-1) и все вершины с номером 0 соединены между собой полным графом.
  • Граф KM KN, являющийся декартовым произведением полных графов. Он тоже представляется как M графов KN с пронумерованными вершинами, в которых всем вершинам с одинаковыми номерами соединены друг с другом. В следующих таблицах приведены длины и время (в секундах) построения обхода разными обходчиками для таких графов. Числа N и M выбраны нечетными, потому что для полного графа с нечетным количеством вершин существует эйлеров цикл. Теоретическая оценка длины обхода KN снизу равна количеству его дуг E(KN)=N × (N - 1).

    Граф dfsm, длинаdfsm, времяndfsm, длинаndfsm, времямин. длина
    K3 14 0 8 0 6
    K5 60 0 24 0 20
    K7 154 0 48 0 42
    K51 46750 9 2600 1 2550
    K53 52364 11 2808 1 2756
    K55 58410 12 3024 1 2970
    K57 64904 13 3248 1 3192
    K59 71862 15 3480 1 3422
    K61 79300 17 3720 1 3660
    Таблица 3.Длина и время обходов полных графов. Отметим тот факт, что обходчик ndfsm проходит на (N-1) дуг больше, чем нужно по минимуму для обхода полного графа KN. Оценка длины обхода графа KM(KN) снизу тоже равна количеству его дуг
    E(KM(KN)) = M × E(KN) + E(KM) = M × N × (N - 1) + M × (M - 1).

    Графdfsm, длинаdfsm, времяndfsm, длинаndfsm, времямин. длина
    K3(K51) 140264 29 7810 2 7656
    K5(K51) 233810 53 13028 3 12770
    K7(K51) 327404 80 18254 5 17892
    K3(K53) 157106 35 8434 2 8264
    K5(K53) 261880 60 14068 4 13800
    K7(K53) 366702 94 19710 6 19334
    K3(K55) 175244 40 9082 2 8916
    K5(K55) 292110 71 15148 4 14870
    K7(K55) 409024 108 21222 6 20832
    Таблица 4. Длина и время обходов графов KM(KN). Отметим, что длина обхода этих графов с помощью dfsm может быть вычислена по той же формуле, что и количество дуг: если D(G) обозначает длину обхода графа G с помощью dfsm, то


    D(KM(KN))=M × D(KN) + D(KM). Можно предположить, что при обходе графа, состоящего из нескольких слабо связанных (при помощи одной-двух дуг) частей, длина его обхода получается сложением длин обходов частей и количества проходов по связывающим дугам. Оценка длины обхода графа KM KN снизу также равна количеству его дуг
    E(KM × KN) = M × E(KN) + N × E(KM) = M × N × (N + M - 2).

    Графdfsm, длинаdfsm, времяndfsm, длинаndfsm, времямин. длина
    K3× K51 269265 62 8108 1 7956
    K5× K51 804614 186 14024 5 13770
    K7× K51 1694413 406 20348 5 19992
    K3× K53 301095 68 8744 2 8576
    K5× K53 898884 216 15104 4 14840
    K7× K53 1890675 302 21888 5 21520
    K3× K55 335337 50 9404 2 9240
    K5× K55 1000234 164 16224 3 15950
    K7× K55 2101501 365 23484 5 23100
    Таблица 5. Длина и время обходов графов KM KN. Теперь длина обхода с помощью dfsm уже не может быть определена с помощью аналогичной формулы. Приведенные таблицы показывают, что на многих графах обходчик ndfsm работает эффективнее dfsm, и, если проверка детерминизма графа не является необходимым элементом тестирования, предпочтительнее использовать первый обходчик. Можно заметить, что при увеличении количества дуг длина обхода с помощью dfsm составляет порядка 50% от произведения количества дуг на количество состояний, что означает практическую невозможность использования dfsm-обходчика в случае насыщенных графов с сотнями состояний.

    Сфера применения тестирования на основе спецификаций

    Тестирование на основе спецификаций максимально эффективно при тестировании групп функций с объемным, логически связным текстуальным описанием, а также функций с закрытым кодом. Рассмотрим преимущества подхода тестирования на основе спецификаций, на примере технологии UniTESK.
  • Абстрагирование от архитектуры.
    Независимость тестов от внутренней архитектуры реализации становится возможной, благодаря отделению кода спецификации от медиаторов.
    В результате спецификации обладают самостоятельной ценностью. При переходе от одной архитектуры реализации к другой может потребоваться изменение медиаторов, логика же тестирования остается неизменной. Разработчику тестов для системы, работающей на специфической платформе достаточно иметь спецификации и интерфейс реализации: они однозначно определяют построение медиаторной части.
  • Конфигурационная система.
    Благодаря разделению спецификаций и реализационно-зависимых компонентов теста, возникает возможность создания гибкой конфигурационной системы, способной адекватно отражать в спецификациях неописанные варианты поведения функций. В частности, это относится к тем случаям, когда поведение тестируемой функции сильно зависит от специфики конкретной реализации (implementation-defined).
  • Генерация отчета по покрытию проверенных требований работы функции.
    Такие отчеты, помимо оценки качества тестирования, дают возможность выделить часто используемые части программы, для их возможной дальнейшей оптимизации.
  • Разделение сценариев и спецификаций.
    Такое разделение позволяет использовать одно описание функциональности тестируемой системы при ее тестировании в различных условиях.
    Например, сценарии легко дорабатываются в следующих направлениях.
  • Увеличение множества тестовых значений параметров функции.
    Это приводит к более полному тестированию функции. Обычно для минимальной полноты тестов требуется по одному набору значений параметров на каждую ветвь функциональности, определенную в спецификации данной функции.
  • Вовлечение в тестирование функции связанных с ней функций.
    Этот метод также приводит к более полному тестированию реализации функции.
    Особенно он важен при тестировании функций со скрытым состоянием, на которое можно влиять исключительно опосредованно, путем вызова других функций.

    Сравнение эффективности обходчиков UniTESK

    , Российско-Армянский (Славянский) государственный университет, Ереван, Армения
    Труды Института системного программирования РАН
    В данном разделе приводятся экспериментальные данные о сравнительной эффективности двух обходчиков CTesK. В первой его части исследовалась зависимость производительности обходчиков от порядка обращений к сценарным функциям для небольших графов. В следующих частях сравнивается эффективность работы обходчиков на разнообразных графах. Сравнивалось, в основном количество проходов по дугам графа (или же обращений к сценарным функциям), выполняемых обходчиками при построении обхода. При этом выполнение каждой отдельной сценарной функции максимально облегчено, в его ходе производился минимум действий. Иногда для наглядности приведено общее время работы теста, которое измерялось в секундах с допустимой погрешностью в 1 секунду. При сравнении использовался компьютер с процессором AMD 3200+, 1024 MB памяти.

    В настоящее время тестирование на

    В настоящее время тестирование на основе моделей получает все большее распространение. Используемые в его ходе модели могут быть более абстрактными, чем реализация, но в то же время они хорошо отражают основные особенности тестируемой системы. Относительно небольшой размер моделей позволяет реализовать их автоматическую обработку, а подобие модели тестируемой системе гарантирует, что тесты, сгенерированные на основе модели, позволят провести ее систематическое тестирование. Кроме того, тесты, построенные на основе моделей, легче сопровождать и проще переиспользовать из-за их более высокого уровня абстракции и независимости от реализации. Одним из наиболее успешных примеров применения подхода тестирования на основе моделей является технология UniTESK [,], разработанная в Институте Системного Программирования РАН. В рамках этой технологии математические модели используются для решения основных задач тестирования: оценки корректности поведения целевой системы, генерации тестовых данных, тестовых последовательностей и оценки качества тестирования. Генерация тестовой последовательности в технологии UniTESK производится на основе построения обхода графа, выступающего в качестве абстрактной модели тестируемой системы и отражающего выделенные разработчиком тестов аспекты ее поведения. Уникальной особенностью технологии UniTESK является задание графа модели в неизбыточном виде. Это означает, что перед началом тестирования о графе ничего не известно и вся информация о нем появляется только в процессе тестирования. Таким образом, граф может не только задаваться в виде статической модели тестируемой системы, но и строиться динамически, в результате наблюдений за поведением тестируемой системы во время тестирования. Извлечение модели тестируемой системы в процессе выполнения теста позволяет добиться существенного улучшения качества тестирования, масштабируемости тестового набора и упрощения переиспользования тестовых сценариев. Для обеспечения этих преимуществ все используемые алгоритмы работы с графом должны учитывать тот факт, что информация о графе появляется только во время выполнения теста. Основными понятиями технологии UniTESK являются понятия спецификации, медиатора, тестового сценария и обходчика. Спецификация представляет собой описание формальной модели тестируемой функции. Медиатор осуществляет взаимодействие спецификации (формальной модели) и реализации тестируемой функции. Тестовый сценарий представляет собой неизбыточное описание графа, моделирующего тестовую систему, и состоит из набора сценарных функций и функции вычисления текущего состояния - вершины графа. Каждая сценарная функция представляет совокупность однотипных тестовых воздействий. Обходчик, основываясь на тестовом сценарии, осуществляет перебор всех допустимых тестовых воздействий во всех достижимых состояниях задаваемого этим сценарием графа. Целью данной работы является исследование эффективности алгоритмов обходчиков, входящих в инструмент CTesK, результаты которого позволят дать рекомендации по построению тестовых сценариев в условиях ограниченных ресурсов.

    Моделирование поведения практически важной системы

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

    Зависимость работы обходчика от порядка сценарных функций

    В Таблице 1 показано количество переходов, выполняемое обходчиками для построения обхода графов из раздела 3 при различных упорядочениях сценарных функций. C в этой таблице обозначает операцию создания нового потока, K - уничтожение одного из имеющихся потоков, U - помещение функции в стек обработчиков завершения потока, O - выталкивание функции из этого стека.

    Порядок сценарных функций Простой пример (Рис. 2)
    Количество состояний - 4.
    Количество дуг - 8.Сложный пример (Рис. 3)
    Количество состояний - 7.
    Количество дуг - 13.
    ndfsm dfsm ndfsm dfsm
    CKUO 16 22 31 76
    CKOU 16 22 29 76
    CUKO 16 25 29 76
    CUOK 13 25 27 88
    COKU 16 19 30 88
    COUK 13 25 27 88
    KCUO 16 22 31 68
    KCOU 16 22 29 68
    KUCO 16 22 30 60
    KUOC 16 22 30 60
    KOCU 16 22 31 68
    KOUC 16 22 31 59
    UCKO 16 25 29 64
    UCOK 13 25 27 69
    UKCO 16 25 29 60
    UKOC 16 25 29 60
    UOCK 13 25 27 69
    UOKC 16 25 31 69
    OCKU 16 19 30 88
    OCUK 13 25 27 88
    OKCU 16 19 30 61
    OKUC 16 19 31 55
    OUCK 13 25 27 68
    OUKC 16 25 31 68
    Таблица 1. Длина строящегося обхода в зависимости от порядка сценарных функций. Таблица 1 показывает, что, меняя только порядок обращений к сценарным функциям, можно добиться следующего ускорения более 35% для dfsm (сложный пример, сравнение CUOK и OKUC) и более 10% для ndfsm (сложный пример, CKUO и CUOK). На рассмотренных примерах ndfsm демонстрирует более высокую производительность. Далее мы рассмотрим дополнительные примеры, подтверждающие это.

    Тестирование софта - статьи

    Планирование тестов

    Первый вопрос, который встает перед нами: "Сколько нужно тестов". Ответ, который часто дается: тестов должно быть столько, чтобы не осталось неоттестированных участков. Можно даже ввести формальное правило:
    Код с не оттестированными участками не может быть опубликован Проблема в том, что хотя неоттестированный код почти наверняка неработоспособен, но полное покрытие не гарантирует работоспособности. Написание тестов исходя только из уже существующего кода только для того, чтобы иметь стопроцентное покрытие кода тестами — порочная практика. Такой подход со всей неизбежностью приведет к существованию оттестированного, но неработоспособного кода. Кроме того, метод белового ящика, как правило, приводит к созданию позитивных тестов. А ошибки, как правило, находятся негативными тестами. В тестировании вопрос "Как я могу сломать?" гораздо эффективней вопроса "Как я могу подтвердить правильность?". Это наглядно демонстрирует статья 61 тест, который потряс программу. В первую очередь тесты должны соответствовать не коду, а требованиям. Правило, которое следует применять:
    Тесты должны базироваться на спецификации. Пример такого подхода можно посмотреть в статье Тривиальная задача.
    Один из эффективных инструментов, для определения полноты тестового набора — матрица покрытия.
    На каждое требование должен быть, как минимум, один тест. Неважно, ручной или автоматический. При подготовке тестового набора рекомендую начать с простого позитивного теста. Затраты на его создание минимальны. Да вероятность создания кода, не работающего в штатном режиме, гораздо меньше, чем отсутствие обработки исключительных ситуаций. Но исключительные условия в работе программы редки. Как правило, все работает в штатном режиме. Тесты на обработку некорректных условий, находят ошибки гораздо чаще, но если выяснится, что программа не обрабатывает штатные ситуации, то она просто никому не нужна. Простой позитивный тест нужен т.к. несмотря на малую вероятность нахождения ошибки, цена пропущенной ошибки чрезмерно высока. Последующие тесты должны создаваться при помощи формальных методик тестирования. Таких как, классы эквивалентности, исследование граничных условий, метод ортогональных матриц и т.д.. Тестирование накопило довольно много приемов подготовки тестов и если эти приемы создавались, то видимо было зачем.
    Последнюю проверку полноты тестового набора следует проводить с помощью формальной метрики "Code Coverage". Она показывает неполноту тестового набора. И дальнейшие тесты можно писать на основании анализа неоттестированных участков.
    Наиболее эффективный способ создания тестового набора — совместное использование методов черного и белого ящиков.

    Распределение обязанностей

    Где-то я читал следующую фразу: "Попросите программиста составить для вас (тестера) план тестов". А потом тестер будет кодировать тесты. Генеральный директор рисовать дизайн, а администратор баз данных писать руководство пользователя. Не очень воодушевляющая картина.
    Кодировать модульные тесты проще всего программисту, который пишет исходный код. Но перед этим их нужно придумать. Кодирование тестов и разработка тестовых сценариев это две разные задачи. И для последней нужны навыки дизайнера сценариев. Если программист ими обладает, то все прекрасно. Если нет, то имеет смысл поручить это тестировщику. Нужно просто четко определить решаемые задачи и навыки, необходимые для их решения.
    Задача Требуемые навыки Роль
    Определение методов обеспечения качества ПО Отличное знание теории тестрования Ведущий тестировщик проекта
    Создание тестов Хорошее знание методов тестирования Дизайнер тестовых сценариев
    Кодирование тестов Средние навыки программирования Программист автоматических тестов
    Выполнение тестов Знание среды выполнения тестов Тестер
    Вполне возможно, что роль ведущего тестировщика проекта будет выполнять аналитик или менеджер проекта, роль дизайнер тестовых сценариев — программист. А может быть и так, что все эти роли будет выполнять тестировщик.
    Не важно кто конкретно будет выполнять работу, и как будет называться должность. Главное, чтобы сотрудник обладал необходимыми навыками.

    Стратегия модульного тестирования

    Модульное тестирование является одной из ключевых практик методологии экстремального программирования. Сторонники XP приводят следующие доводы в защиту этой практики:
  • Написание тестов помогает войти в рабочий ритм
  • Придает уверенность в работоспособности кода.
  • Дает запас прочности при дальнейшей интеграции или изменениях кода.
  • Согласен, вхождение в рабочий ритм — благородная задача. Уверенность в работоспособности — тоже хорошо. Но "уверенности в работоспособности" я предпочитаю действительно работоспособный код. Пусть даже при этом я не совсем "уверен".
    Ключевой фактор при оценке перспективности любого метода — стоимость проекта. Дополнительная работа по созданию тестов, их кодированию и проверке результатов вносит существенный вклад в общую стоимость проекта. И то, что продукт окажется более качественным не всегда перевешивает то, что он будет существенно дороже.
    Известно, что продукт оптимальный по набору бюджет/функциональность/качество получается при применении различных способов обеспечения качества. Бездумное применение тотального модульного тестирования почти гарантированно приведет к получению неоптимального продукта. И никакие "запасы прочности" и "быстрый вход в рабочий ритм" не спасут проект от провала.
    На мой взгляд, модульное тестирование оправдано, если оно:
  • Снижает время на отладку
  • Дает возможность поиска ошибок с меньшими затратами, нежели при других подходах
  • Дает возможность дешевого поиска ошибок при изменениях кода в дальнейшем
  • Суммарный выигрыш от применения модульных тестов должен быть больше, чем затраты на их создание и поддержание в актуальном состоянии.
    Если в результате исправления ошибок интеграции меняется исходный код, в нем с большой вероятностью появляются ошибки. Если в результате добавления новой функциональности меняется исходный код, в нем с большой вероятностью появляются ошибки. И искать их лучше с помощью ранее созданных модульных тестов.
    Цель модульного тестирования: Получение работоспособного кода с наименьшими затратами.
    И его применение оправдано тогда и только тогда, когда оно дает больший эффект, нежели другие методы.
    Отсюда следует несколько выводов:

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

    Терминология

    Unit testing (юнит тестирование или модульное тестирование) — заключается в изолированной проверке каждого отдельного элемента путем запуска тестов в искусственной среде. Для этого необходимо использовать драйверы и заглушки. Поэлементное тестирование — первейшая возможность реализовать исходный код. Оценивая каждый элемент изолированно и подтверждая корректность его работы, точно установить проблему значительно проще чем, если бы элемент был частью системы.
    Unit (Элемент) — наименьший компонент, который можно скомпилировать.
    Драйверы — модули тестов, которые запускают тестируемый элемент.
    Заглушки — заменяют недостающие компоненты, которые вызываются элементом и выполняют следующие действия:
  • возвращаются к элементу, не выполняя никаких других действий;
  • отображают трассировочное сообщение и иногда предлагают тестеру продолжить тестирование;
  • возвращают постоянное значение или предлагают тестеру самому ввести возвращаемое значение;
  • осуществляют упрощенную реализацию недостающей компоненты;
  • Имитируют исключительные или аварийные условия. White-box testing. Для конструирования тестов используются внутренняя структура кода и управляющая логика. При этом существует вероятность, что код будет проверяться так, как он был написан, а это не гарантирует корректность логики.
    Black-box testing. Для конструирования тестов используются требования и спецификации ПО. Недостатки:
  • таким способом невозможно найти взаимоуничтожающихся ошибок,
  • некоторые ошибки возникают достаточно редко (ошибки работы с памятью) и потому их трудно найти и воспроизвести


  • Тестирование софта - статьи

    Аннотация.

    Данная статья излагает базовые принципы технологии разработки тестов UniTesK, основанной на использовании формальных моделей тестируемого программного обеспечения (ПО). Кроме того, излагается опыт использования этой технологии для тестирования ПО разных видов, описываются проблемы внедрения в промышленность, свойственные всем технологиям, основанным на формальных методах, и формулируются возможные пути их решения. Технология UniTesK была разработана в группе спецификации, верификации и тестирования [RedVerst] ИСП РАН на основе многолетнего опыта проведения проектов верификации и тестирования сложного промышленного ПО.
    Статья содержит материал, который заинтересует как исследователей в области формальных методов, так и практиков, которые хотели бы ознакомиться с потенциалом тестирования на основе моделей в реальных крупномасштабных приложениях. Читателям из последней группы рекомендуется после разделов "Основные принципы UniTesK" и "Процесс построения тестов по UniTesK" перейти к разделу "Опыт использования UniTesK", а затем выбрать те разделы, которые покажутся им интересными.

    Критерии тестового покрытия, основанные на спецификациях

    Структура программных контрактов используется в UniTesK для определения критериев покрытия спецификаций, которые необходимы, чтобы оценить качество тестирования с точки зрения требований. Для того, чтобы сделать возможным автоматическое извлечение такого рода критериев, накладываются дополнительные ограничения на структуру постусловий. А именно, вводятся дополнительные операторы для определения ветвей функциональности, расстановка которых в постусловии лежит на пользователе. Ветвь функциональности соответствует подобласти в области определения операции, в которой операция ведет себя "одинаково". Для большей определенности "одинаковым" можно считать такое поведение, при котором ограничения на результат работы операции и изменение состояния описываются для всех точек подобласти одним и тем же выражением в постусловии.
    В графе потока управления постусловия на каждом пути от входа к любому из выходов должен находиться ровно один оператор, определяющий ветвь функциональности, причем на части пути до такого оператора не должно быть ветвлений, зависящих от результатов работы операции. Тогда, во-первых, каждый допустимый вызов данной операции может быть однозначно отнесен к одной из ветвей функциональности, и, таким образом, можно измерять качество тестирования операции как процент покрытых во время теста ее ветвей функциональности. Во-вторых, определить ветвь функциональности можно по текущему состоянию компонента и набору аргументов операции, не выполняя саму операцию, что позволяет построить фильтр, отсеивающий наборы аргументов, не добавляющие ничего к уже достигнутому покрытию.
    Отталкиваясь от определения ветвей функциональности в постусловии, можно автоматически извлечь более детальные критерии покрытия, основанные на структуре ветвлений в пред- и постусловиях. Наиболее детальным является критерий покрытия дизъюнктов. Он определяется всеми возможными комбинациями значений элементарных логических формул, использованных в ветвлениях. Этот критерий является аналогом критерия MC/DC [MCDC] для покрытия кода.
    При тестировании, нацеленном на достижение высокого уровня покрытия по дизъюнктам, возможны проблемы (аналогичные проблемам, возникающим при использовании критерия MC/DC), связанные с недостижимостью некоторых дизъюнктов в силу наличия неявных семантических связей между используемыми логическими формулами. Такие проблемы решаются при помощи явного описания имеющихся связей в виде тавтологий, т.е. логических выражений, построенных из элементарных формул и являющихся тождественно истинными в силу зависимостей между значениями формул.
    Помимо возможности управлять автоматически извлекаемыми из структуры спецификаций критериями покрытия, пользователь может описать свои собственные критерии покрытия спецификаций в виде наборов предикатов, зависящих от аргументов операций и состояния, и использовать их для определения целей тестирования.

    Описание функциональных требований

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

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

    UniTesK позволяет использовать доступную извне

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

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

    Определение связи спецификаций и реализации

    Спецификации, используемые UniTesK для разработки тестов, могут быть связаны с реализацией не прямо, а при помощи медиаторов. Это делает возможной разработку и использование более абстрактных спецификаций, которые гораздо удобнее получать из требований и можно использовать для тестирования нескольких версий целевого ПО. Таким образом, тесты становятся более абстрактными и многократно используемыми. Помимо преимуществ, перечисленных в начале данного раздела, можно указать дополнительные выгоды от такого способа организации разработки теста.
  • Соответствие между требованиями, представленными в виде спецификаций, и тестами может отслеживаться полностью автоматически.
  • Поддерживается ко-верификационная разработка ПО, при которой сама целевая система и тесты к ней разрабатываются одновременно и параллельно, и сокращается общий срок разработки ПО с определенным уровнем качества.
  • Появляется поддержка для более эффективной инфраструктуры распрост-ранения готовых компонентов ПО на коммерческой основе. Для функцио-нальности, реализуемой такими компонентами можно иметь обще-доступные, один раз написанные спецификации, дополненные тестовым набором, убедительно показывающим, что компонент действительно реализует указанные функции. Разработчик компонента может сопроводить свою реализацию медиаторами, связывающими ее с общедо-ступными спецификациями, тем самым, позволяя любому пользователю или независимому тестировщику убедиться в ее правильности. Кроме того, пользователи таких компонентов могут использовать для тестирования тестовые наборы, пополненные нужным им способом.
  • Медиаторы можно разрабатывать вручную и определять, таким образом, довольно сложные преобразования между интерфейсом модели и интерфейсом реализации целевой системы. В простых случаях можно использовать шаблон построения медиаторов, который позволяет сгенерировать медиатор автоматически, указав спецификационный и реализационный компоненты, которые нужно связать, и определив соответствие между их операциями. Для каждой операции при этом нужно указать способ преобразования модельных аргументов в реализационные и реализационных результатов в модельные, если эти преобразования не тождественны.

    Опыт использования UniTesK

    Работы над инструментами для поддержки технологии UniTesK начались в конце 1999 года. К тому времени уже было проведено несколько проектов, где технология KVEST использовалась для разработки тестов и регрессионного тестирования крупных промышленных систем компании Nortel Networks, производителя цифрового телекоммуникационного оборудования. Примерами систем Nortel Networks, к которым применялся KVEST были:
  • Ядро операционной системы реального времени (размер около 250 тысяч строк, язык реализации ПРОТЕЛ, близок к С).
  • Система хранения и быстрого поиска сообщений.
  • Утилиты базисного уровня телефонных сервисов.
  • Распределенная система управления сообщениями P2P.
  • Первой была готова для использования реализация UniTesK для программ на языке C - CTesK. Упрощенный вариант инструментов для поддержки этой технологии был готов к концу 2000 года. К середине 2001 они уже активно использовались в исследовательском проекте по гранту Microsoft Research. Целью проекта было тестирование реализации стека интернет-протоколов нового поколения. IPv6. Кроме того, ставилась задача проверки пригодности UniTesK для тестирования задач нового класса - реализаций телекоммуникационных протоколов. К концу 2001 года проект был завершен. Были найдены серьезные ошибки в реализации, способные привести к нарушению работы произвольного узла в сети на основе IPv6. Поскольку данная реализация тестировалась одновременно несколькими командами тестировщиков в разных странах мира, и никто не обнаружил этих ошибок, была показана высокая результативность UniTesK. В рамках данного проекта был впервые опробован новый механизм тестирования распределенных и асинхронных систем на основе автоматных моделей в виде IOSM.
    В 2003 году был проведен пилотный проект применения CTesK для тестиро-вания ПО реального времени (алгоритмы управления навигационными устройствами, ГосНИИАС). Несмотря на сжатые сроки проекта удалось найти серьезную ошибку в алгоритме. В середине 2002 года была готова первая коммерческая версия инструмента J@T, инструмента разработки тестов для Java программ .
    Основное же их преимущество в том, что они позволяют автоматически построить оракулы [Parnas,KVEST,ADLt], проверяющие соответствие поведения целевой системы спецификациям, и критерии тестового покрытия, которые достаточно близки к критериям покрытия требований.

  • Практически невозможно обеспечить универсальный механизм построения единичных тестовых воздействий (например, вызовов операций с разными наборами аргументов), который был бы достаточно эффективен как по времени, затраченному на тестирование, так и с точки зрения достижения высокого тестового покрытия. В то же время, довольно просто построить итератор, перебирающий большое множество значений некоторого типа. Инструменты, поддерживающие UniTesK, предоставляют пользователям библиотеки базовых итераторов значений простых типов, которые могут быть непосредственно использованы для генерации тестовых воздействий, а могут быть скомпонованы в более сложные генераторы. Для уменьшения затрат времени на тестирование сгенерированные тестовые воздействия можно фильтровать, отбрасывая те и них, которые не увеличивают достигнутый уровень покрытия. Фильтры для этого генерируются автоматически из определения критерия тестового покрытия (см. пункт 6).

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


  • Стратегия тестирования определяет, когда тестирование можно заканчивать. UniTesK предлагает при этом опираться на достигнутый уровень тестового покрытия в соответствии с некоторым критерием покрытия. Из структуры спецификаций, разработанных в соответствии с технологией UniTesK, можно автоматически извлечь несколько таких критериев. Пользователь имеет возможность гибко управлять этими критериями или определять свои собственные.

  • Чтобы обеспечить более удобную интеграцию в существующие процессы разработки, UniTesK может использовать для представления спецификаций и тестовых сценариев расширения широко используемых языков программирования, построенные на основе единой системы понятий (хотя классические языки формальных спецификаций тоже могут использоваться). Такое представление делает спецификации и сценарии понятнее для обычного разработчика ПО и позволяет сократить срок освоения основных элементов технологии до одной недели. Сразу после этого обучения разработчик тестов может использовать UniTesK для получения практически значимых результатов. Кроме того, использование расширений известных языков программирования вместо специального языка значительно облегчает интеграцию тестовой и целевой систем, необходимую для проведения тестирования. На данный момент в ИСП РАН разработаны инструменты, поддерживающие работу по технологии UniTesK с использованием расширений Java, C и C#.

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


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

    Например, при тестировании блоков оптимизации в компиляторах разработка спецификаций функциональности такого блока в полном объеме, если и возможна, то очень трудоемка, поскольку они должны, например, выражать тот факт, что оптимизация программы действительно была проведена. В то же время, сравнить быстродействие и проверить неизменность функциональности тестовых программ специального вида довольно легко, выполняя их на конечном наборе входных значений, что дает способ построения оракулов, хотя и не столь общий, как описанный выше, но достаточный для практических целей [OPT].

    Подход UniTesK к разработке тестов: достижения и перспективы

    А.В. Баранцев, И.Б. Бурдонов, А.В. Демаков, С.В. Зеленов, А.С. Косачев, В.В. Кулямин, В.А. Омельченко, Н.В. Пакулин, А.К. Петренко, А.В. Хорошилов
    Труды

    Построение тестовых последовательностей

    UniTesK использует конечно-автоматные модели целевого ПО в виде тестовых сценариев для динамической генерации последовательностей тестовых воздействий. Сценарий определяет, что именно рассматривается как состояние автомата и какие операции с какими наборами аргументов должны быть вызваны в каждом состоянии. Во время выполнения теста обходчик строит некоторый "исчерпывающий" путь по переходам автомата, порождая тем самым тестовую последовательность.
    Такой метод построения теста гарантирует, что состояние системы изменяется только за счет вызовов целевых операций, и только достижимые этим способом состояния будут возникать во время тестирования. Таким образом, перебор состояний осуществляется автоматически, и разработчику теста достаточно указать только нужный способ перебора аргументов вызываемых операций.
    При разработке сценария можно использовать некоторый критерий покрытия спецификаций в качестве целевого и определить набор состояний и переходов таким образом, чтобы обход всех переходов в получившемся автомате гарантировал достижение нужного покрытия. Для этого достаточно рассмотреть набор предикатов, определяющий элементы выбранного критерия покрытия для некоторой тестируемой операции, как набор областей в пространстве состояний и аргументов этой операции, и взять проекции полученных областей на множество состояний.
    Построив все возможные пересечения таких проекций для всех тестируемых операций, мы получим набор таких множеств состояний, что, вызывая любые операции в двух состояниях из одного такого множества, можно покрыть одни и те же элементы по выбранному критерию покрытия. Следовательно, все такие состояния системы эквивалентны с точки зрения выбранного критерия покрытия, и можно объявить состоянием результирующего автомата полученные множества. Стимулами в таком автомате считаются классы эквивалентности вызовов операций по выбранному критерию покрытия, т.е. покрывающие один и тот же его элемент. Может потребоваться дополнительно преобразовать полученный автомат, чтобы сделать его детерминированным, подробности см.
    в [FACTOR].

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

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

    Сценарий может определять состояния описываемой автоматной модели, осно-вываясь не только на модельном состоянии, описанном в спецификациях, но и учитывая какие-то аспекты реализации, не нашедшие отражения в специфи-кациях. С другой стороны, можно также абстрагироваться от каких-то деталей в спецификациях, уменьшая тем самым число состояний в результирующей модели (см. [FACTOR]). Таким образом, способ построения теста может варьироваться независимо от спецификаций, и, следовательно, независимо от механизма проверки корректности поведения при единичном воздействии. Тестовые сценарии можно разрабатывать вручную, но в большинстве случаев они могут быть сгенерированы при помощи интерактивного инструмента, шаблона построения сценариев, который запрашивает у пользователя только необходимую информацию, и может использовать разумные умолчания.


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

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

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

    Без спецификаций асинхронных реакций можно тестировать системы, удовлетворяющие аксиоме чистого параллелизма (plain concurrency axiom): результат параллельного выполнения любого набора вызовов операций такой системы такой же, как при выполнении того же набора вызовов в некотором порядке. Для систем, не удовлетворяющих этой аксиоме, можно ввести дополнительные "срабатывания", соответствующие выдаче асинхронных реакций или внутренним, не наблюдаемым извне, изменениям состояния системы, таким образом, что полученная модель уже будет "чистой" (plain).

    Автоматные модели, используемые для тестирования таких систем, являются некоторым обобщением автоматов ввода/вывода [IOSMA]. При проведении тестирования "чистой" системы используется следующий метод проверки корректности ее поведения.


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

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

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

    Процесс построения тестов по UniTesK

    В приведенной ниже таблице процесс разработки спецификаций и тестов и собственно тестирования в UniTesK представлен как последовательность шагов. На практике, конечно, бывает необходимо вернуться к ранее пройденным шагам и пересмотреть уже принятые решения, но общая логика процесса - это движение от требований к получению тестов и анализу результатов тестирования.
    1. Анализ функциональных требо-ваний к целевому ПО на основе имеющихся документов или знаний участников проекта, запись требований в виде формальных спецификаций. Процесс построения тестов по UniTesK
    2. Формулировка требования к качеству тестирования - какой уровень тестового покрытия будет считаться достаточным, чтобы прекратить тестирование. Процесс построения тестов по UniTesK
    3. Разработка набора тестовых сценариев, обеспечивающего достижение нужного уровня покрытия. Сценарии разрабатываются на основе спецификаций и не привязаны к конкретной реализации целевого ПО или его конкретной версии. Процесс построения тестов по UniTesK
    4. Привязка полученных тестовых сценариев к конкретной реализации целевой системы. Для этого надо разработать набор медиаторов. Процесс построения тестов по UniTesK
    5. Получение готовой к исполне-нию тестовой программы. Для того нужно оттранслировать спецификации, медиаторы и сценарии с расширения языка программирования в целос-тную тестовую систему на целевом языке программи-рования и скомпилировать ее. Процесс построения тестов по UniTesK
    6. Выполнение тестовой прог-раммы, возможно, отладка спецификаций, медиаторов и тестовых сценариев. Процесс построения тестов по UniTesK
    7. Анализ результатов, а именн,о анализ полноты тестового пок-рытие и принятие решения и продолжении или прекра-щении тестирования. Оформ-ление описания ошибок. Процесс построения тестов по UniTesK
    Представленная ниже, на Рис. 2, схема показывает процесс получения основных компонентов архитектуры тестового набора по технологии UniTesK.
    Процесс построения тестов по UniTesK
    Рис. 2. Получение компонентов теста по UniTesK.
    Теперь рассмотрим действия, которые нужно предпринимать на разных шагах технологии более подробно.

    Сравнение с другими подходами к разработке тестов на основе моделей

    Хотя практически любая функциональная характеристика технологии UniTesK может быть найдена и в других технологиях и методах разработки тестов, иногда даже в более развитой форме, ни один из имеющихся подходов к разработке тестов, предлагаемый в академическом сообществе или в индустрии разработки ПО, не обладает всей совокупностью характеристик UniTesK.
    В кратком обзоре, помещенном в этом разделе, мы сконцентрировали внимание на методах разработки тестов, поддержанных инструментами и нацеленных на использование в промышленной разработке ПО. Таким образом, множество интересных методов и техник осталось за рамками обзора.
    Имеющиеся подходы к разработке тестов, в основном, используют стандартную, сложившуюся еще несколько десятилетий назад архитектуру теста. Тест в ней представляет собой набор тестовых вариантов (test cases), каждый из которых служит для проверки некоторого свойства целевой системы в определенной ситуации. В UniTesK тесты строятся в виде сценариев, каждый из которых, по существу, исполняет роль целого набора тестовых вариантов, проверяющих работу целевой системы при обращении к выделенной группе интерфейсов в различных ситуациях. В результате, в тестовом наборе UniTesK больше уровней иерархии, что удобно при тестировании больших и сложных систем. С другой стороны, тестовые варианты позволяют более эффективно воспроизводить нужные ситуации при повторном тестировании, например, на наличие ранее обнаруженной ошибки. Для регрессионного тестирования обе схемы пригодны в равной степени, поскольку при этом обычно требуется возможность прогона всего набора тестов.
    Автоматическое построение тестовых оракулов на основе спецификаций отличает UniTesK от инструментов, таких как JUnit [JUNIT], автоматизирующих только выполнение тестов. Вместе с тем, оно поддерживается очень многими существующими инструментами, например, следующими:
  • iContract [iContract,iContractW], JMSAssert [JMS], JML [JML,JMLW], jContractor [jContr,jContrW], Jass [Jass,JassW], Handshake [Handshake], JISL [JISL] используют контрактные спецификации, написанные в исходном коде целевой системы в виде комментариев на расширении Java (обзоры таких систем можно найти в [CCNET] и [ORA] )
  • SLIC [SLIC] позволяет оформлять контрактные спецификации на расширении C с использованием предикатов временных логик
  • Test RealTime [TRT] от Rational/IBM использует контракты и описание структуры конечно-автоматной модели целевого компонента в виде специальных скриптов
  • JTest/JContract [PARASOFT] от Parasoft и Korat [KORAT] позволяют писать предусловия, постусловия и инварианты в виде особых комментариев в Java-программах
  • ATG-Rover [TRover] использует спецификации в виде программных контрактов-комментариев на С, Java или Verilog, которые могут содержать предикаты временных логик LTL или MTL
  • Семейство инструментов ADL [ADLt] основано на расширениях С, С++, Java и IDL, которые используются для разработки контрактных спецификаций, не привязанных жестко к конкретному коду
  • T-VEC [TVEC] использует пред- и постусловия, оформленные в виде таблиц в нотации SCR [SCR]
  • От инструментов, перечисленных в первых трех пунктах, UniTesK отличает наличие существенной поддержки разработки тестов, в частности, определение критериев покрытия на основе спецификаций и механизм генерации тестовых последовательностей из сценариев.
    В инструменте JTest возможность автоматической генерации тестовых последовательностей заявлена, но генерируемые последовательности могут содержать не более 3-х вызовов операций, и строятся случайным образом, без возможности нацелить их на достижение высокого тестового покрытия.

    Инструмент Korat является одним из инструментов, разработанных в рамках проекта MulSaw [MulSaw] лаборатории информатики MIT. Он использует контракты, оформленные на JML, для генерации множества наборов входных данных одного метода в классе Java, включая и сам объект, в котором данный метод вызывается, гарантирующего покрытие всех логических ветвлений в спецификациях. Таким образом, вместо построения тестовой последовательности можно сразу получить целевой объект в нужном состоянии. С другой стороны, спецификации должны быть жестко привязаны к реализации. В частности, они не должны не допускать таких состояний целевого компонента, которые не могут возникнуть в ходе его работы, иначе много сгенерированных тестов будут соответствовать недостижимым состояниям компонента.

    Инструменты ADL предоставляют поддержку разработки тестов только в виде библиотеки генераторов входных данных, аналогичной библиотеке итераторов в UniTesK. ATG-Rover позволяет автоматически генерировать шаблоны тестовых последовательностей для покрытия спецификаций. Из доступной документации неясно, должны ли эти шаблоны дорабатываться вручную, чтобы превратиться в тестовые последовательности, но возможность такой доработки присутствует.

    T-VEC использует специальный вид спецификаций для автоматического извлечения информации о граничных значениях областей, в которых описываемая спецификациями функция ведет себя "одинаково" (ср. определение ветвей функциональности в UniTesK). Тестовые воздействия генерируются таким образом, чтобы покрывать граничные точки ветвей функциональности для данной функции. Полный тест представляет собой список пар, первым элементом которых является набор аргументов тестируемой операции, а вторым --- корректный результат ее работы на данном наборе аргументов, вычисленный по спецификациям.


    Генерация тестовых последовательностей не поддерживается.

    Кроме T-VEC, нам не известны инструменты, поддерживающие, подобно UniTesK, генерацию тестов, нацеленных на достижение высокого покрытия по критериям, построенным по внутренней структуре контрактных спецификаций. Большинство имеющихся инструментов способно отслеживать покрытие спецификаций только как процент операций, которые были вызваны. Генерация тестовых последовательностей поддерживается многими инструментами, использующими модели целевой системы в виде различного рода автоматов: расширенных конечных автоматов, взаимодействующих конечных автоматов, автоматов ввода/вывода, систем помеченных переходов, сетей Петри и пр. Такие инструменты хорошо подходят для верификации телекоммуникационного ПО, при разработке которого зачастую используются формальные языки спецификаций, основанные на перечисленных представлениях ПО --- SDL [SDL,ITUSDL,ITUSDLN], LOTOS [LOTOS], Estelle [Estelle], ESTEREL [ESTEREL,ESTERELL] или Lustre [Lustre]. Большинство этих инструментов использует в качестве спецификаций описание поведения системы на одном из указанных языков, трансформируя его в автоматную модель нужного вида.

    Часть таких инструментов использует, помимо спецификаций поведения системы, сценарий тестирования, называемый обычно целью теста (test purpose) и заданный пользователем в виде последовательности сообщений, которой обмениваются компоненты ПО (MSC), или небольшого автомата (см., например, [TPA,TPB,CADPO,TGV]). Другая часть использует явно описанные автоматные модели для генерации тестовых последовательностей, нацеленных на достижение определенного уровня покрытия согласно какому-либо критерию (см. [TorX,SDLt,EST]). UniTesK, как уже говорилось, поддерживает построение тестовых последовательностей и из заданных пользователем сценариев, и на основе автоматной модели системы, интегрируя оба подхода.

    Наиболее близки к UniTesK по поддерживаемым возможностям инструменты GOTCHA-TCBeans [UMBTG,GOTCHA] (один из инструментов генерации тестов, объединяемых в рамках проекта AGEDIS [AGEDIS,AGEDISW]), и AsmL Test Tool [ASMT,ASMTW].


    Оба они используют автоматные модели целевого ПО. Для GOTCHA-TCBeans такая модель должна быть описана на расширении языка Murphi [Murphi], AsmL Test Tool использует в качестве спецификаций описание целевой системы как машины с абстрактным состоянием (abstract state machine, ASM, см. [ASMI,ASMB]).

    Объединяет все три подхода использование разных видов моделей для построения теста, что позволяет строить более эффективные, гибкие и масштабируемые тесты, а также иметь больше компонентов для повторного использования. В UniTesK это модель поведения в виде спецификаций и модель тестирования в виде сценария, в GOTCHA-TCBeans и других инструментах проекта AGEDIS, - автоматная модель системы и набор тестовых директив, управляющих процессом создания тестов на ее основе, в последних версиях AsmL Test Tool - ASM-модель системы и множество наблюдаемых величин, наборы значений которых определяют состояния конечного автомата, используемого для построения тестовой последовательности.

    В указанных инструментах используются техники уменьшения размера модели, аналогичные факторизации в UniTesK. Инструмент GOTCHA-TCBeans может применять частный случай факторизации, при котором игнорируются значения некоторых полей в состоянии исходной модели [PROJ]. AsmL Test Tool может строить тестовую последовательность на основе конечного автомата, состояния которого получаются редукцией полного состояния машины до набора значений элементарных логических формул, используемых в описании ее переходов [FfASM].

    Основными отличиями UniTesK от GOTCHA-TCBeans и AsmL Test Tool являются поддержка расширений языков программирования для разработки спецификаций, использование контрактных спецификаций, автоматизация отслеживания покрытия спецификаций и использование фильтров для получения тестовых воздействий, нацеленных на его повышение.

    Универсальная архитектура теста

    Гибкость технологии или инструмента, возможность использовать их в большом многообразии различных ситуаций и контекстов, определяется, в первую очередь, лежащей в основе данной технологии или данного инструмента архитектурой. Архитектура теста, используемая в UniTesK проектировалась на основе опыта проведения тестирования сложного промышленного ПО. Она нацелена на решение двух основных проблем.
  • Невозможно полностью автоматизировать разработку тестов, поскольку критерии корректности целевого ПО и стратегии проведения тестирования может предоставить только человек. Тем не менее, очень многое может и должно быть автоматизировано.
  • Выбранная архитектура должна совмещать единообразие с возможностью тестирования ПО, относящегося к разным предметным областям, и в проектах, решающих различные задачи.
  • Основная идея архитектуры теста UniTesK состоит в том, что разрабатывается набор компонентов, пригодный для тестирования различных видов ПО с использованием разных стратегий тестирования. Эти компоненты должны иметь четко определенные обязанности в системе и интерфейсы для взаимодействия друг с другом. Далее, информация, которую в общем случае может предоставить только разработчик тестов, концентрируется в небольшом числе компонентов с четко определенными ролями. Для каждого такого компонента разрабатывается компактное и простое представление, создание которого потребует минимальных усилий со стороны пользователя.
    Архитектура теста UniTesK [UniArch] основана на следующем разделении задачи тестирования на подзадачи:
  • Задача проверки корректности поведения системы в ответ на единичное воздействие
  • Задача создания единичного тестового воздействия
  • Задача построения последовательности таких воздействий, нацеленной на достижение нужного покрытия
  • Задача установления связи между тестовой системой, построенной на основе абстрактного моделирования, и конкретной реализацией целевой системы Для решения каждой из этих задач предусмотрена технологическая поддержка.
    Для проверки корректности реакции целевого ПО в ответ на одно воздействие используются тестовые оракулы.
    Поскольку генерация тестовых воздействий отделена от проверки реакции системы на них, нужно уметь оценить поведение системы при достаточно произвольном воздействии. Для этого не подходит распространенный способ получения оракулов, основанный на вычислении корректных результатов для фиксированного набора воздействий. Используются оракулы общего вида, основанные на предикатах, связывающих воздействие и ответную реакцию системы.

    Такие оракулы легко строятся из спецификаций программного контракта в виде пред- и постусловий интерфейсных операций и инвариантов типов, формулирующих условия целостности данных [KVEST,ADLt]. При таком подходе каждое возможное воздействие моделируется как обращение к одной из интерфейсных операций с некоторым набором аргументов, а ответ системы на него - в виде результата этого вызова. Далее будут более детально рассмотрены специфика моделирования асинхронных реакций целевой системы и используемые техники специфицирования.

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

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

    Для построения последовательности тестовых воздействий используется конечно-автоматная модель системы. Конечные автоматы достаточно просты, знакомы большинству разработчиков и могут быть использованы для моделирования практически любой программы. Для тестирования парал-лелизма или распределенных систем используется разновидность автоматов ввода/вывода [IOSMA], в которых переходы помечаются только входным или только выходным символом.


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

    Подробнее об используемых автоматных моделях для тестирования параллелизма можно прочитать в [AsSM].

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

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


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

    Универсальная архитектура теста

    Рис. 1. Архитектура теста UniTesK.

    Рис. 1 представляет основные компоненты архитектуры теста, используемой UniTesK. В дополнение к этим компонентам тестовая система содержит несколько вспомогательных, отвечающих за трассировку хода тестирования, своевременную синхронизацию состояний между модельными и реализа-ционными объектами, и пр. Эти вспомогательные компоненты не зависят от тестируемого ПО и выбранной стратегии тестирования.

    Универсальное расширение языков программирования

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

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

    Java
    specification package example; class SqrtSpecification { specification static double sqrt ( double x ) reads x, epsilon { pre { return x >= 0; } post { if(x == 0) { branch "Zero argument"; return sqrt == 0; } else { branch "Positive argument"; return sqrt >= 0 && Math.abs((sqrt*sqrt-x)/x) Декларация
    пакета Декларация класса Сигнатура операции Описание доступа
    на чтение/запись Предусловие Постусловие Определение ветви
    функциональности Ограничения
    на результат Определение ветви
    функциональности Ограничения
    на результат
    C#
    namespace Examples { specification class SqrtSpecification { specification static double Sqrt ( double x ) reads x, epsilon { pre { return x >= 0; } post { if(x == 0) { branch ZERO ("Zero argument"); return $this.Result == 0; } else { branch POS ("Positive argument"); return $this.Result >= 0 && Math.Abs( ($this.Result * $this. Result - x)/x) < epsilon; } } } } } Декларация
    пространства имен Декларация класса Сигнатура операции Описание доступа
    на чтение/запись Предусловие Постусловие Определение ветви
    функциональности Ограничения
    на результат Определение ветви
    функциональности Ограничения
    на результат
    C
    specification double SQRT ( double x ) reads x, epsilon { pre { return x >= 0.; } coverage BRANCHES { if(x == 0) return(ZERO, "Zero argument"); else return(POS, "Positive argument"); } post { if(coverage(BRANCHES)==ZERO) return SQRT == 0.; else return SQRT >= 0. && abs((SQRT*SQRT - x)/x) < epsilon; } } } Сигнатура операции Описание доступа
    на чтение/запись Предусловие Описание
    структуры тестового
    покрытия Определение ветви
    функциональности Определение ветви
    функциональности Постусловие Ограничения
    на результат Ограничения
    на результат


    В настоящее время промышленное производство

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

    Выполнение тестов и анализ их результатов

    Инструменты UniTesK поддерживают автоматическое выполнение тестов, разработанных с их помощью, и автоматический сбор трассировочной информации. После окончания работы теста на основе его трассы можно сгенерировать набор дополнительных тестовых отчетов. Эти отчеты показывают структуру автомата, выявленную в ходе тестирования, уровень достигнутого тестового покрытия для всех критериев, определенных для некоторой спецификационной операции, и информацию об обнаруженных в ходе теста нарушениях, связанных с ошибками в целевой системе или с ошибками в спецификациях, сценариях и медиаторах.
    Трасса теста может служить для получения дополнительной информации, например, о зафиксированных нарушениях. Так, из трассы можно узнать вид нарушения, значения аргументов вызова операции, при выполнении которого это нарушение было обнаружено, какое именно ограничение в постусловии было нарушено, и т.д. Представленная в трассе и других отчетах информация достаточна, как для отладки тестовой системы, так и для оценки качества тестирования и, зачастую, для предварительной локализации обнаруженных ошибок.
    Ниже приведены примеры отчетов, которые инструмент J@T, поддержи-вающий технологию UniTesK для программ на Java, автоматически формирует на основе трассы теста.
    Выполнение тестов и анализ их результатов
    Рис. 3. Описание найденной ошибки
    Выполнение тестов и анализ их результатов
    Рис. 4. Отчет о тестовом покрытии.

    Открытые проблемы и направления дальнейшего развития

    Опыт разработки и использования UniTesK, как и работы других исследователей в области тестирования на основе моделей, показывает, что данное направление является весьма перспективным. Каковы же главные проблемы, которые мешают более широкому распространению тестирования на основе моделей? Их можно разделить на три основные группы:
  • методические - как разрабатывать спецификации и тесты?
  • технические - как унифицировать методы разработки спецификаций и тестов и инструменты, которые поддерживают эти работы?
  • организационные - как внедрить новые методики и инструменты в реальные процессы разработки ПО?
  • Сложность успешного решения перечисленных проблем возрастает от первой группы (методика) к последней (организационная перестройка). В подтверждение этого тезиса можно сослаться на опыт многих групп, использующих методы тестирования на основе спецификаций в промышленных приложениях. За исключением единичных случаев всегда удается найти подходящий способ моделирования, который дает хорошие результаты как в плане качества тестирования, так и плане достижения хороших стоимостных показателей. Тем самым показано, что неразвитость теории и методики не является сдерживающим фактором распространения данного подхода.
    Сейчас еще нет общего согласия по поводу нотаций и методов, при помощи которых ведется разработка моделей и тестов на их основе, нет унифицированной, общепринятой архитектуры тестирующей системы , отдельные инструменты для статического и динамического анализа программ и представления результатов анализа еще далеки от унификации. Тем не менее потребность в унификации уже назрела. Наиболее известным продвижением в области унификации является разработка документа UML Testing Profile [UML-TP], подготовленного в рамках программы MDA (Model Driven Architecture, [MDA]). Вместе с тем следует отметить, что авторы UML Testing Profile, возможно, пытаясь найти компромисс пригодный для всех участников консорциума, предлагают архитектуру, нацеленную больше на ручную разработку тестов.
    Этот подход в некоторой степени может быть использован при разработке тестов на основе спецификаций сценариев использования, но он становится ограничивающим фактором, если в качестве модели целевого ПО берутся автоматные модели или спецификации ограничений, разработанные по методу Design-by-Contract. Следствием такого одностороннего подхода является непонимание потенциальных преимуществ использования спецификаций ограничений, и, в частности, языка OCL (Object Constraint Language), в промышленном программировании. Тем самым, проблема унификации представляется не только технической. Важно найти унифицированный подход, который позволил бы на базе единой концепции разрабатывать тесты на основе разных видов моделей с использованием ручных, автоматизированных и автоматических технологий.
    Организационные проблемы на данный момент являются наиболее острыми [Robinson, Manage]. Как уже отмечалось выше, новая технология тестирования должна достаточно хорошо интегрироваться с имеющимися процессами разработки, и не требовать долгой и дорогой переподготовки персонала. Есть ли шанс найти для методов разработки тестов на основе моделей возможности сочетаться с традиционными методами разработки таким образом, что это позволит ввести новые технологии без ломки уже сложившихся процессов?
    По-видимому, абсолютно безболезненным внедрение не будет, и причиной этого являются не столько технические проблемы, сколько проблемы персонала, причем как технического, так и управляющего. В настоящее время при промышленной разработке ПО широко используется подход к разработке тестов и тестированию как вспомогательной деятельности, не требующей навыков программирования, соответственно, в качестве тестировщиков используется, в основном, персонал без знания языков программирования и без базовых программистских умений. Пытаясь упростить проблемы организации взаимодействия команд разработчиков и тестировщиков, руководители проектов ориентируют тестировщиков только на системное тестирование, что влечет практическое отсутствие модульного и компонентного.


    В этом случае появляется иллюзия, что тестировщики, имея большой запас времени, могут создать качественные тесты. В реальности же разработка обычно затягивается, и к моменту приемо-сдаточных испытаний система в состоянии продемонстрировать работоспособность на ограниченном количестве наиболее вероятных сценариев, но нуждается в серьезных доработках на большинстве нестандартных сценариев использования. Очевидно, что "сломать" негативные тенденции нельзя, они могут быть изжиты только вследствие эволюционных изменений. Сейчас такими эволюционными шагами могут стать дополнительные работы, которые поручаются проектировщикам и разработчикам. Эти дополнительные работы войдут в обиход реальных процессов разработки только тогда, когда их выполнение окажет положительный эффект на основную деятельность проектировщиков и программистов. Поэтому сейчас средства автоматизации тестирования на основе моделей имеет смысл вписывать в среду разработки, тем самым привлекая к тестированию не только профессиональных тестировщиков, но и разработчиков.
    Для того чтобы расширить сообщество пользователей методов тестирования на основе моделей надо развивать формы обучения этим методам. Все инструменты UniTesK сопровождаются не только обычной пользовательской документацией, но и материалами для обучения. Предлагаются две формы обучения: традиционная университетская форма и форма интенсивного тренинга. Университетский курс требует от 15 до 30 часов занятий, тренинг требует 3-4 учебных дня по 8 академических часов каждый день. Материалы для университетов также как и лицензии на инструменты UniTesK для использования в образовательных целях предоставляются бесплатно. Также бесплатно предоставляются примеры спецификаций и тестов для разнообразных приложений. Основной стратегической целью дальнейших работ над UniTesK является построение полномасштабных индустриально применимых технологий тестирования на основе моделей для всех видов приложений, разрабатываемых в промышленности.
    Для успешного развития технологий тестирования на основе моделей нужно одновременно поддерживать их эволюцию в трех направлениях: наращивать функциональность технологий, наращивать сопряженность этих технологий с современными процессами разработки и наращивать удобство их использования.


    Особенно важно увеличивать удобство использования технологий для решения наиболее часто встречающихся задач.
    Исследования по расширению области применимости подхода к тестированию на основе моделей должны проводиться по целому ряду направлений
  • Разработка полномасштабных технологий тестирования для всех компонентов таких видов приложений, как компиляторы, интерпретаторы, СУБД и др., функции которых связаны с обработкой запросов на хорошо структурированных формальных языках.
  • Автоматизация тестирования приложений через графический пользовательский интерфейс.
  • Разработка технологий полномасштабного тестирования распределенных систем, из которых наиболее широко встречаются сейчас многоуровневые приложению в архитектуре клиент-сервер приложений-сервер данных.
  • Тестирование компонентов, имеющих очень сложную функциональность и очень ограниченный интерфейс: планировщики задач, сборщики мусора, менеджеры транзакций и пр.
  • Тестированию приложений с элементами искусственного интеллекта: распознавание образов, выбор стратегии достижения цели в неопределенной ситуации, нечеткая логика, интеллектуальные агенты, и пр. Помимо решения задач собственно тестирования для привлечения внимания к технологиям тестирования на основе моделей необходимо искать подходящие метрики качества ПО, которые позволили бы продемонстрировать преимущества этого подхода. На данный момент многие метрики, используемые в промышленности для оценки качества ПО и состояния процесса тестирования, подходят для традиционной разработки тестов вручную, но приводят к парадоксам при попытке их использования в проектах, где тесты строятся автоматизировано на основе моделей. Кроме того, необходимо искать пути повышения прозрачности связей между используемыми моделями и требованиями к ПО, а также метрики переиспользуемости, позволяющие объективно оценить влияние изменений в требованиях к ПО, в требованиях к качеству тестирования, в архитектуре целевого ПО, в используемых технологиях разработки и пр. на изменения в тестах.Разработка и апробация таких метрик в промышленных проектах позволит повысить значимость технологий, нацеленных на повышение качества ПО, что, в свою очередь придаст новый импульс работам по автоматизации тестирования и по тестированию на основе моделей.

    Тестирование софта - статьи

    Аннотация. В статье рассматриваются

    Грид (англ. grid ? решетка, сеть) ? это согласованная, открытая и стандартизованная компьютерная среда, которая обеспечивает гибкое, безопасное и скоординированное использование вычислительных ресурсов и данных. Термин “Грид” появился в начале 1990-х гг. в сборнике “TheGrid: Blueprint for a new computing infrastructure” под редакцией Яна Фостера как метафора о такой же легкости доступа к вычислительным ресурсам, как и к электрической сети (англ. power grid). Создание Грид-систем было продиктовано необходимостью повышения вычислительных мощностей ресурсов. Так как требования к точности получаемых результатов и скорости работы вычислительных комплексов неуклонно растут, то возникает вопрос: как удовлетворить таким требованиям с наименьшими затратами? Один из возможных способов разрешения данной проблемы заключается в объединении различных ресурсов в одну систему. Причем необходимо, чтобы “объединенный” ресурс работал как единое целое даже при отсутствии централизованного управления. Не должна также оказывать никакого отрицательного влияния на работоспособность системы и разнородность ресурсов, хотя особенности каждого из ресурсов также должны учитываться (для оптимизации времени выполнения). Что же касается конечного пользователя, то он может и не иметь никакого понятия о том, как именно устроена система. Зато предполагается возможность коллективного разделяемого режима доступа к ресурсам и связанным с ними услугами в рамках глобально распределенных виртуальных организаций, состоящих из предприятий и отдельных специалистов, совместно использующих общие ресурсы. Согласно Яну Фостеру, Грид-система (далее ГС) ? это система, которая:
  • координирует использование ресурсов при отсутствии централизованного управления этими ресурсами;
  • использует стандартные, открытые и универсальные протоколы и интерфейсы;
  • нетривиальным образом обеспечивает высококачественное обслуживание. ГС, следуя определению, является универсальной инфраструктурой обработки и хранения распределенных данных, в которой функционируют различные службы, называемые Грид-сервисами. Последние не только позволяют решать конкретные задачи, но и могут предоставлять определенные услуги, например поиск ресурсов, сбор информации об их состоянии, хранение и доставка данных. Ясно также, что из определения проистекает высокая специфичность соответствующего ПО ГС. Особого внимания заслуживает второй пункт определения. В нём указано, что различные ГС могут и должны поддерживать стандартные протоколы и интерфейсы, несмотря на расхождения в архитектуре или особенности реализации. Иными словами, любая ГС должна соответствовать определенному набору стандартов. Цель стандартизации ГС ? обеспечить переносимость вычислительных приложений между различными Гридами, в том числе построенных на различных инфраструктурных программных пакетах.

    Обзор целевой системы

    Объектом тестирования в данной работе является программный пакет GlobusToolkit 4.2. Ниже перечислены основные компоненты данного инструментария: 1) Компоненты поддержки времени выполнения (Common runtime components):
  • C Core Utilities - обеспечение переносимости;
  • C WS Core ? поддержка разработки Web-сервисов и выполнения клиентских приложений на C;
  • Java WS Core ? поддержка разработки Web-сервисов и выполнения клиентских приложений на Java;
  • CoG jglobus ? поддержка безопасности и выполнения не Web-сервисной части Java;
  • Core WS Schema ? поддержка схемы стандартов WSRF и WSN;
  • Python Core ? разработка и исполнение WS и не-WS клиентских приложений на языке Python;
  • XIO ? расширяемые библиотеки ввода-вывода на С. 2) Управление данными (Data Management):
  • GridFTP ? протокол передачи файлов;
  • OGSA-DAI ? инфраструктура, основанная только на java-сервисах, для получения доступа к ресурсам и интеграции их в Грид;
  • Reliable File Transfer ? технология надежной передачи файлов, основанная на Web-сервисах;
  • Replica Location ? технология копирования и обнаружения данных;
  • Data Replication ? технология идентификации групп файлов в среде Грид, и их локального копирования. 3) Управление выполнением (Execution Management):
  • GRAM ? обнаружение местоположения, инициализация выполнения, наблюдение за работой и завершение удаленных задач на Грид-ресурсах;
  • GridWay ? коллективное использование вычислительных ресурсов;
  • MPICH-G2 ? реализация стандарта MPI . 4) Информационные сервисы (Information Services):
  • MDS4 ? технология для слежения за ресурсами и их поиска, включающая сервисы индексирования и триггеры. 5) Компоненты безопасности (Security):
  • C Security ? технология поддержки безопасности;
  • CAS\SAML Utilities ? технология, относящиеся к авторизации сообществом;
  • Delegation Service ? технология, предоставляющая хосту учетные данные;
  • GSI-OpenSSH ? модифицированная версия OpenSSH, которая поддерживает аутентификацию сертификатов и их предоставление, удаленный зарегистрированный доступ и сервис передачи файлов;
  • MyProxy ? технология хранения и извлечения учетных данных из репозитория. Остановимся на некоторых принципиальных особенностях архитектуры Globus Toolkit 4.2. Сервисно-ориентированная архитектура.
    Globus Toolkit 4. 2 создан для поддержки приложений, в которых множества сервисов взаимодействуют посредством стандартных протоколов. ПО включает и сами сервисы полностью, и библиотеки, реализующие стандартные протоколы. Сервисы инфраструктуры. Globus Toolkit 4.2 включает встроенные сервисы для организации, наблюдения, управления и контроля доступа к таким элементам инфраструктуры, как ресурсы данных и вычислительные ресурсы. Web-сервисы. Globus Toolkit 4.2 использует протоколы стандартных Web-сервисов и механизмы описания сервисов, обнаружения, контроля доступа, аутентификации и авторизации. Контейнеры. ПО Globus Toolkit 4.2 включает компоненты, которые могут быть использованы для конструирования контейнеров для “помещения” в них Web-сервисов, написанных на Java, C, или Python. Безопасность. Подсистема безопасности выполняет задачи защиты сообщений, аутентификации, авторизации и передачи полномочий. Компоненты. Компоненты Globus Toolkit 4.2 не отвечают, вообще говоря, нуждам конечного пользователя напрямую: большинство из них выступает скорее как TCP\IP библиотека или реализация Web-сервера, чем как Web-браузер. Вместо этого, Globus Toolkit 4.2 предоставляет широкий диапазон компонент и инструментов, которые обеспечивают высокоуровневые возможности, необходимые определенным сообществам пользователей. Согласно утверждению Globus Alliance, компонент Globus Toolkit 4.2 Java WS Core реализует требования стандарта WSRF. В работе задача тестирования соответствия решалась именно для этого компонента. В рамках Java WS Core Web-сервис ? это просто Java-объект, а поддерживаемые им обмены сообщениями соответствуют методам класса. Для вызова методов класса следует посылать сервисам XML-сообщения по протоколу SOAP/HTTP. Стоит также заметить, что в реализации возможны передача сообщений как с применением шифрования, так и без него.

    Опыт практического тестирования реализации ИПО Грид

    Представленный выше метод к разработки тестовых наборов для ИПО Грид использовался при создании тестового набора для проверки соответствия реализаций ИПО Грид стандарту WSRF.

    Применение технологии UniTESK для функционального тестирования инфаструктурного ПО Грид

    ,
    Труды Института системного программирования РАН

    Разработка формальной спецификации

    Каждый обмен сообщениями в WSRF представляет собой пару <запрос ? ответ> где в качестве ответа может быть сообщение с возвращаемым значением или сообщение об ошибке. Тем самым обмены сообщениями WSRF можно представить как вызовы функций с возвращаемым значением, а сообщения об ошибках моделировать как исключения. Благодаря этому в рассматриваемом методе формализации требований к ИПО Грид Web-сервис моделируется как объект некоторого класса, а обмены сообщениями между клиентом ИПО и реализацией представляются как вызовы методов объекта. Модельное состояние сервиса формализуется как набор полей объекта. Такой способ моделирования неявно подразумевает синхронный сценарий взаимодействия между клиентом и сервисом: после отправки запроса клиент ожидает ответа. Не допускаются сценарии, в которых клиент может послать серию запросов, не дожидаясь ответов. Однако, анализ сценариев использования Грид-систем показал, что большинство современных клиентских приложений разрабатываются в синхронной парадигме. Более того, сам стандарт WSRF описывает семантику поведения сервиса в синхронном стиле. По этим причинам в рассматриваемом методе к формализации требований мы моделируем обмены сообщениями между клиентом и ИПО Грид как процедурные вызовы. В модельное состояние ресурса необходимо включить переменные для представления свойств ресурса и окружения:
  • EndpointReference ? модель идентификатора WS-ресурса, т.н. ссылка на конечную точку, удовлетворяющая требованиям стандарта WS-Addressing адресации Web-сервисов;
  • ResourcePropertyDocument ? модель RPD WS-ресурса как множества элементов, моделирующие отдельные свойства (properties) ресурса;
  • набор параметров модели, учитывающих отсутствие поддержки ряда необязательных обменов сообщениями в реализации;
  • CurrentTime ? переменная, определяющая текущее системное время;
  • TerminationTime ? переменная, определяющая время прекращения существования WS-ресурса. Наличие первого поля определяется требованием спецификации WS-Resource об идентифицируемости.
    Что же касается второго и третьего пунктов, то на них стоит остановиться поподробнее. Свойство ресурса как элемента RPD в спецификации моделируется классом со следующими полями: идентификатор-имя свойства (QName), значение свойства и минимальное количество его включений в RPD. Наличие последнего поля продиктовано рядом требований стандарта, касающихся тех свойств WS-ресурса, у которых оно является нулевым, т.е. наличие этого свойства, вообще говоря, необязательно. Что касается третьего пункта, то его наличие связано со спецификой организации стандарта WSRF 1.2. Дело в том, что требования стандарта, находящиеся даже в одной и той же его части, совершенно необязательно имеют один и тот же “ранг” в градации RFC 2119, т.е. ключевые слова в них различные. В Таблице 1 изображено распределение требований по частям стандарта в зависимости от их “ранга”:
    WS-BaseFaults WS-Resource WS-Resource Properties WS-Resource Lifetime WS-ServiceGroup
    MUST 17 6 115 30 41
    SHOULD 1 0 12 8 15
    MAY 11 6 32 13 17
    Таблица 1. Распределение требований по отдельным частям стандарта WS-RF. Таким образом, в спецификации было формализовано около половины всех требований стандарта WSRF (порядка 160 требований), и порядка 60% содержащихся в частях WS-Resource, WS-ResourceLifetime и WS-ResourceProperties стандарта WSRF требований. Реализация остальных требований осуществлялась в медиаторе. Стоит также упомянуть о полях CurrentTime и TerminationTime класса WSResourceSpecification. Они используются для формализации требований “времени жизни” из спецификации WS-Resource, а также с требований спецификации WS-ResourceLifetime. Сигнатура метода, моделирующего отдельный обмен сообщений стандарта WSRF, определяется структурой сообщений запросов и ответов. Запросы представляются в виде набора параметров спецификационного метода, ответы представляются как возвращаемые значения метода, а сообщения об ошибках моделируются исключениями. Всё множество требований можно разделить на две основные группы: синтаксические и функциональные.


    Синтаксические требования налагают ограничения на структуру сообщений и связи между полями одного сообщения. Функциональные требования представляют собой ограничения на функциональность обработки запросов и связь между содержимым запроса и ответом на запрос.
    WS-BaseFaults WS-Resource WS-Resource Properties WS-Resource Lifetime WS-ServiceGroup
    Синтаксис 27 1 50 12 5
    Семантика 2 11 109 39 68
    Таблица 2. Соотношение между синтаксическими и семантическими требованиями стандарта WS-RF. Функциональные требования к реализациям WSRF представляют собой описания простых операций, таких как изменение значения поля ресурса, добавление или удаление свойств (операции с множеством), детерминированных и полностью определенных. Такие требования в рамках предлагаемого метода моделируются в явном виде с использованием блоков update, поддерживаемых в реализации UniTESK ? инструменте JavaTESK. update-блок содержит инструкции, которые явным образом изменяют значения полей модельного состояния в соответствии с текстом требования стандарта. Синтаксические требования мы предлагаем проверять в медиаторах на этапе разбора сообщений, полученных от целевой системы.

    Разработка медиатора

    Основная функция медиатора, как компонента тестового набора, ? это установление соответствия между модельным объектом (объектом спецификационного класса) и целевой системой. В случае тестирования ИПО Грид возможность непосредственного доступа к полям и методам системы отсутствует, потому воздействовать на ГС можно было лишь посредством отправки соответствующих сообщений-запросов, а реакции принимать ? обрабатывая отклики. Соответственно, главной функцией медиатора является генерация сообщений-запросов и разбор сообщений-откликов с целью выявления полезной информации о выполнении операции. В рассматриваемом методе медиаторы преобразуют параметры вызова спецификационного метода в XML сообщение, преобразуют в сообщение протокола SOAP/HTTP и отсылают его в целевую систему по установленному TCP соединению. Полученные ответы медиатор разбирает, проверяет соответствие ответа синтаксическим требованиям и формирует возвращаемое значение спецификационной функции. Одна из особенностей разработки медиаторов для ИПО Грид заключается в том, что существующие реализации (в частности, GlobusToolkit 4.2) не удовлетворяют синтаксическим требованиям стандарта. Для проведения тестирования потребовалась адаптация медиаторов под нарушения стандарта, допускаемых реализацией. К примеру, Globus Toolkit 4.2 реализует один из последних предварительных проектов стандарта (draft) и поддерживает устаревшее пространство имен XML элементов (namespace); в результате реализация отвергала запросы, сформированные в соответствии со стандартом. Также путем анализа исходного кода ИПО ГС Globus Toolkit 4.2, а также ряда экспериментов было выяснено, что реализацией не поддерживаются следующие обмены сообщениями ? PutResourceProperties, SetResourceProperties.

    Разработка тестового сценария

    Следующим этапом разработки тестового набора являлась разработка тестовых сценариев. Тестовым сценариям в технологии UniTESK отводится немаловажная роль. Как уже указывалось ранее, для их построения используется конечно-автоматная модель целевого ПО. При таком выборе модели актуальны ответы на следующие вопросы:
  • Как вычислять состояния тестируемой системы?
  • Как осуществляются переходы между состояниями?
  • Как задается автомат теста и как производится обход графа переходов автомата? Ответ на первый вопрос не может быть дан, исходя только из общих соображений. Для каждой целевой системы состояние должно быть смоделировано с учётом её особенностей и требований, предъявляемых к качеству тестового набора вообще. В случае ИПО Грид в качестве объекта тестирования выступает WS-ресурс. Основной его характеристикой выступает, очевидно, RPD, как наиболее полно описывающая ресурс сущность. Следовательно, RPD может быть предъявлен в качестве описателя состояния WS-ресурса. Однако, такой подход является избыточным, а тестовые сценарии на его основе, вообще говоря, могут выполняться неопределенно долгое время. Действительно, представим себе, что WS-ресурс обладает n свойствами, каждое из которых может принимать только значение “1”, и пусть изначально его RPD пуст. Рассмотрим конечный автомат, построенный на основе такого ресурса, причем в качестве модели состояния будем использовать его RPD, а в качестве переходов ? два метода: add и remove, обозначающие, соответственно добавление нового свойства и удаление некоторого старого. Тогда из начального состояния автомат сможет перейти в n состояний, соответственно, образуется 2n дуг на графе состояний. Из состояний “ранга 1” (т.е. таких, при которых RPD WS-ресурса содержит только одно свойство), которых ровно, в состояния “ранга 2” уже будет переходов. Из состояний “ранга 2” в состояния “ранга 3”, которых имеется переходов уже будет при больших n. Это означает, что граф состояний будет иметь вершин, а число ребер ? будет расти экспоненциально с ростом n, что приведет к крайне медленной работе обходчика а, следовательно, и замедлит работу тестов.
    Другим важным недостатком такого подхода является его избыточность. Действительно, с точки зрения тестирования методов add и remove совершенно необязательно обходить все ребра такого графа (т.к. все свойства такого WS-ресурса с “точки зрения” данных методов эквивалентны). Достаточно обработать лишь некоторые (например, ребра, идущие из состояния, в котором все n свойств WS-ресурса равны 1 и ребра, идущие из начального состояния). Потому в ряде случаев представляется разумным выбирать другую модель состояния тестируемой системы. При разработке тестового сценария для тестирования соответствия ИПО Грид стандарту WSRF в качестве идентификатора состояния автомата теста мы предлагаем использовать мощность RPD WS-ресурса, т.е. количество свойств WS-ресурса. С одной стороны, такой способ существенно уменьшает размер предполагаемого графа состояний а, следовательно, и время работы тестов. С другой стороны, при таком задании состояния сохраняется детерминированность графа, что принципиально для корректной работы обходчика UniTESK. Ответ на второй вопрос традиционен для большинства приложений UniTESK ? переходы между состояниями автомата осуществляются посредством подачи стимулов в целевую систему. В качестве стимулов в случае построения тестового набора для ИПО Грид выступают, естественно, вызовы медиаторных методов. Последние же представляют собой не что иное, как отправки соответствующих XML-сообщений целевому ресурсу. Теперь перейдем к обсуждению третьего вопроса ? заданию автомата теста. Так как используется синхронная модель целевой системы, то для каждого спецификационного метода можно задать отдельный сценарный метод, который будет перебирать параметры метода и вызывать (неявно) медиатор для оказания тестового воздействия и оракул для проверки правильности возвращаемого значения.

    Регламентирующие документы и требования к реализациям ИПО Грид

    Как упоминалось во введении, в настоящее время при разработке ИПО Грид используются два семейства стандартов: OGSA и WSRF. Рассмотрим особенности формализации требований указанных стандартов. При изучении стандарта OGSA оказалось, что:
  • стандарт носит описательный характер, требования нечётко выражены;
  • стандарт практически не содержит функциональных требований;
  • в стандарте не приводятся описания форматов сообщений и протоколов удаленного обращения к сервисам;
  • стандарт признан устаревшим по сравнению с новыми подходами к организации удаленных вызовов, основанных на Web-сервисах. По этим причинам стандарт OGSA не подходит в качестве основы для разработки формальных спецификации и основанного на них тестового набора. Стандарт WSRF больше подходит для формализации. Стандарт содержит 5 спецификаций:
  • WS-BaseFaults ? определяет формат сообщений об ошибках и механизм их обработки;
  • WS-Resource ? определяет само понятие WS-Resource, форматы сообщений и семантику сервисов управления ресурсом;
  • WS-ResourceLifetime ? определяет механизмы прекращения существования WS-Resource;
  • WS-ResourceProperties - определяет, как WS-Resource связан с интерфейсом, описывающим Web-сервис, а также позволяет извлекать, изменять и уничтожать свойства WS-ресурса;
  • WS-ServiceGroup ? определяет интерфейс к набору гетерогенных Web-сервисов. Рассмотрим понятие ресурса (Resource) и WS-ресурса (WS-Resource), введенных в WS-Resource. Ресурс ? это логическая сущность, обладающая следующими характеристиками:
  • идентифицируемость;
  • наличие “времени жизни”;
  • наличие множества (быть может пустого) свойств, представимых в XML InfoSet . Теперь перейдем к понятию WS-ресурса. WS-ресурс представляет собой композицию ресурса и Web-сервиса, посредством вызова методов или полей которого осуществляется доступ к ресурсу. WS-ресурс также должен быть идентифицируемым (роль идентификатора должен играть XML элемент типа EndpointReference, описание которого содержится в стандарте WS-Addressing), а множество свойств ресурса, ассоциированного с сервисом, ? представимым в XML InfoSet. Стандарт WSRF весьма удобен для анализа по причине его структурированности.
    Так, например, большая их часть функциональных требований снабжена ключевыми словами стандарта RFC 2119 ? MUST, SHOULD, MAY. Кроме того, большинство требований снабжено блоками объяснений и примерами, написанными на псевдокоде, имеющем много общего с языком WSDL 2.0 описания Web-сервисов. Отдельные части стандарта имеют различные уровни обязательности в градации RFC 2119. А именно: WS-ресурс обязан (MUST) реализовывать требования спецификаций WS-Resource и WS-ResourceProperties, ему следует (SHOULD) руководствоваться требованиями WS-BaseFaults и он может (MAY) удовлетворять требованиям WS-ResourceLifetime. Следовательно, при создании тестового набора наиболее пристальное внимание нужно уделить именно первым двум обязательным спецификациям. Действительно, такой подход существенно упрощает как структуру тестового набора, так и его размер, при этом сохраняя корректность тестов как решения задачи соответствия. Итак, спецификация WS-Resource содержит определения ресурса и WS-ресурса. Также она содержит описания двух сообщений об ошибках, которые WS-ресурс может (MAY) возвращать в ответ на запросы. Что же касается спецификации WS-ResourceProperties, то в ней описан следующий способ хранения свойств WS-ресурса. Каждое свойство WS-ресурса представляет собой XML-элемент, полями которого являются уникальное имя свойства (в терминах спецификации WS-ResourceProperties ? QName), значение свойства и, возможно, служебные параметры. Все свойства ресурса объединены в некоторую композицию, называемую Resource Property Document (далее RPD), имеющую собственный идентификатор. Иными словами, свойства ресурса являются дочерними элементами по отношению к некоторому корневому элементу ? фактически, идентификатору RPD. Вместе с тем не указывается, каким именно образом сервис должен реализовывать свой RPD. В спецификации WS-ResourceProperties описаны следующие обмены сообщениями:
  • GetResourcePropertyDocument ? получение всех свойств WS-ресурса;
  • GetResourceProperty ? получение определенного свойства WS-ресурса;
  • GetMultipleResourceProperties ? получение нескольких свойств WS-ресурса;
  • QueryResourceProperties ? выяснение структуры свойств WS-Resource и выполнение запросов к RPD и вычислений над его элементами (отдельно указывается диалект, на котором записано вычисляемое выражение, примером такого диалекта может служить язык XPath);
  • PutResourcePropertyDocument ? замена всего RPD WS-ресурса “новым” RPD;
  • SetResourceProperties ? изменение нескольких свойств WS-ресурса (фактически, некоторая композиция следующих трех обменов сообщениями);
  • InsertResourceProperties ? добавление новых свойств WS-ресурса;
  • UpdateResourceProperties ? изменение значений свойств WS-ресурса;
  • DeleteResourceProperties ? удаление свойств WS-ресурса. В ходе анализа стандартов были выделены 325 функциональных требований, из них 29 относятся к спецификации WS-BaseFaults, 12 ? к WS-Resource, 51 ? к WS-ResourceLifetime, 159 ? к WS-ResourceProperties, 73 ? к WS-ServiceGroup.

    Результаты тестирования

    В данной работе была протестирована реализация ИПО ГС Globus Toolkit 4.0 средствами технологии UniTESK (JavaTESK). В качестве результатов тестирования выступали сгенерированные инструментом отчеты о покрытии требований. Всего было покрыто 60% функциональности, в чём данный тестовый набор не уступает тестам, применяемым разработчиками Globus Toolkit'а; последние для оценки качества тестирования измеряют покрытие тестами кода с помощью инструментов JUnit и Clover, и данные инструменты позволили разработчикам установить, что их юнит-тесты покрывают 60% функциональности компонента Java WS Core. Однако, как уже говорилось ранее, эти тесты ничего не могут сообщить о соответствии реализации Globus Toolkit 4.0 какому-либо стандарту. Таким образом, существующие и разработанные тесты не только дополняют друг друга, но и позволяют с разных точек зрения рассматривать реализацию. В разработанный тестовый набор не вошли спецификации и тесты для необязательных требований спецификации WS-ServiceGroup и требования к операции QueryResourceProperties. При тестировании реализации были выявлены несоответствия семантическим требованиям стандарта WSRF 1.2 в обменах сообщениями InsertResourceProperties и UpdateResourceProperties. В частности, эти методы позволяют добавлять или изменять значения свойств WS-ресурса с разными идентификаторами (QName), что запрещается требованиями стандарта.

    Стандартизация Грид

    Реализации ГС начали появляться с 1995 года, когда появился инфраструктурный программный пакет Globus Toolkit, ныне являющийся де-факто стандартом ГС. Он был выпущен организацией Globus Alliance ? крупнейшим международным консорциумом в области Грид. В 1997 году был начат европейский проект по созданию программного пакета для ГС, приведший к созданию ИПО UNICORE. В 2004 году под эгидой проекта EGEE (Enabling Grids for E-sciencE) был выпущен пакет gLite. С появлением большого числа несовместимых между собой реализаций ИПО Грид необходимость унификации и стандартизации стала актуальной, и началась активная работа над созданием стандартов Грид. В 2004 году компания OASIS объявила о выходе стандарта WSRF (Web Services Resource Framework), а в 2005 году Global Grid Forum ? о стандарте OGSA (Open Grid Services Architecture). В том же 2005 году компания Microsoft выпустила ещё один стандарт, определяющий управление разнородными ресурсами ? WS-Management. Таким образом в настоящее время действуют три группы стандартов для ИПО Грид.

    Стандарты OGSA и WSRF. Ограничение области определения задачи

    Рассмотрим характерные особенности стандартов, использующихся при разработке ИПО Грид: OGSA и WSRF. Стандарт OGSA описывает инфраструктурное ПО (далее ИПО) OGSI (OpenGrid Services Infrastructure), занимающее “промежуточное” положение между приложениями пользователей и непосредственно вычислительными ресурсами. Это означает, что приложения не могут взаимодействовать с ресурсами напрямую, а только с ИПО. Таким образом, от пользователей скрывается внутренняя структура ГС, и им совершенно нет нужды вникать в детали реализации вычислений на самих ресурсах, приложение взаимодействует только с этой промежуточной средой. В основе архитектуры OGSI лежит понятие Грид-сервиса, который представляет собой механизм удаленных вызовов, разработанный специально для Globus Toolkit версии 3. Посредством удаленного доступа к методам Грид-сервиса приложение получает определенный вид обслуживания. Таким образом, унифицируются различные функции: доступа к вычислительным ресурсам, ресурсам хранения, базам данных и к любой программной обработке данных. Однако, при всех своих достоинствах, концепция грид-сервисов имеет ряд недостатков. Во-первых, само понятие Грид-сервиса недостаточно формализовано, а это означает, что в различных реализациях Грид-сервисами могут быть совершенно разные, зачастую даже несовместимые сущности. Во-вторых, наиболее близким аналогом Грид-систем можно считать Web. Соответственно, представляется разумным обеспечить их совместимость, чего столь специфичное решение, как Грид-сервисы, дать не может. Одновременно с разработкой стандарта OGSA, в 2004 году консорциум OASIS предложил стандарт WSRF. Он также описывает некоторое ИПО, в основе которого, однако, лежат уже не Грид- а Web-сервисы. А в 2005 году был опубликован стандарт WS-Management, фактически, представляющий собой протокол для управления серверами, устройствами, и приложениями, основанный на использовании всё тех же Web-сервисов. В последней версии Globus Toolkit разработчики отказались от использования Грид-сервисов в пользу Web-сервисов. За основу была взята спецификация WSRF. Соответственно, областью определения данной работы является именно ИПО, основанное на Web-сервисах, так как на этой концепции базируются наиболее широко используемые реализации ИПО Грид. В данной работе исследуется возможность применения технологии автоматизированного тестировании UniTESK для функционального тестирования ИПО Грид, включая тестирования соответствия реализации стандарту. В частности, рассматриваются следующие вопросы:
  • представление требований в сервисам ИПО Грид в виде формальных спецификаций UniTESK;
  • разработка медиаторов для оказания воздействий на ИПО Грид;
  • разработка тестовых сценариев для ИПО Грид;

    Существующие методы и подходы тестирования ИПО Грид

    В 2006-м году Европейский институт стандартизации телекоммуникаций (ETSI) организовал экспертную группу по разработке тестового набора для тестирования совместимости ИПО Грид . Метод, разрабатываемый в ETSI, основывается на разработке большого числа тестов (testcases) на языке TTCN-3. Каждый тест предназначен для проверки отдельной цели тестирования ? тестовой ситуации, сформулированной полуформально на языке TPL (Test Purpose Language). В настоящее время ведется выделение целей тестирования. Прототип тестового набора для ИПО Грид на языке TTCN3 представлен в работе . Тестовый набор не связан с каким-либо стандартом для ИПО Грид. Вместо проверки требований стандарта тестовый набор проверяет применимость Грид в типовых сценариях использования ? постановки вычислительной задачи в очередь, выполнение задачи на одном из вычислительных узлов, доставка результатов вычисления клиенту. В рамках проекта построения распределенной системы EGEE обработки экспериментальных данных с Большого адронного коллайдера проводилось тестирование пакета gLite, разработанного в CERN. В качестве основного метода тестирования используется интеграционное тестирование ? выполнение задач по пересылке крупных массивов данных. В случае тестирования gLite этот метод тестирования оправдан, так как основное назначение EGEE ? быстрая передача больших объемов данных с датчиков коллайдера в вычислительные системы. Задачу обеспечения совместимости с другими ИПО Грид разработчики EGEE не ставят. Подход к тестированию ИПО Грид, близкий к представленному в данной статье, развивается в работе . Авторы предлагают использовать формализм автоматов с абстрактным состоянием (Abstract State Machine, ASM) и автоматически генерировать тестовые последовательности из обхода автомата модели. Вопросы анализа стандартов ИПО Грид, выделения требований и построения формальной модели требований не рассматриваются.

    Технология автоматизированного тестирования UniTESK

    С 1994 года в ИСП РАН разрабатывается технология автоматизированного тестирования UniTESK, которая с успехом использовалась для тестирования различных классов программных систем ? программных интерфейсов, телекоммункационных протоколов, аппаратного обеспечения. Доступ к ИПО Грид реализуется посредством различных механизмов удаленных вызовов процедур и служебных протоколов, что близко к области применимости UniTESK, поэтому данная технология была выбрана в качестве технологической платформы для разработки тестов. Мы не будем здесь подробно описывать данную технологию, а остановимся лишь на основных этапах разработки тестов с её применением. Разработка тестов с применением технологии UniTESK ведется в следующие 7 этапов:
  • анализ требований и их формализация, построение формальных спецификаций;
  • формулировка требований к качеству тестирования;
  • разработка тестовых сценариев, реализующих заданное покрытие;
  • привязка тестовых сценариев к конкретной целевой системе посредством разработки медиаторов;
  • получение готового тестового набора (трансляция и компиляция тестов);
  • отладка и исполнение тестов;
  • анализ результатов тестирования. Важным преимуществом технологии UniTESK является возможность оценки качества проведенного тестирования посредством вычисления покрытия требований. Этим она отличается от подавляющего большинства тестовых наборов для ГС. Автоматизированы как вычисление покрытия требований, так и построение так называемых оракулов, проверяющих соответствие целевой системы спецификации. Таким образом, технология UniTESK позволяет проводить широкомасштабное тестирование широкого класса программных систем и, в частности, разрабатывать тестовые наборы для решения задач соответствия.

    Тестирование реализации

    Разработанный с помощью инструмента JavaTESK тестовый сценарий включает сценарии для семи методов (они же сценарные методы): GetResourcePropertyDocument, GetResourceProperty, GetMultipleResourceProperties Insert-, Update- и DeleteResourceProperties, а также ImmediateDestroy. Сценарий для GetResourceProperty предельно прост: в нём устанавливаются нижний и верхний пределы мощности RPD WS-ресурса, и пока мощность находится в заданном диапазоне, выбирается один из элементов RPD, т.е. некоторое свойство WS-ресурса. Имя свойства выбирается не произвольно, а как поле элемента массива, в который конвертируется RPD. Осуществляется это посредством применения метода toArray() библиотеки java.util.Set. Запрос искомого свойства осуществляется с помощью вызова одноименного медиаторного метода. Аналогичны сценарии для методов GetResourcePropertyDocument и GetMultipleResourceProperties. Сценарий для InsertResourceProperties тоже несложен: в нём устанавливается только верхний предел мощности RPD WS-ресурса (нижний устанавливать бессмысленно, т.к. новое свойство может быть добавлено даже в RPD мощности 0), а затем с помощью одноименного медиаторного метода отправляется искомое XML-сообщение. В данном сценарии случайным образом генерируется имя свойства и его значение (с помощью функций класса java.util.Random). В некотором смысле аналогичен ему сценарий для метода UpdateResourceProperties ? также устанавливается верхний предел мощности RPD WS-ресурса, но вместе с ним устанавливается и нижний предел (т.к. невозможно изменить значение свойства у WS-ресурса, который не обладает ни одним свойством). Далее с помощью одноименного медиаторного метода отправляется XML-сообщение. Новое значение свойства также генерируется случайно. Наконец, сценарий для обмена сообщениями DeleteResourceProperties таков: устанавливается нижний предел мощности RPD WS-ресурса, а затем вызывается одноименный медиаторный метод. Как и в сценарии для обмена GetResourceProperty, имя удаляемого свойства WS-ресурса не произвольно, а является именем элемента массива, в который конвертируется RPD с помощью метода toArray() библиотеки java.util.Set. Соответствующий граф состояний тестируемой системы имеет следующий вид(Рис.1): Тестирование реализации
    Рис. 1 Граф состояний тестируемой системы Дуги, соответствующие переходам, приводящим к увеличению номера состояния, соответствуют вызовам сценарного метода InsertResourceProperties, обратные ? DeleteResourceProperties, а циклические ? вызовам методов GetResourcePropertyDocument, GetResourceProperty, GetMultipleResourceProperties. Стоит пояснить, почему именно 7 вышеуказанных обменов сообщениями были выбраны в качестве основных для написания соответствующего генератора XML-сообщений. В процессе разработки тестового набора предполагалось, что с его помощью будут протестированы сервисы контейнера компонента Java WS Core инструментария Globus Toolkit 4.0. По умолчанию, всего в контейнере содержится 34 Web-сервисов, из которых 23 поддерживают некоторые обмены сообщениями из разделов WS-ResourceProperties и WS-ResourceLifetime стандарта WSRF. В частности, все они поддерживают обмен типа GetResourceProperty, 9 ? ImmediateDestroy а также Insert-, Update- и DeleteResourceProperties, 14 ? QueryResourceProperties, 13 ? GetMultipleResourceProperties, и 7 ? SetTerminationTime (обмен сообщениями из раздела WS-ResourceLifetime стандарта WSRF, соответствующий установке времени окончания работы WS-ресурса). Соответственно, данные 7 обменов сообщениями являлись как достаточно простыми в смысле написания спецификационных методов (чего нельзя сказать, к примеру, об обмене QueryResourceProperties), так и позволяли в полной мере провести тестирование соответствия стандарту WSRF 1.2. Отдельный сценарий был реализован для метода ImmediateDestroy. В этом сценарии просто вызывается соответствующий медиаторный метод. Соответствующий граф состояний таков(Рис.2): Тестирование реализации
    Рис 2. Граф состояний для сценария ImmediateDestroy Переход здесь всего 1, результатом выполнения сценарного метода является уничтожение WS-ресурса (состояние “Destroyed”).

    Вопросы тестирования реализаций Грид

    Одной из особенностей предметной области является наличие ряда, вообще говоря, несовместимых стандартов и нескольких независимых реализаций. Заметим, что в данной работе не решается задача анализа адекватности стандартов, т.е. не только не утверждается, но и не проверяется гипотеза о том, что реализации, соответствующие одному и тому же стандарту, совместимы. Однако ясно, что весьма актуальной задачей является тестирование реализации на соответствие стандарту. Также стоит отметить, что в предметной области имеет место специфичность средств взаимодействия пользовательских приложений и самой ГС ? в данном случае это Грид- и Web-сервисы, а также удаленные вызовы процедур. Web-сервис ? это программная система, идентифицируемая строкой URI (Uniform Resource Identifier ? некоторая последовательность, однозначно определяющая физический или абстрактный ресурс), чьи общедоступные интерфейсы определены на языке XML (eXtensible Markup Language ? расширяемый язык разметки, предоставляющий возможность хранения структурированных данных для обмена ими между программами). Описание этой программной системы может быть найдено другими программными системами, причем последние могут с ней взаимодействовать, обмениваясь сообщениями в формате XML с помощью протоколов Интернета (например, SOAP) . Наиболее распространенными подходами к тестированию ГС являются следующие:
  • Модульное тестирование (Unit testing) - тестирование различных модулей исходного кода ПО. При таком подходе тесты пишутся для каждой нетривиальной функции или метода в отдельности. В частности, разработчики ИПО Globus toolkit широко используют JUnit для проверки реализации.
  • Интеграционное тестирование (Integration testing, оно же тестирование взаимодействия) - тестирование выполнения приложений на сборке ИПО Грид. Типичными примерами интеграционных тестовых сценариев являются пересылки больших массивов данных и проведение типовых расчетов. Оба подхода направлены на выявление ошибок реализации. Однако у них есть существенный недостаток в контексте тестирования совместимости: отсутствует связь между тестами и требованиями соответствующих стандартов. То есть по результатам тестирования нельзя сделать вывод о соответствии или несоответствии реализации стандарту. Таким образом, важным вопросом тестирования ГС является тестирование на соответствие стандартам. Сложность этого вопроса заключается в наличии ряда жестких условий на используемые технологии тестирования и получаемые тесты. Технологии разработки тестов должны предоставлять возможность определения покрытия требований ? без выполнения этого условия ценность получаемых результатов весьма сомнительна. Сами же тесты должны представлять собой последовательности вызовов процедур или обращений к сервисам, детали реализаций которых могут быть неизвестны.

    В данной работе решалась задача

    В данной работе решалась задача тестирования соответствия реализации ИПО ГС Globus Toolkit 4.2 стандарту систем управления распределенными ресурсами WSRF 1.2. Данный стандарт был проанализирован и на его основе составлен его каталог требований. Также была исследована структура и интерфейсы компонента Java WS Core программного пакета Globus Toolkit 4.2. На основе полученных данных был разработан тестовый набор для указанного программного пакета с применением технологии тестирования UniTESK (JavaTESK) и проведено тестирование. Тестирование показало, что реализация Globus Toolkit 4.2 соответствует стандарту WSRF, хотя выявило отсутствие выполнения ряда необязательных требований, как семантических, так и синтаксических. Также тестирование показало, что технология UniTESK применима для тестирования ИПО ГС, в частности, имеют место следующие особенности её применения:
  • Обмены сообщениями с Web-сервисами естественным образом моделируются спецификационными функциями;
  • Для разработки тестового набора нужна библиотека классов для автоматизации построения различных (в том числе некорректных) сообщений Web-сервисов. В рамках данной работы были получены следующие результаты:
  • показана применимость технологии UniTESK для разработки тестовых наборов для инфраструктурного программного обеспечения (ИПО, middleware) Грид-систем;
  • разработан метод формализации требований и разработки тестов для стандартов ИПО Грид, основанных на архитектуре Web-сервисов;
  • разработан прототип тестового набора для проверки соответствия реализаций ИПО Грид базовому стандарту WSRF версии 1.2:
  • проведен анализ базового стандарта WSRF, составлен каталог требований;
  • на основе каталога требований разработана формальная спецификация;
  • проведено исследование реализации, на основе которого разработан медиатор
  • разработан тестовый набор для реализации ИПО ГС Globus Toolkit 4.0 средствами технологии UniTESK (JavaTESK);
  • проведено тестирование реализации ИПО ГС Globus Toolkit 4.0.
  • при тестировании реализации был обнаружен ряд несоответствий базовому стандарту WSRF. В качестве направлений для дальнейшей работы мы рассматриваем разработку тестового набора, проверяющего специфические требования к сервисам ИПО Грид, такие как сервис передачи больших массивов данных, сервис создания и управления вычислительными задачами и др.

    Тестирование софта - статьи

    Аннотация.

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

    Архитектура тестовой системы UniTesK

    Архитектура тестовой системы UniTesK [] была разработана на основе многолетнего опыта тестирования промышленного программного обеспечения из разных предметных областей и разной степени сложности. Учет этого опыта позволил создать гибкую архитектуру, основанную на следующем разделении задачи тестирования на подзадачи []:
  • Построение последовательности тестовых воздействий, нацеленной на достижение нужного покрытия.
  • Создание единичного тестового воздействия в рамках последовательности.
  • Установление связи между тестовой системой и реализацией целевой системы.
  • Проверка правильности поведения целевой системы в ответ на единичное тестовое воздействие. Для решения каждой из этих подзадач предусмотрены специальные компоненты тестовой системы: для построения последовательности тестовых воздействий и создания единичных воздействий - обходчик и итератор тестовых воздействий, для проверки правильности поведения целевой системы - оракул, для установления связи между тестовой системой и реализацией целевой системы - медиатор. Рассмотрим подробнее каждый из указанных компонентов. Обходчик является библиотечным компонентом тестовой системы UniTesK и предназначен вместе с итератором тестовых воздействий для построения последовательности тестовых воздействий. В основе обходчика лежит алгоритм обхода графа состояний конечного автомата, моделирующего целевую систему на некотором уровне абстракции. Итератор тестовых воздействий работает под управлением обходчика и предназначен для перебора в каждом достижимом состоянии конечного автомата допустимых тестовых воздействий. Итератор тестовых воздействий автоматически генерируется из тестового сценария, представляющего собой описание конечно-автоматной модели целевой системы. Оракул оценивает правильность поведения целевой системы в ответ на единичное тестовое воздействие. Он автоматически генерируется на основе формальных спецификаций, описывающих требования к целевой системе в виде пред- и постусловий интерфейсных операций и инвариантов типов данных. Медиатор связывает абстрактные формальные спецификации, описывающие требования к целевой системе, с конкретной реализацией целевой системы. Трасса теста отражает события, происходящие в процессе тестирования. На основе трассы можно автоматически генерировать различные отчеты, помогающие в анализе результатов тестирования.

    Архитектура тестовой системы

    При расширении базовой архитектуры тестовой системы CTesK мы учитывали то обстоятельство, что используемый симулятор Icarus Verilog не позволяет напрямую управлять симуляцией через интерфейс VPI [], используемый нами для интеграции тестовой системой с симулятором. Предлагаемая архитектура тестовой системы показана на Рис. 5. Тестовая система состоит из двух потоков:
  • потока симулятора Verilog - основного потока;
  • потока тестовой системы CTesK - подчиненного потока. В начале симуляции при помощи вызова специальной функции из симулятора Verilog в отдельном потоке запускается тестовая система CTesK, которая в цикле предоставляет тестовые воздействия на тестируемую Verilog-модель, принимает реакции на них и оценивает правильность этих реакций. Verilog-окружение содержит экземпляр тестируемой Verilog-модели. Оно осуществляет инициализацию экземпляра тестируемой модели, запускает через VPI-модуль в отдельном потоке тестовую систему CTesK, от которой в цикле принимает тестовые воздействия и которой передает реакции тестируемой модели на них. Прием тестовых воздействий и передача реакций осуществляются через VPI-медиатор. VPI-модуль связывает Verilog-окружение с тестовой системой CTesK. Он реализует функцию запуска тестовой системы CTesK и содержит в качестве составной части VPI-медиатор. VPI-медиатор связывает экземпляр тестируемой Verilog-модели с медиатором тестовой системы CTesK. Он реализует прием тестовых воздействий от тестовой системы CTesK и посылку реакций тестируемой модели на них, а также осуществляет синхронизацию состояний экземпляра тестируемой модели и спецификационной модели данных тестовой системы CTesK.
    Тестовая система CTesK выполняется в симуляторе SystemC, она подает на тестируемую SystemC-модель тестовые воздействия, принимает реакции на них и оценивает правильность этих реакций. Взаимодействие тестовой системы CTesK и тестируемой SystemC-модели осуществляется через C-медиатор. C-медиатор представляет собой реализацию программного интерфейса на языке программирования C, предназначенного для доступа к экземпляру тестируемой SystemC-модели. C-медиатор используется медиатором тестовой системы CTesK для подачи тестовых воздействий и приема реакций. Модуль запуска тестовой системы предназначен для запуска тестовой системы CTesK в симуляторе SystemC.

    Инструмент разработки тестов CTesK

    Инструмент CTesK [], используемый в данной работе, является реализацией концепции UniTesK для языка программирования C. Для разработки компонентов тестовой системы в нем используется язык SeC (specification extension of C), являющийся расширением ANSI C. Инструмент CTesK включает в себя транслятор из языка SeC в C, библиотеку поддержки тестовой системы, библиотеку спецификационных типов и генераторы отчетов. Для пользователей Windows имеется модуль интеграции в среду разработки Microsoft Visual Studio 6.0. Компоненты тестовой системы UniTesK реализуются в инструменте CTesK с помощью специальных функций языка SeC, к которым относятся:
  • спецификационные функции - содержат спецификацию поведения целевой системы в ответ на единичное тестовое воздействие, а также определение структуры тестового покрытия;
  • медиаторные функции - связывают спецификационные функции с тестовыми воздействиями на целевую систему;
  • функция вычисления состояния теста - вычисляет состояние конечного автомата, моделирующего целевую систему.
  • сценарные функции - описывают набор тестовых воздействий для каждого достижимого состояния теста; Обходчик из библиотеки инструмента CTesK требует, чтобы конечный автомат, способ построения которого описан в тестовом сценарии (с помощью функций двух последних видов), являлся детерминированным, а также имел сильно связный граф состояний.

    Краткий обзор технологии UniTesK

    Технология UniTesK была разработана в ИСП РАН на основе опыта, полученного при разработке и применении технологии KVEST (kernel verification and specification technology) []. Общими чертами этих технологий являются использование формальных спецификаций в форме пред- и постусловий интерфейсных операций и инвариантов типов данных для автоматической генерации оракулов (компонентов тестовой системы, осуществляющих проверку правильности поведения целевой системы), а также применение конечно-автоматных моделей для построения последовательностей тестовых воздействий. В отличие от технологии KVEST, в которой для спецификации требований использовался язык RSL (RAISE specification language) [], технология UniTesK использует расширения широко известных языков программирования. На данный момент в ИСП РАН разработаны инструменты, поддерживающие работу с расширениями языков C, Java и C#: CTesK [], J@T [] и Ch@se [] соответственно.

    Модели аппаратного обеспечения и технология UniTesK

    Теперь, после того как мы сделали краткий обзор технологии UniTesK и рассмотрели особенности моделей аппаратного обеспечения на языках высокого уровня, обсудим вопрос о применимости технологии UniTesK к функциональному тестированию таких моделей. Традиционная архитектура тестовой системы для тестирования моделей аппаратного обеспечения выглядит следующим образом: Тестовый модуль (testbench) последовательно подает на тестируемую модель тестовые воздействия и осуществляет проверку правильности реакций на них. Результатом теста является VCD-файл, создаваемый симулятором и содержащий изменения значений сигналов на входах и выходах тестируемой модели во времени. Полученный файл обычно используется для визуализации волновой диаграммы теста - традиционного средства анализа результатов тестирования. Наша цель - расширить тестовый модуль так, чтобы он включал в себя полномасштабную тестовую систему CTesK. Для начала рассмотрим следующие общие вопросы:
  • Как специфицировать модели аппаратного обеспечения на языке SeC.
  • Как использовать понятие времени в рамках технологии UniTesK.
  • Как адаптировать тестовую систему CTesK к выполнению в симуляторе. Сначала рассмотрим вопрос о спецификации моделей аппаратного обеспечения. Для каждого процесса, определенного в модели, классифицируем используемые им входы на управляющие, информационные и несущественные. Будем называть вход управляющим, если процесс реагирует на возникающие на данном входе события, информационным, если значение сигнала на данном входе влияет на поведение процесса, все остальные входы будем называть несущественными. Под единичным тестовым воздействием будем понимать непустое непротиворечивое множество событий из списка чувствительности одного из процессов, реализованных одновременно, а также набор значений сигналов на информационных входах этого процесса. Предположим, что списки чувствительности процессов, определенных в модели, попарно не пересекаются, а управляющие входы одного процесса не являются информационными входами другого.
    При таких ограничениях, тестовое воздействие может активизировать только один модельный процесс. Для спецификации модели на языке SeC, множество всех тестовых воздействий должно быть разбито на группы, каждая из которых описывается одной спецификационной функцией. Один из возможных подходов разбиения состоит в следующем. Каждому процессу, определенному в модели, ставится в соответствие отдельная спецификационная функция, параметры которой отражают множество событий из списка чувствительности, реализуемые на управляющих входах, а также значения подаваемых на информационные входы сигналов. В предусловии спецификационной функции, помимо разного рода смысловых проверок, должна осуществляться проверка того, что значения сигналов на управляющих входах процесса позволяют реализовать указанные в параметрах события, а в постусловии - то, что указанные события действительно были реализованы. Проиллюстрируем этот подход на примере. Рассмотрим модуль на языке Verilog HDL. module Module(x, y, z, r); input x, y, z; output r; reg r; always @(posedge(x), negedge(y)) begin: Process r = z; end endmodule Интерфейс модуля состоит из трех входов: x, y, z и одного выхода r. Для процесса, определенного в модуле, входы x и y являются управляющими, а вход z - информационным. При возникновении фронта сигнала на входе x или среза сигнала на входе y процесс присваивает r значение сигнала на входе z. Для спецификации возможности наступления событий из списка чувствительности процесса, состояние спецификационной модели данных должно включать текущие значения сигналов на входах x и у. // спецификационная модель данных модуля typedef struct { // текущие значения сигналов на входах x и y bool x, y; } Module; Спецификационная функция, описывающая поведение процесса, будет иметь в качестве параметров указатель на состояние спецификационной модели данных, индикаторы событий из списка чувствительности и значение информационного входа z. // спецификационная функция процесса specification bool Process(Module *module, bool posedge_x, bool negedge_y, bool z) // процесс читает какие события произошли, // а также значение информационного входа z reads posedge x, negedge _y, z // тестовое воздействие изменяет значения // сигналов на управляющих входах updates x = module->x, y = module->y { pre { // проверка возможности реализации событий return ((posedge x negedge _y) && (posedge х => !x) && (negedge у => y)); } post { // проверка реализации событий return ((posedge x => x) && (negedge у => !y)) && Process == z; } } Заметим, что данный подход к выделению спецификационных функций является достаточно общим, а потому не всегда оптимальным.


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


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

    Особенности моделей аппаратного обеспечения

    Модели аппаратного обеспечения на таких языках как Verilog HDL [] и SystemC [] представляют собой системы из нескольких взаимодействующих модулей. Как и в языках программирования, модули используются для декомпозиции сложной системы на множество независимых или слабо связанных подсистем. Каждый модуль имеет интерфейс - набор входов и выходов, через которые осуществляется соединение модуля с окружением, и реализацию, определяющую способ обработки модулем входных сигналов: вычисление значений выходных сигналов и изменение внутреннего состояния. Обработка модулем входных сигналов инициируется событиями со стороны окружения. Под событиями в моделях аппаратного обеспечения понимают любые изменения уровней сигналов. Поскольку обычно рассматривают двоичные сигналы, выделяют два основных вида событий: фронт сигнала (posedge, positive edge) - изменение уровня сигнала с низкого на высокий и срез сигнала (negedge, negative edge) - изменение уровня сигнала с высокого на низкий. Как правило, каждый модуль состоит из нескольких статически заданных параллельных процессов, каждый из которых реализует следующий цикл: сначала осуществляется ожидание одного или нескольких событий из заданного набора событий, затем их обработка, после чего цикл повторяется. Набор событий, ожидаемых процессом для обработки, называется списком чувствительности (sensitive list) процесса. Будем называть процесс пассивным, если он находится в состоянии ожидания событий, и активным в противном случае. Важной особенностью моделей аппаратного обеспечения является наличие в них понятия времени: поведение таких моделей определяется не только последовательностью событий, но и длительностью временных интервалов между ними. Время моделируется дискретной целочисленной величиной, физический смысл единицы времени можно задавать. Для описания причинно-следственных отношений между событиями, происходящими в одну единицу модельного времени используется понятие дельта-задержки (delta delay). События, между которыми есть дельта-задержка, выполняются последовательно одно за другим, но в одну и ту же единицу модельного времени. Для выполнения моделей с целью анализа их поведения обычно используют симуляцию по событиям (event-driven simulation).
    В отличие от симуляции по интервалам времени (time-driven simulation), в которой значения сигналов и внутренние состояния модулей вычисляются через регулярные интервалы времени, в этом способе модель рассматривается только в те моменты времени, когда наступают некоторые события. Работа событийного симулятора (event-driven simulator) осуществляется следующим образом. В начале симуляции модельное время устанавливается в ноль. Далее в цикле, пока есть активные процессы, выбирается один из них и выполняется до тех пор, пока этот процесс не станет пассивным. После того, как выполнены все активные процессы, симулятор проверяет, есть ли события, запланированные через дельта-задержку на текущий момент времени. Если такие события есть, симулятор реализует эти события и перевычисляет множество активных процессов, после чего цикл повторяется. Если, после очередного выполнения цикла, событий запланированных на текущий момент времени нет, симулятор проверяет, есть ли события, запланированные на будущие моменты времени. Если таких событий нет, симуляция заканчивается, в противном случае, симулятор изменяет модельное время на время ближайшего события, реализует события, запланированные на этот момент времени и перевычисляет множество активных процессов, после чего цикл повторяется.

    Применение технологии UniTesK для функционального тестирования моделей аппаратного обеспечения

    , , , Препринт Института системного программирования РАН (ИСП РАН)

    Пример счетчика

    Рассмотрим пример небольшого устройства - счетчика, на Verilog- и SystemC-моделях которого мы будем иллюстрировать основные шаги разработки тестов. Интерфейс счетчика состоит из двух двоичных входов inc и rst и одного целочисленного выходного регистра cnt. Пример счетчика Рис. 4.Схема входов и выходов счетчика. В ответ на фронт сигнала inc, устройство увеличивает содержимое регистра cnt на единицу, на фронт rst - обнуляет регистр cnt. Срезы сигналов не обрабатываются. Для упрощения спецификации и тестирования будем считать, что поведение устройства при одновременном возникновении фронтов сигналов на обоих входах не определено. Рассмотрим спецификацию счетчика на языке SeC. Спецификационная модель данных включает в себя текущие значения входных сигналов inc и rst, а также выходного регистра cnt: Если в тесте есть тестовое воздействие, включающее фронт (срез) некоторого сигнала, то в нем должно присутствовать тестовое воздействие, включающее срез (фронт) этого сигнала. // спецификационная модель данных счетчика typedef struct { bool inc; // текущее значение сигнала // на входе inc bool rst; // текущее значение сигнала // на входе rst int cnt; // текущее значение регистра cnt } Model; Спецификационные функции описывают поведение устройства в ответ на фронты и срезы сигналов inc и rst. Ниже приводится спецификационная функция inc_posedge_spec, описывающая реакцию на фронт сигнала на входе inc. Остальные функции определяются аналогичным образом. // спецификация реакции на фронт сигнала на входе inc specification void inc_posedge_spec(Model *model) updates cnt = model->cnt, inc = model->inc { pre { return model != NULL && inc == false && cnt < INT_MAX; } coverage C { return {single, "Single branch"}; } post { return inc == true && cnt == @cnt + 1; } } Рассмотрим сценарий тестирования счетчика. В качестве состояния конечного автомата для нашего примера будем просто использовать состояние спецификационной модели, то есть текущие значения входных сигналов inc и rst, а также выходного регистра cnt.
    В этом случае функция вычисления состояния конечного автомата будет выглядеть как показано ниже. static List* scenario_state() { List* list = create_List(&type_Integer); append_List(list, create_Integer(model.inc)); append List(list, create Integer(model.rst)); append List(list, create Integer(model.cnt)); return list; } Чтобы ограничить число состояний конечного автомата, запретим подачу фронта inc в состояниях, в которых значение регистра cnt больше или равно десяти. Остальные стимулы (фронт rst, срезы inc и rst) сделаем допустимыми во всех достижимых состояниях. В начальном состоянии теста устанавливаем низкие уровни сигналов inc и rst, а значению регистра cnt присваиваем ноль. Ниже приводится сценарная функция для фронта inc. Остальные функции определяются аналогичным образом. scenario bool inc_posedge_scen() { if(model.cnt < 10) { if(pre_inc_posedge_spec(&model)) inc posedge spec(&model); } return true; }

    Разработка C-медиатора

    Поскольку инструмент CTesK предназначен для разработки тестов для программных интерфейсов на языке программирования С, то из компонентов тестовой системы CTesK нельзя напрямую обращаться к SystemC-модели. Все обращения к тестируемой модели должны осуществляться через специально разработанный С-медиатор, который предоставляет интерфейс для подачи тестовых воздействий и получения реакций тестируемой модели на них. Ниже приводится интерфейс С-медиатора для SystemC-модели счетчика. #ifdef __cplusplus extern "C" { #endif // #ifdef __cplusplus // интерфейсные функции, осуществляющие // тестовые воздействия // на экземпляр SystemC-модели счетчика void count_inc_posedge(void); void count_rst_posedge(void); void count_inc_negedge(void); void count_rst_negedge(void); // интерфейсные функции, получающие информацию // о состоянии экземпляра SystemC-модели счетчика int count_inc(void); int count_rst(void); int count_cnt(void); #ifdef __cplusplus } #endif // #ifdef __cplusplus Заметим, что в функциях, реализующих тестовые воздействия, должна осуществляться приостановка модельного процесса тестовой системы для того, чтобы симулятор SystemC мог активизировать процесс тестируемой модели, занимающийся обработкой поданного воздействия.
    В инструменте CTesK медиатор реализуется c помощью медиаторных функций, каждая из которых связывает спецификационную функцию с группой тестовых воздействий на целевую систему. Код медиаторной функции состоит из блока воздействия (блока call), в котором осуществляется тестовое воздействие, и блока синхронизации (блока state), в котором осуществляется синхронизация состояния спецификационной модели данных с состоянием целевой системы. Разработку медиатора для Verilog-модели можно осуществить автоматически по следующей схеме:
  • для каждой спецификационной функции пишется медиаторная функция следующего вида:
  • блок call≡ {apply_<воздействие>(<параметры>) ;};
  • блок state ≡ {wait_for_check();}. Медиаторная функция для спецификационной функции inc posedge spec будет выглядеть следующим образом: // медиаторная функция для inc_posedge_spec mediator inc_posedge_media for specification void inc_posedge_spec(Model *model) updates cnt = model->cnt, inc = model->inc { // посылаем тестовое воздействие call { apply_inc_posedge(model); } // ожидаем реакции state { wait_for_check(); } }


    После того, как создан C-медиатор, разработка медиатора осуществляется по следующей схеме:
  • разрабатывается функция синхронизации состояний map_state_up, осуществляющая синхронизацию состояния спецификационной модели данных тестовой системы CTesK с состоянием экземпляра тестируемой SystemC-модели;
  • для каждой спецификационной функции пишется медиаторная функция следующего вида:
  • в блоке call осуществляется вызов соответствующей интерфейсной функции C-медиатора;
  • в блоке state осуществляется вызов функции map_state_up. Ниже приводится медиаторная функция для спецификационной функции inc_posedge_spec. // медиаторная функция для inc_posedge_spec mediator inc_posedge_media for specification void inc_posedge_spec(Model *model) updates cnt = model->cnt, inc = model->inc { // вызываем соответствующую функцию C-медиатора call { count_inc_posedge(); } // вызываем функцию синхронизации состояний state { map_state_up(model); } } Видно, что при совпадении интерфейсов C-медиатора и спецификации разработку медиатора можно автоматизировать.

    Разработка модуля взаимодействия потоков

    Модуль взаимодействия потоков реализует функции синхронизации потока симулятора Verilog и потока тестовой системы CTesK. Эти функции используются медиатором и VPI-модулем, поэтому модуль взаимодействия потоков рекомендуется разрабатывать перед разработкой медиатора или VPI-модуля. Модуль взаимодействия потоков должен реализовывать следующие функции:
  • wait_for_check - функция ожидания реакции на тестовое воздействие. Вызывается в медиаторе;
  • wait_for_apply - функция ожидания тестового воздействия. Вызывается в VPI-медиаторе, возвращает идентификатор тестового воздействия;
  • apply_check - функция передачи реакции на тестовое воздействие. Вызывается в VPI-медиаторе;
  • apply_finish - функция посылки сообщения о завершения теста. Вызывается тестовой системой CTesK;
  • apply_<воздействие> - функции посылки тестовых воздействий. Вызываются в медиаторе. Также в модуле взаимодействия потоков можно определить функции:
  • start_scenario - функция запуска тестовой системы CTesK. Вызывается в VPI-медиаторе, создает необходимые для взаимодействия потоков ресурсы, устанавливает имя UniTesK-трассы, запускает в отдельном потоке тестовую систему CTesK;
  • end_scenario - функция завершения работы тестовой системы CTesK. Вызывается в VPI- медиаторе при получении сообщения о завершении теста, освобождает созданные ресурсы. Так как модуль использует средства создания и взаимодействия потоков, он является платформенно-зависимым. Мы вели разработку на платформе Windows 2000 и использовали механизм событий Win32. Очевидно, что разработку модуля взаимодействия потоков для каждой конкретной платформы можно автоматизировать.

    Разработка модуля запуска тестовой системы

    Разработка модуля запуска тестовой системы осуществляется по следующей схеме:
  • разрабатывается функцию запуска тестовой системы CTesK;
  • разрабатывается SystemC-модуль для вызова тестовой системы CTesK в отдельном модельном процессе. Ниже приводится функция count_start, запускающая тестовый сценарий count_scenario. // функция запуска тестового сценария void count_start(const char *trace) { addTraceToFile(trace); count_scenario(0, NULL); } Ниже приводится SystemC-модуль для вызова тестовой системы CTesK в отдельном модельном процессе. // модуль запуска тестовой системы SC_MODULE(count_testbench) { public: // определяем отдельный модельный процесс SC_CTOR(count_testbench) { SC_THREAD(main); } // метод запуска теста void start(void) { sc_start(); } // процесс тестовой системы CTesK void main(void) { count_start("simulation.unitrace"); } }; Видно, что разработку модуля запуска тестовой системы можно полностью автоматизировать.

    Разработка теста

    В этом разделе подробно рассматривается процесс разработки теста для Verilog-моделей аппаратного обеспечения с помощью инструмента CTesK. Для иллюстрации процесса будем использовать пример счетчика (см. раздел ), Verilog-модель которого приводится ниже. module count(inc, rst); // входы inc и rst input inc, rst; // выходной регистр cnt integer cnt; // увеличивает счетчик task increment; begin cnt = cnt + 1; end endtask // сбрасывает счетчик task reset; begin cnt = 0; end endtask // в начальном состоянии счетчик сброшен initial begin reset; end // обработчик фронта сигнала на входе inc always @(posedge inc) begin increment; end // обработчик фронта сигнала на входе rst always @(posedge rst) begin reset; end endmodule Процесс разработки теста состоит из следующих шагов:
  • Разработка спецификации устройства.
  • Разработка модуля взаимодействия потоков.
  • Разработка медиатора.
  • Разработка тестового сценария.
  • Разработка Verilog-окружения.
  • Разработка VPI-модуля. Рассмотрим подробно каждый из этих шагов за исключением шага разработки спецификации и шага разработки тестового сценария, которые были описаны выше (см. раздел ).
    В этом разделе подробно рассматривается процесс разработки теста для SystemC-моделей аппаратного обеспечения с помощью инструмента CTesK. Для иллюстрации процесса будем использовать пример счетчика (см. раздел ), SystemC-модель которого приводится ниже. SC_MODULE(count) { // входы сигналы inc и rst sc_in<bool> inc; sc_in<bool> rst; // выходной регистр cnt int cnt; // обработчик изменения значения сигнала inc void increment(void) { if(inc.posedge()) cnt++; } // обработчик изменения значения сигнала inc void reset(void) { if(rst.posedge()) cnt = 0; } SC_CTOR(count): cnt(0), inc(false), rst(false) { SC_METHOD(increment); sensitive(inc); SC_METHOD(reset); sensitive(rst); } }; Процесс разработки теста состоит из следующих шагов:
  • Разработка C-медиатора.
  • Разработка спецификации системы.
  • Разработка медиатора.
  • Разработка тестового сценария.
  • Разработка модуля запуска тестовой системы. Рассмотрим подробно каждый из этих шагов за исключением шага разработки спецификации и шага разработки тестового сценария, которые были описаны выше (см. раздел ).

    Разработка Verilog-окружения

    Verilog-окружение представляет собой модуль верхнего уровня на языке Verilog HDL и разрабатывается по следующей схеме:
  • для каждого входа тестируемой Verilog-модели внутри Verilog-окружения определяется однотипный регистр;
  • определяется экземпляр тестируемой Verilog-модели target, в качестве аргументов которого выступают определенные ранее регистрами;
  • определяется блок initial, внутри которого:
  • устанавливается имя VCD-файла и трассируемые в него сигналы;
  • вызывается системная задача $startScenario, запускающая тестовую систему CTesK;
  • в цикле с помощью системной функции $applyAction принимаются тестовые воздействия от тестовой системы CTesK и с помощью системной задачи $checkAction посылаются реакции на них. Ниже приводится Verilog-окружение для Verilog-модели счетчика: module testbench(); parameter delay = 10; // входы экземпляра тестируемой Verilog-модели reg inc, rst; // экземпляр тестируемой Verilog-модели count target(inc, rst); initial begin // устанавливаем имя VCD-файла и // трассируемые сигналы $dumpfile("simulation.vcd"); $dumpvars(1, testbench); // запускаем тестовую систему CTesK $startScenario(); #(delay); // в цикле получаем тестовые воздействия и // передаем реакции на них while($applyAction() == 0) begin #(delay); $checkAction(); #(delay); end end endmodule Verilog-окружение использует системные задачи $startScenario, $checkAction и системную функцию $applyAction, которые должны быть реализованы в VPI-модуле. Видно, что разработку Verilog-окружения можно полностью автоматизировать.

    Разработка VPI-модуля

    VPI-модуль разрабатывается на языке программирования C с использованием интерфейса VPI. Он должен реализовывать следующие системные функции и задачи:
  • $startScenario - вызывает функцию start_scenario модуля взаимодействия потоков;
  • $applyAction - вызывает функцию wait_for_action модуля взаимодействия потоков и в зависимости от возвращаемого значения производит необходимые изменения входных сигналов тестируемой Verilog-модели;
  • $checkAction - осуществляет синхронизацию состояний экземпляра Verilog-модели и спецификации тестовой системы CTesK, после этого вызывает функцию apply_check модуля взаимодействия потоков; Системная функция $applyAction и системная задача $checkAction образуют VPI-медиатор. Видно, что разработку части VPI-модуля, связанную с запуском тестовой системы CTesK, можно полностью автоматизировать.

    Тестирование SystemC-моделей

    В этом разделе предлагается способ расширения базовой архитектуры тестовой системы UniTesK для функционального тестирования SystemC-моделей аппаратного обеспечения. Описывается процесс разработки теста с помощью инструмента CTesK и приводятся оценки возможности автоматизации шагов этого процесса. Помимо инструмента CTesK и свободно распространяемой библиотеки SystemC, нами использовалась среда разработки Microsoft Visual Studio 6.0.

    Тестирование Verilog-моделей

    В этом разделе предлагается способ расширения базовой архитектуры тестовой системы UniTesK для функционального тестирования Verilog-моделей аппаратного обеспечения. Описывается процесс разработки теста с помощью инструмента CTesK и приводятся оценки возможности автоматизации шагов этого процесса. Помимо инструмента CTesK, нами использовались свободно распространяемый симулятор Icarus Verilog [] и компилятор GCC из набора инструментов MinGW [].

    Возможность автоматизации шагов разработки

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

    в Институте системного программирования РАН

    Технология тестирования UniTesK [] была разработана в Институте системного программирования РАН (ИСП РАН) []. Первоначальное и основное назначение технологии - разработка качественных функциональных тестов для программного обеспечения. В работе рассматривается новая и достаточно перспективная область применения технологии UniTesK - функциональное тестирование моделей аппаратного обеспечения. Под моделями аппаратного обеспечения в данной работе понимаются, прежде всего, HDL-модели (HDL models), то есть модели, написанные на каком-нибудь языке описания аппаратуры (HDL, hardware description language), например, на VHDL или Verilog HDL [], а также модели системного уровня, разрабатываемые на таких языках, как SystemC [] или SystemVerilog []. Заметим, что в работе рассматриваются только модели цифрового аппаратного обеспечения. Известно, что слабым звеном в технологической цепочке проектирования сложного аппаратного обеспечения является функциональная верификация. Согласно Бергерону (Janiсk Bergeron) [] функциональная верификация занимает около 70% общего объема трудозатрат, число инженеров, занимающихся верификацией, примерно вдвое превосходит число проектировщиков, а размер исходного кода тестов (testbenches) достигает 80% общего размера кода проекта. Технология UniTesK и реализующие ее инструменты были успешно применены для тестирования различных классов программных систем: ядер операционных систем, телекоммуникационных протоколов, систем реального времени и т.д. Ключевым моментом в успешности применения технологии UniTesK является гибкая архитектура тестовой системы, которая позволяет легко адаптировать технологию к различным классам целевых систем []. В работе предлагаются способы расширения базовой архитектуры тестовой системы UniTesK для функционального тестирования моделей аппаратного обеспечения на языках Verilog HDL и SystemC. Для каждого из указанных классов моделей описывается процесс разработки теста с помощью инструмента CTesK [] и приводятся оценки возможности автоматизации шагов этого процесса.
    При расширении архитектуры тестовой системы мы руководствовались тем соображением, что трудоемкость разработки дополнительных компонентов теста должна быть минимальной. Языки Verilog HDL и SystemC были выбраны для исследования по следующим причинам. Язык Verilog HDL, на ряду с VHDL, является классическим языком описания аппаратуры, но, в отличие от последнего, он имеет близкий к языку программирования C синтаксис и стандартный интерфейс для вызова функций, написанных на С [], что облегчает интеграцию с инструментом CTesK, кроме того, он гораздо чаще используется на практике для проектирования сложного аппаратного обеспечения. Что касается SystemC, он является современным и многообещающим языком системного уровня, позволяющим моделировать системы со смешанными аппаратными и программными частями (HW/SW) []. Таким образом, были выбраны два достаточно разных языка: один - классический язык описания аппаратуры, другой - современный язык системного уровня. Структура статьи такова. Во втором, следующем за введением, разделе делается краткий обзор технологии UniTesK и инструмента CTesK. В третьем разделе рассматриваются особенности моделей аппаратного обеспечения и то, как эти особенности соотносятся с технологией UniTesK. Там же описывается пример небольшого устройства, для которого с помощью инструмента CTesK разрабатываются спецификация и сценарий тестирования. В четвертом и пятом разделах описывается процесс разработки теста для Verilog- и SystemC-моделей соответственно. В заключении рассматриваются направления дальнейшего развития предлагаемого подхода.

    Взаимодействие компонентов

    Ниже показана последовательность взаимодействия основных компонентов тестовой системы. Чтобы не вдаваться в детали внутренних взаимодействий, такие компоненты, как обходчик и итератор тестовых воздействий, объединены на диаграмме в один компонент - генератор. При вызове $startScenario из Verilog-окружения VPI-модуль запускает в отдельном потоке тестовую систему CTesK (генератор, оракул, медиатор). Далее в цикле осуществляются вызовы $applyAction, для приема очередного тестового воздействия, и $checkAction, для передачи реакции на него. Генератор через оракул передает медиатору очередное тестовое воздействие Action, который преобразует его в посылку сообщения ApplyAction VPI-модулю и переходит в состояние ожидания реакции на него WaitForCheck. VPI-модуль при вызове $applyAction переходит в состояние ожидания очередного тестового воздействия WaitForAction и выходит из него при получении сообщения ApplyAction от медиатора тестовой системы CTesK. После этого он вызовом SetSignals изменяет нужным образом входные сигналы экземпляра тестируемой Verilog-модели и передает управление Verilog-окружению, возвращая статус OK. При вызове $checkAction VPI-модуль, используя GetSignals, получает значения выходных сигналов и синхронизирует состояния экземпляра тестируемой Verilog-модели и спецификации тестовой системы CTesK. После этого он посылает сообщение ApplyCheck медиатору. При приеме сообщения ApplyCheck медиатор выходит из состояния WaitForCheck и передает управление оракулу, который проверяет правильность реакции экземпляра тестируемой Verilog-модели на тестовое воздействие. Цикл завершается при получении VPI-модулем в состоянии ожидания тестового воздействия WaitForAction сообщения Finish о завершении теста от тестовой системы CTesK. Verilog-модель Verilog-о кружение


    В работе была рассмотрена новая и, на наш взгляд, достаточно перспективная область применения технологии UniTesK - функциональное тестирование моделей аппаратного обеспечения. Главным образом, в статье описывались подходы к организации взаимодействия между тестовой системой CTesK и симуляторами моделей на языках Verilog HDL и SystemC. Для каждого из указанных классов моделей был предложен способ расширения базовой архитектуры тестовой системы, показано, что разработку некоторых дополнительных компонентов удается полностью или частично автоматизировать. На настоящий момент опыт применения технологии UniTesK и инструмента CTesK для тестирования моделей аппаратного обеспечения ограничивается небольшими примерами. Многие сложные вопросы такие, как декомпозиция спецификаций для упрощения описания сложных систем, тестирование систем с таймерами, а также тестирование систем со смешанными аппаратными и программными частями остались в работе нерассмотренными. То же относится к вопросам о возможности более тесной интеграция технологии UniTesK и инструмента CTesK с конкретными симуляторами и библиотеками. Данные вопросы являются темами будущих исследований.

    Тестирование софта - статьи

    Абстрактные классы

    При тестировании ряда методов возникает необходимость построения объекта абстрактного класса. Следует, однако, уточнить, что непосредственно такой объект необходим только в том случае, когда тестируется его метод. В остальных же случаях можно, как правило, обойтись каким-либо объектом наследующего класса.
    В первом случае необходимо провести дополнительную работу по определению чисто виртуальных методов. Реализация функциональности каждого метода является трудоемкой задачей, решения которой при проведении тестирования работоспособности хотелось бы избежать. Поэтому компоновщик тестов автоматически генерирует код, в котором определяется класс-наследник абстрактного класса, имеющий те же методы, что и родитель. Чисто виртуальные методы реализованы как заглушки, возвращающие какое-либо значение необходимого типа данных. Это значение получается по общим правилам генерации тестов, то есть может быть либо взято из предопределенного пользователем множества, либо сконструировано компоновщиком.
    Впрочем, в ряде случаев такой подход оказывается неприемлемым. Например, если заглушка используется каким-либо другим методом, вызываемым в процессе тестирования. В подобных ситуациях, если это явилось причиной падения теста, необходимо вручную реализовывать чисто виртуальные методы, основываясь, как правило, на исходных кодах одного из существующих наследников тестируемого класса.
    Дополнительно, стоит отметить, что язык C++ допускает реализацию по умолчанию абстрактного метода, которая может быть использована при описании наследника. Однако в библиотеке Qt3 такая возможность не используется.
    В тех же случаях, когда объект абстрактного класса выступает в качестве параметра, возможны различные варианты его инициализации. Во-первых, можно воспользоваться описанным выше способом и позволить компоновщику сконструировать объект. Во-вторых, можно доопределить абстрактный класс вручную. Однако более предпочтительным является использование объекта какого-либо класса, наследующего данному. Также могут встретиться методы, возвращающие указатель на нужный абстрактный класс. Разумеется, в действительности они возвращают указатель на объект наследующего класса. Результат работы таких методов также можно использовать в качестве значения параметра.
    Аналогично, при тестировании невиртуальных методов можно не доопределять абстрактный класс, а воспользоваться экземпляром одного из наследников тестируемого класса.
    In-charge конструкторы и деструкторы абстрактного класса протестировать невозможно, поскольку они предназначены для построения или разрушения объекта именно этого класса.

    Алгоритм поиска

    В данном разделе описан алгоритм поиска различных порядков вызовов методов. Информацию о тестируемой системе алгоритм получает через управляющего (Controller) . В начале каждого удаленного вызова метода выполнение этого вызова блокируется, и управление передается управляющему, который получает информацию о вызываемом методе в виде возможного перехода (Transition) . Будем говорить, что система находится в глобальном состоянии, если в тестируемой системе не остается активных потоков, т.е. все потоки ожидают команды управляющего. Алгоритму поиска информация предоставляется через интерфейс управляющего (рис. 11). Для обнаружения достижения глобального состояния алгоритм вызывает метод управляющего waitForGlobalState. В глобальном состоянии управляющий выдает информацию о глобальном состоянии (метод getStateInfo), включающую в себя список возможных переходов (метод getTransitionList) и сигнал о завершении взаимодействий с тестируемой системой (метод isEndState). Алгоритм поиска может выполнить один из переходов, вызвав метод applyTransition. public interface Controller { public static interface StateInfo { public List getTransitionList(); public boolean isEndState(); } public void applyTransition(Transition t); public StateInfo getStateInfo(); public void waitForGlobalState(); } Рис. 11.Интерфейс управляющего Как уже упоминалось ранее, в предлагаемом методе тестирования тестовая последовательность строится на основе обхода графа автомата. Переходы автомата представляют собой тестовые воздействия на тестируемую систему. Состояния - обобщенные состояния. Для построения тестовой последовательности от графа автомата требуется сильная связность, т.е. из любого состояния существует путь в любое другое. Алгоритм поиска различных порядков выполняется для некоторого перехода в автомате в некотором обобщенном состоянии. Каждый раз при выполнении алгоритма система находится в некотором состоянии, соответствующем данному обобщенному состоянию. Для перехода в автомате и состояния системы определим понятие дерева перебора.
    Вершинами дерева перебора являются глобальные состояния, а дуги в дереве - это возможные переходы из соответствующих глобальных состояний. Пусть заданы обобщенное состояние S, которому принадлежат состояния системы s1,…, sn, и переход автомата a. Каждому состоянию si и переходу a соответствует дерево перебора ti. Будем говорить, что дерево t1 является поддеревом дерева t2, если для любого пути p1 из t1 существует путь p2 в t2, такой что p1 является префиксом p2. Задача алгоритма поиска - перебрать все пути дерева t, содержащего все пути p, такие что для каждого дерева ti найдется такой путь pi, что p является префиксом pi, то есть t = { p | для всех ti существует pi в ti: p префикс pi }. Отметим, что t является поддеревом любого дерева ti. Такое поддерево будем называть максимальным поддеревом деревьев t1,..., tn. После каждого отката алгоритм сохраняет дерево перебранных путей выполнения. На выходе у алгоритма максимальное поддерево деревьев t1,..., tn, соответствующих состояниям s1,…, sn обобщенного состояния S. Алгоритм управляющего выполняется в несколько этапов. На первом этапе вход алгоритма пуст, и алгоритм начинает выполняться в некотором состоянии. На каждом последующем этапе алгоритм получает на входе пройденное дерево переходов, включающее информацию о последнем пройденном пути. Выполнение на каждом последующем этапе может начинаться в другом состоянии, но принадлежащем тому же обобщенному состоянию. На каждом этапе алгоритм пытается пройти новый путь, принадлежащий деревьям предыдущих этапов. Если же все пути уже пройдены, алгоритм проходит произвольный путь и сообщает о завершении поиска. В конце каждого этапа алгоритм выдает обновленное дерево путей, а также информацию о завершении поиска. Отметим, что алгоритм определяет окончание пути по информации от управляющего. В конце каждого перехода алгоритму известно, завершился ли путь. В тестовых наборах, разработанных по технологии UniTESK, завершение пути соответствует завершению выполнения сценарного метода.

    Аннотация.

    В статье описывается разработанный автором метод тестирования компонентов, взаимодействующих посредством удаленного вызова методов. Метод позволяет гарантировать, что будут проверены все различные чередования вызовов методов в системе, приводящие к различным результатам. В работе выделены ограничения, при которых такой перебор различных порядков вызовов методов гарантирует корректность системы. Показано, что этим ограничениям удовлетворяют системы, разработанные по технологии Enterprise JavaBeans. В отличие от методов проверки моделей (model checking), в предложенном методе перебор осуществляется не для всей системы целиком, а для отдельных тестовых воздействий, что позволяет существенно сократить область перебора.

    Всю информацию, необходимую для выполнения

    Всю информацию, необходимую для выполнения перебора различных порядков, алгоритм поиска получает через интерфейс управляющего (Controller). Архитектура, реализующая методы этого интерфейса, показана на . Основные поставщики информации - это EJBServerInterceptor и JVMHook. EJBServerInterceptor работает на сервере приложений и перехватывает удаленные вызовы методов компонентов. Встраивание данного класса происходит с помощью механизма перехватчиков (interceptor) , определенных в EJB 3.0 []. До выполнения метода компонента выполняются перехватчики, определенные для данного типа компонентов. Механизм перехватчиков используется для реализации таких служб, как управление транзакциями и безопасностью. Нам же от этого механизма требуется только блокировка удаленного вызова метода. EJBServerInterceptor через удаленный интерфейс InfoCollector сообщает информацию о вызываемом методе, создавая по описанию метода переход Transition (processTransition) . Вызов при этом блокируется. JVMHook работает на той же JVM, на которой выполняется тест. Этот класс передает информацию о порождаемых в тесте потоках incActiveThreadCount и decActiveThreadCount, а также информацию о завершении теста notifyEndState. Информация о количестве порождаемых потоков необходима для обнаружения начального глобального состояния. Компоненты, разработанные по технологии EJB, обладают рядом важных свойств:
  • В компонентах не создаются новые потоки. Если с системой работает N клиентов, то для блокировки всех взаимодействий в системе достаточно заблокировать N вызовов методов.
  • В каждый момент для экземпляра компонента может выполняться лишь один метод. В зависимости от настроек сервера приложений другие вызовы либо помещаются в очередь, либо завершаются с исключением. Это гарантирует, что данные экземпляра могут одновременно изменяться лишь одним потоком. Кроме того, мы будем требовать выполнения двух дополнительных ограничений, которые не являются обязательными по спецификации EJB, однако выполняются для большинства систем:
  • Обращения к EntityManager происходят внутри транзакций.
    Этим обеспечивается атомарность обновления данных в базе данных.
  • Все обращения к разделяемой памяти сосредоточены в EntityManger, т.е. других обращений нет. Последние два ограничения, по сути, требуют, чтобы все обращения к разделяемой памяти были атомарными и контролировались (перехватывались) управляющим. Для систем, удовлетворяющих этим ограничениям, верно, что для любого конкретного состояния системы и воздействий на систему:
  • Существует единственное дерево перебора;
  • Пути дерева перебора описывают все возможные результаты выполнения. Таким образом, гарантируется, что если для воздействий, осуществляемых в переходе автомата, система может работать некорректно (несоответствует спецификации), то соответствующий некорректный результат будет обнаружен. Корректность для всей системы в целом зависит от выбранного разработчиком тестов обобщенного состояния и тестовых воздействий, осуществляемых в сценарных методах. При выполнении ряда гипотез [] можно гарантировать, что вся система корректна. В гипотезах требуется, чтобы система в обобщенных состояниях вела себя "похоже". Важным результатом данной работы является возможность утверждать корректность не только для последовательных, но и распределенных программ.

    Дополнительное обеспечение корректности тестового набора

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

    Достоинства и недостатки подхода

    Описанная в предыдущих разделах реализация системы разработки тестов, основанная на технологии Azov, была применена при создании тестового набора для интерфейса библиотеки Qt3. Тестированию подлежало около 10000 операций, поведение которых описывается стандартом LSB. При этом практически во всех случаях удалось построить работоспособные простейшие тесты. На основе полученного опыта можно сделать выводы о преимуществах и недостатках технологии в целом и данной ее реализации.
    Технология построения простейших тестов работоспособности действительно позволяет создавать тестовые наборы, решающие поставленную задачу для интерфейсов большого размера. Эффективность ее применения возрастает в тех случаях, когда интерфейс содержит небольшое количество существенно различных типов данных по сравнению с общим числом операций в нем. Это можно увидеть на примере подсистемы работы с базами данных QSql, которая содержит 447 операций из LSB (501 всего), 20 собственных классов и 7 общеупотребительных типов данных. При этом работающие тесты можно построить даже при отсутствии документации на часть операций, что для библиотеки Qt более чем актуально.
    В результате расширения базовой методики технологии Azov простейшие тесты удалось создать для приблизительно 99,5% объема входящей в LSB части интерфейса Qt. Лишь около 35 операций не получилось протестировать по причине простоты тестов или критической нехватки документации. При этом в процессе описания ошибок проявилась неожиданная сложность. Дело в том, что при отсутствии сколько-нибудь подробной документации чрезвычайно трудно отличить случаи неверного использования операции от случаев обнаружения настоящих ошибок в ее реализации. Фактически для принятия решения в этих случаях приходится изучать исходный код операций.
    В существующем виде технология не предполагает реализацию зависимости между параметрами операции и возвращаемым ею значением. Поэтому в общем случае допустимы только проверки общего вида (например, метод должен вернуть неотрицательное число), которые далеко не всегда можно указать.
    Однако в целях повышения аккуратности тестового набора в тех случаях, когда указаны конкретные значения параметров, влияющие на результат, при помощи специализированного типа проверяется равенство результата операции конкретному правильному значению.

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

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

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

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

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

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

    Генерация тестовой последовательности

    Для генерации тестовой последовательности в UniTESK используется обход конечных автоматов [,]. Такой способ построения тестовой последовательности хорошо зарекомендовал себя на практике, многие исследователи отмечают высокое качество получаемых тестовых последовательностей [,]. В качестве состояний автомата выбирается модельное состояние или его обобщение. Обобщение необходимо для сокращения количества состояний и представляет собой разбиение состояний на классы эквивалентности, рассматриваемые как равные состояния автомата. Переходами автомата являются вызовы одного или более тестируемых методов. Как показано в [], одним из удачных обобщенных состояний для деревьев является мультимножество чисел детей. Чтобы получить дерево, в нашем примере мы добавим корневой узел, чтобы все компании без родителей были его детьми. Кроме того, мы разделим детей на активных и неактивных, т.е. обобщенным состоянием будет мультимножество пар (число активных детей, число неактивных детей). На Рис. 8 показаны два различных состояния с добавленным корневым узлом. Обоим состояниям соответствует одинаковое обобщенное состояние {(0,0), (0,0), (1,0), (1,1)}. Такое обобщение позволяет значительно сократить количество состояний автомата и, тем самым, длину тестовой последовательности. Генерация тестовой последовательности Рис. 8.Обобщение состояний Генерация тестовой последовательности Рис. 9. Архитектура UniTESK В процессе тестирования, используя заданное пользователем обобщенное состояние, специальный компонент тестовой системы - обходчик строит тестовую последовательность на основе обхода всех переходов автомата. Информация для построения переходов автомата задается в тестовом сценарии в виде сценарных методов (Рис. 9). В сценарном методе задаются тестовые воздействия на систему. Это могут быть как простые тестовые воздействия, такие как вызовы методов с различными аргументами, так и сложные, такие как порождение нескольких потоков и вызов последовательности методов. Примером сценарного метода является тестовый вариант на рис. 5 с заданной итерацией параметров id и parent по всем компаниям. Будем ссылаться на этот пример как на сценарный метод scenExample.

    Этап работы алгоритма

    В начале выполнения этапа у алгоритма имеется пройденное дерево переходов Tree. Текущий узел дерева - его корень. Алгоритм ожидает начального глобального состояния. После прихода в глобальное состояние алгоритм выполняет цикл, пока не достигнет конечного состояния. В цикле алгоритм получает список возможных переходов и выполняет действия в зависимости от режима работы. В режиме поиска алгоритм пытается пройти по новому пути. Алгоритм находит пересечение списка переходов в текущем узле дерева и списка переходов для текущего глобального состояния. Таким образом, находится общее поддерево, т.е. остаются только те переходы, которые имеются для уже пройденного дерева и дерева перебора для конкретного состояния системы. Если в результате пересечения уже пройденный переход не был удален, и поддерево в следующем состоянии дерева по этому переходу имеет не пройденные переходы, то алгоритм проходит по ранее пройденному переходу. Текущая вершина - следующая вершина дерева по пройденному переходу. Иначе, если в текущей вершине имеются непройденные переходы, алгоритм проходит по новому переходу и текущая вершина - новая вершина в дереве. Иначе данное поддерево не имеет непройденных переходов, и тогда алгоритм завершает проход по произвольным переходам в режиме симуляции. В режиме симуляции алгоритм проходит по произвольному пути. В текущем глобальном состоянии выбирается первый из возможных переходов. В конце цикла алгоритм ожидает глобального состояния. По приходу в него он обновляет информацию о состоянии и переходит в начало цикла. По завершению цикла, алгоритм возвращает true, если в дереве остались непройденные переходы, и false иначе.

    Конструкторы и деструкторы

    В языке C++ каждому конструктору на программном уровне соответствуют два различных конструктора на бинарном уровне: in-charge и not-in-charge, а каждому деструктору – два или три бинарных: in-charge, not-in-charge и, дополнительно для виртуальных классов, – in-charge deleting. Поскольку стандарт LSB описывает поведение системы на бинарном уровне, то тестированию подлежат все вышеперечисленные конструкторы и деструкторы.
    In-charge конструкторы и деструкторы вызываются при непосредственной работе с объектом класса. In-charge deleting деструктор используется при удалении объекта виртуального класса из общей памяти. Таким образом, для конструктора и деструктора невиртуального класса компоновщик составляет тест, создающий объект либо в стековой, либо в общей памяти, а затем удаляющий его. В случае виртуального класса область памяти, в которой будет создаваться объект, зависит от тестируемого деструктора.
    Not-in-charge конструкторы и деструкторы используются неявно при работе с объектом какого-либо класса, наследующего данному. Поэтому в тесте описывается наследник, и путем создания и уничтожения объекта полученного типа опосредованно выполняется вызов этих конструкторов и деструкторов.

    Контроль корректности наложенных ограничений

    Дополнительные проверки, описанные в разделе 3.1, позволяют выявлять ошибки только в тех последовательностях инициализации данных, которые формируются самим компоновщиком. Однако уточнения, вносимые разработчиком, также могут быть некорректными. Обычно, код, генерируемый на основе специализированных типов, синтаксически верен и выполняет возложенные на него функции, поэтому основным источником ошибок является указание неверного специализированного типа для параметра операции.
    Проверка наложенных ограничений выполняется статически при помощи дополнительного инструмента, реализованного в инструменте разработчика, который просматривает базу данных с уточненной информацией о тестируемых операциях на предмет потенциально опасных случаев. Дополнительные возможности обеспечиваются особыми правилами для наименований специализированных типов, которые позволяют классифицировать их по назначению, например, тип, проверяющий возвращаемое значение и имеющий префикс R_, не должен уточнять неизменяемый параметр метода.
    В частности специализированный тип, указанный для возвращаемого операцией значения, должен содержать некоторую проверку этого значения, а также иметь соответствующее название. Обратно, уточнение для параметров, которые не могут быть изменены в результате вызова целевой операции, не должно содержать проверки возвращаемого значения.
    Не характерно наличие специализированных типов, уточняющих объекты, для статических методов, конструкторов и деструкторов классов, хотя иногда они необходимы для того, чтобы правильно проинициализировать среду исполнения теста. Также проверяется наличие общих специализированных типов, то есть таких, которые используются по умолчанию, для простейших типов данных, таких как int, bool, char и т.д., поскольку это приводит к массовым нарушениям ранее заданных ограничений на возвращаемые значения методов. Специализированные типы для типов более сложной организации чаще описывают правильную последовательность инициализации объекта, а не конкретное его значение, поэтому для них такая проверка не требуется.

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

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

    Методика построения корректных тестов

    Основной задачей рассматриваемой технологии является построение корректного теста работоспособности для каждой операции, входящей в тестируемый интерфейс.
    Под корректным понимается такой тест, который обеспечивает правильную последовательность инициализации параметров целевой операции, инициализацию параметров среды исполнения, содержит непосредственный вызов целевой операции, а также освобождает по завершении задействованные ресурсы и возвращает систему в исходное состояние. Следует также отметить, что в тесте должно проверятся, выполнился ли вызов тестируемой операции успешно или наблюдается отклонение от ожидаемого поведения.
    Вопросы, касающиеся инициализации и финализации (освобождение захваченных тестом ресурсов) тестовых данных относятся скорее к технологии в целом, и освещены в статье [1]. Здесь же речь пойдет об особенностях реализации общего подхода технологии Azov при использовании языка C++ и для тестирования библиотеки Qt3.
    Описанные ниже особенности обеспечиваются функциональностью инструмента-компоновщика тестов, генерирующего тесты по базе данных с уточненной информацией об интерфейсных операциях [1,5], и разработчику, как правило, не приходится о них беспокоиться.

    Методы, не входящие в программный интерфейс приложения

    Определяя набор интерфейсных операций на бинарном уровне, стандарт LSB в некоторых случаях включает в себя операции, не являющиеся частью программного интерфейса (Application Programming Interface, API) описываемых им библиотек. Однако, при тестировании работоспособности необходимо, по возможности, протестировать даже подобные скрытые операции.
    В рамках Qt3 существует ряд классов, предназначенных исключительно для использования внутри самой этой библиотеки. Описания таких классов находятся либо в заголовочных файлах, предназначенных только для сборки библиотеки, либо и вовсе в исходных кодах.
    Одним из возможных подходов к тестированию методов таких классов является построение теста для метода, входящего в API, в процессе работы которого вызывается скрытый метод. Однако данный способ имеет большое количество недостатков. Во-первых, требуется весьма трудоемкое исследование того, какие методы могут быть использованы и какие значения их параметров приводят к вызову тестируемого метода. Во-вторых, отсутствует непосредственный контроль над параметрами тестового воздействия, а опосредованный контроль может быть слишком труден или даже вовсе невозможен. И, наконец, в-третьих, достаточно сложно определить, отработал ли тестируемый метод должным образом.
    Учитывая все сказанное, на практике используется существенно более простое решение. Найденное описание класса выносится в отдельный заголовочный файл, который поставляется вместе с тестом. Вообще говоря, вынесение описания в отдельный файл необходимо только для классов, использующих предоставляемые Qt расширения языка C++, поскольку прекомпилятор Meta Object Compiler, который генерирует на их основе определения на чистом C++, обрабатывает только файлы заголовочного типа. Однако в целях стандартизации процесса подготовки необходимых для создания теста данных различий между классами, определенными на чистом языке, и классами, использующими расширения, не делается. Теперь, при подключении этого заголовочного файла к тесту, компилятор будет считать исследуемые методы частью API, и к ним можно обращаться напрямую, как и в общем случае. Данный подход также требует некоторого количества ручной работы по поиску объявления класса, но ее объем не идет ни в какое сравнение с предыдущим вариантом.
    Также в состав LSB входит множество так называемых thunk методов, которые конструируются компилятором и служат для обращения к виртуальным методам, декларированным в классах-предках. В силу их сугубо вспомогательной природы и отсутствия документации по ним такие операции считаются не подлежащими тестированию и исключаются из дальнейшего рассмотрения.

    Описание системы

    Рассмотрим пример системы, построенной из компонентов, которые взаимодействуют посредством удаленного вызова методов. Система написана на основе Enterprise Java Beans 3.0 []. Система содержит один сеансовый компонент с состоянием (stateful session bean) CompanyManagerBean, реализующий удаленный интерфейс CompanyManager, и один класс сущность (entity) Company (Рис. 1). Описание системы Рис. 1.Система управления компаниями Компонент CompanyManagerBean предоставляет интерфейс работы с иерархиями компаний. У каждой компании имеются уникальный идентификатор, имя, могут иметься активный/неактивный статус и родительская компания. Через компонент CompanyManagerBean можно редактировать компании. Клиент запрашивает экземпляр компонента, затем устанавливает компанию, которую собирается редактировать (editCompany), блокируя доступ к данной компании другим экземплярам, редактирует компанию, изменяя статус компании (setActive, setInactive) и родительскую компанию (setParent, removeParent), затем освобождает компанию (freeCompany), делая ее доступной для редактирования другим экземплярам. На иерархию компаний накладываются следующие ограничения:
  • Компания с активным статусом не может иметь родительскую компанию с неактивным статусом;
  • Иерархия компаний более четырех уровней (глубины) недопустима. Таким образом, данные ограничения запрещают иерархии, показанные на Рис. 2: (а) - активная компания C2 имеет неактивного родителя C1, (б) - иерархия компаний имеет более четырех уровней. Описание системы Рис. 2. Примеры запрещенных иерархий Реализация методов компонента CompanyManagerBean состоит в предварительной проверке ограничений и, в случае успеха, изменении состояния компании и сохранении результатов изменения в базе данных. Для примера рассмотрим реализации методов setInactive и setParent. Метод setInactive устанавливает статус компании неактивным (Рис. 3). Предварительно метод проверяет, что все дочерние компании имеют неактивный статус. Проверка производится во вспомогательном методе isChildrenInactive, в котором происходит поисковый запрос к базе данных с выбором всех дочерних компаний, т.е.
    компаний, у которых родительская компания совпадает с данной. Далее, если все дочерние компании неактивны, то возвращается true, статус компании меняется на неактивный, и изменения сохраняются в базе данных (enityManager.merge), иначе возвращается false, и статус компании не изменяется. Обращения к базе данных производятся через специальный компонент EntityManager, который предоставляет методы добавления (persist), изменения (merge), поиска (find) записей в базе данных. Отметим, что для корректности результатов данной работы требуется, чтобы обращения компонентов к общим данным, хранящимся в базе данных, являлись атомарными, т.е. одновременно может выполняться запрос не более чем от одного компонента. В данном примере это требование обеспечивается механизмом транзакций EJB таким образом, что обращения к EntityManager включаются в контекст транзакций. public boolean setInactive() { if (isChildrenInactive()) { company.setActive(false); entityManager.merge(company); return true; } else { return false; Рис. 3. Реализация метода setInactive public boolean setParent(int id) { Company newParent = entityManager.find(Company.class, id); if (newParent == null) throw new NoResultException(); if (newParent.getActive() !company.getActive()) { company.setParent(newParent); entityManager.merge(company); return true; } else { return false; } } Рис. 4. Реализация метода setParent Метод setParent устанавливает родительскую компанию (Рис. 4). Требуется, чтобы существовала компания с идентификатором, указанным в качестве аргумента вызова этого метода. Метод проверяет, что для активной компании нельзя установить неактивного родителя. Т.е. если родитель активный или компания неактивная, то устанавливается новый родитель, и изменения сохраняются в базе данных; иначе изменения не происходят. Рассмотрим один из возможных тестов для компонента CompanyManagerBean. В тесте предполагается, что в начальном состоянии имеются две активные компании без родителей.


    Имена компаний C1, C2, а идентификаторы 1, 2 соответственно. Тест состоит из двух потоков, выполняющихся одновременно. Оба потока выполняют метод testCase, показанный на Рис. 5, с разными аргументамии. Первый выполняет метод с аргументами 0, 1, 2 а второй - с аргументами 1, 2. Таким образом, первый поток захватывает компанию C1 на редактирование и, в случае успеха, вызывает метод setParent с аргументов - идентификатором компании C2 (будем писать C1.setParent(C2)), затем освобождает компанию. Второй захватывает компанию C2 и, в случае успеха, вызывает метод setInactive (будем писать C2.setInactive()), затем освобождает компанию. Для создания экземпляра компонента CompanyManagerBean используется вспомогательный метод getCompanyManager. public static void testCase(int num, int id, int parent) throws Exception { CompanyManager manager = getCompanyManager(); manager.editCompany(id); try { if (num==0) { manager.setParent(parent); } else { manager.setInactive(); } } finally { manager.freeCompany(); } } Рис. 5. Тестовый вариант Результаты выполнения данного теста зависят от того, в какой последовательности выполнялись удаленные вызовы методов (рис. 6). Если сначала полностью выполнится первый поток, а затем второй, то в результате для компании C1 будет установлен родитель C2, а статус компании C2 по-прежнему останется активным, так как метод isChildrenInactive обнаружит у C1 активного ребенка C2. Если сначала выполнится второй поток, а затем первый, то в результате статус компании C2 станет неактивным, но родитель для C1 установлен не будет, так как в момент проверки статус родительской компании C2 будет неактивным. Однако возможен еще и третий вариант, когда проверки в методах setParent и setInactive выполняются до того, как происходят изменения в базе данных, т.е. выполняются методы entityManager.merge. Например, пусть вызовы выполняются в следующей последовательности: t1.editCompany; t1.setParent; s1.find; t2.editCompany; t2.setInactive; s2.find; s1.merge; s2.merge; t1.freeCompany; t2.freeCompany где префиксы t1, t2 означают, что вызовы происходили из первого и второго потоков соответственно, а s1, s2 - соответствующие экземпляры CompanyManagerBean.Результатом такого выполнения будет установка для C1 родительской компании C2, а для C2 - установка неактивного статуса, что нарушает первое ограничение. Описание системы Рис. 6. Возможные результаты выполнения теста Таким образом, на данном примере можно видеть, что результат работы системы существенным образом зависит от порядка вызовов удаленных методов. Для полноценной проверки системы необходимо проверить работу системы для различных порядков вызова методов.

    Оптимизация разработки тестов

    Ключевым достоинством технологии Azov является возможность достичь чрезвычайно высокой производительности при разработке тестового набора. Затраты на создание одного теста уменьшаются при разработке тестов для интерфейсов большого размера (свыше 1000 операций). Основные трудозатраты приходятся на задачи, выполняемые разработчиком вручную, а именно изучение стандарта, формирование необходимых специализированных типов и отладка полученных тестов.
    Учитывая огромное количество подлежащих тестированию операций, даже небольшое уменьшение времени разработки теста в среднем приводит к значительному выигрышу для всего тестового набора в целом. Поэтому среда разработки должна иметь гибкий и удобный интерфейс, а также обеспечивать широкие возможности переиспользования уже созданных специализированных типов данных.

    В данной статье рассматривается задача

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

    Поиск различных порядков

    Итерация параметров в сценарном методе происходит с помощью итерационных переменных. Итерационые переменные отличаются от обычных переменных тем, что значения этих переменных вместе с именем сценарного метода определяют стимул автомата. Количество переходов по заданному стимулу в заданном обобщенном состоянии автомата зависит от результатов вызова сценарного метода с заданными значениями итерационных переменных. Если вызов сценарного метода приводит всегда к одному и тому же результату, то автомат имеет единственный переход по заданному стимулу; иначе переходов столько, сколько возможно различных результатов. Для того чтобы получить различные результаты вызова сценарного метода, требуется перебрать различные порядки событий в системе. В рассмотренном примере сценарного метода scenExample с использованием теста testCase возможны три различных результата, соответствующих вызову этого метода с итерационными переменными id=1, parent=2 в начальном состоянии {(0,0), (0,0), (0,2)}: {(0,0), (0,0), (1,1)}, {(0,0), (1,0), (1,0)}, {(0,0), (0,1), (1,0)}. Рассмотрим следующий пример. Предположим, что для рассмотренного примера иерархии компаний написан сценарий, в котором в качестве обобщенного состояния выбрано описанное выше мультимножество. В сценарии, во-первых, заданы сценарные методы для методов интерфейса CompanyManager. Данные сценарные методы позволяют получить разнообразные состояния иерархии: широкие, длинные, с разными конфигурациями активных и неактивных компаний. Эти методы также обеспечивают сильную связность графа автомата. Во-вторых, предположим, что имеется сценарный метод scenExample, описанный на основе метода testCase. Предположим, что обходчик в ходе построения тестовой последовательности попадает в состояние, соответствующее обобщенному состоянию {(0,0), (0,0), (1,1)}, в котором компания C1 - неактивная, а C2 - активная. В этом состоянии обходчик выполняет переход, соответствующий сценарному методу scenExample с итерационными переменными id=1, parent=2.
    На Рис. 10 показаны возможные пути выполнения без учета вызовов editCompany и freeCompany. Кроме того, на рисунке пунктиром показаны вызовы, перебора которых можно избежать за счет использования методов редукции частичных порядков. В соответствии с постановкой задачи метод поиска не имеет возможности отката в предыдущее состояние, поэтому каждый раз метод поиска проходит один из путей целиком и запоминает пройденное дерево. После этого обходчик продолжает обход автомата, и если метод сообщил, что в состоянии {(0,0), (0,0), (1,1)} есть неперебранные порядки, обходчик возвращается в это состояние. При этом от обходчика требуется лишь то, чтобы каждый переход был пройден столько раз, сколько это нужно методу поиска порядков. Никаких других изменений для обходчика не требуется. Поиск различных порядков Рис. 10. Деревья перебора для обобщенного состояния {(0,0), (0,0), (1,1)} Так как обобщенному состоянию {(0,0), (0,0), (1,1)} соответствует также состояние системы, в котором компания C1 - активная, C2 - неактивная, обобщенный откат может быть осуществлен в это состояние вместо первоначального состояния. Таким образом, поиск будет необходимо продолжить в этом состоянии. На Рис. 10 полужирными линиями показано соответствующее ему дерево различных порядков выполнения. Как можно видеть, в этом дереве отсутствует часть порядков, которые присутствовали в предыдущем состоянии. От метода поиска требуется перебрать все возможные порядки, встречающиеся во всех состояниях, соответствующих одному обобщенному состоянию. В рассматриваемом примере множеством порядков, которые необходимо перебрать, является дерево вершин, выделенных полужирными линиями.

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

    Специализированный тип фактически является композицией исходного типа данных и некоторого набора ограничений, то есть, по сути, состоит из достаточно независимых компонент. Эту особенность можно использовать для гибкого формирования новых специализированных типов из уже имеющихся в системе.
    В текущей реализации специализированный тип имеет шесть атрибутов: исходный тип, к которому применяются ограничения, значение типа, блоки дополнительной инициализации и финализации параметра, блок проверки требований и блок вспомогательного кода. Доступ к объекту осуществляется через шаблон, разворачиваемый впоследствии компоновщиком, поэтому все атрибуты практически независимы, за исключением того, что при работе с параметром предполагается, что его тип соответствует исходному типу данных.
    В системе реализовано однократное наследование специализированных типов. Каждому типу может быть сопоставлен один родитель. Если тип-наследник не содержит описания какого-либо из блоков, то используется описание этого блока у его непосредственного предка. Однако если последний в свою очередь заимствует соответствующее описание у своего предка, то в результате блок наследника окажется пустым. При таком подходе создавать цепочки наследования не имеет практического смысла, и в процессе разработки это, как правило, и не требуется. С другой стороны весьма полезно иметь некий базовый специализированный тип, обеспечивающий основную инициализацию, а уже на его основе строить типы, проверяющие требования. Тогда, при необходимости изменить инициализирующий код, это нужно будет сделать только в одном месте.
    Существующий механизм полезно в дальнейшем дополнить наследованием от двух родителей с одинаковым исходным типом одновременно. От одного родителя можно будет взять инициализирующий код, от второго – проверку возвращаемого значения, а в самом наследнике определить специфичную для тестируемой операции инициализацию и финализацию. Это позволит несколько сократить время разработки теста, а также изменять меньшее количество специализированных типов при необходимости внесения поправок.

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

    В качестве направления дальнейшего развития этого механизма можно предложить заимствование иерархии наследования самого языка C++. То есть, например, специализированный тип, уточняющий объект класса-наследника, может использоваться при инициализации параметра, имеющего тип класса-родителя.

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

    Проверки, добавляемые автоматически

    Компоновщик тестов может гарантировать только синтаксическую корректность составляемого им кода, основываясь при этом на знании сигнатур операций. Для построения параметров, которые должны приводить к сценарию нормального использования целевой операции, этой информации недостаточно, и разработчик должен пополнять ее, внося соответствующие уточнения в виде специализированных типов. Если же при автоматической инициализации параметра был использован неподходящий специализированный тип или нужный специализированный тип не был описан на момент сборки, то полученный тест может не достигать поставленных целей.
    Полностью исключить ошибочные ситуации автоматически невозможно, однако проверка промежуточных условий корректности везде, где это возможно, повышает уверенность в корректности теста в целом.
    Требование, проверка которого вставляется в тесты компоновщиком по умолчанию, заключается в неравенстве нулю указателей на объекты, расположенные в общей памяти. Фактически, все возвращаемые методами при инициализации параметров указатели на объекты проходят такую проверку.
    Более конкретные, и потому возможно более полезные, требования задаются разработчиками для каждого типа данных в отдельности и оформляются в виде так называемых общих специализированных типов. В качестве примера таких требований можно привести проверку свойств isNull() и isValid(), которые имеются у большого числа классов и характеризуют полноценность объекта.
    Недостатком этого подхода является необходимость применения дополнительного специализированного типа там, где автоматически внесенное ограничение может быть нарушено из разумных соображений.

    Разбиение на группы и порядок выполнения работ

    В процессе разработки пользователь может столкнуться с необходимостью проинициализировать параметр типа, для которого еще никто не составил подходящих специализированных типов. В этих случаях разработчику требуется отвлечься на время от текущей задачи, изучить соответствующий раздел стандарта и описать нужные уточнения для типа данных или подождать, пока другой разработчик сделает это, что зачастую занимает весьма значительное время. При разработке тестов для большого числа операции постоянное отвлечение на другие задачи сильно замедляет создание тестов, поскольку разработчику придется позже восстанавливать свои знания об отложенной на время операции.
    Для того чтобы минимизировать время на переключение контекста в процессе разработки, используется инструмент построения расписания работ. Множество операций, для которых создается тестовый набор, разбивается на группы в соответствии с их назначением, что позволяет разработчикам изучать один и тот же раздел стандарта сразу для целой группы функций или классов. Затем для каждой группы и для каждой операции внутри группы определяется приоритет, согласно которому следует вести разработку. Применительно к C++ удобнее оперировать не в терминах отдельных операций, а в терминах целых классов.
    Внутри группы каждому классу ставится в соответствие вес, равный разности между количеством классов этой же группы, имеющих методы, в которых этот класс выступает в качестве параметра, и количеством классов группы, которые используются в его методах. Чем больше вес класс, тем раньше следует создать уточняющего его специализированные типы. На практике это означает, что более высокий приоритет имеют низкоуровневые и вспомогательные классы, которые затем используются более сложными.
    На уровне групп веса рассчитываются таким же образом, с учетом всех входящих в группу классов. Однако здесь приоритеты имеют более общий смысл и отражают то, насколько часто в тестируемом интерфейсе используется та или иная подсистема.

    Сигналы и слоты

    В библиотеке Qt передача сообщений между объектами приложения осуществляется посредством механизма сигналов и слотов. Вызов метода-сигнала объекта отправляет в основной цикл обработки сообщение, содержащее параметры этого сигнала, которое перехватывается и обрабатывается методом-слотом другого или того же самого объекта. Канал связи между методами определяется макросом connect.
    Функциональность сигнала заключается в передаче слоту параметров через сообщение, неявно включая указатель на передающий объект, который может быть получен при помощи вызова QObject::sender() в теле слота. Именно ее и нужно проверять в процессе тестирования. В тесте формируется дополнительный объект, имеющий метод-слот с такими же, как и у сигнала, параметрами. Реализация этого метода проверяет значения пришедших параметров на эквивалентность посланным, а также неравенство нулю указателя на передающий сообщение объект, и выполняет выход из приложения. Таким образом, если сообщение не дошло до слота, то приложение не выйдет из основного цикла и будет завершено аварийно.
    Слот представляет собой обычный метод, работающий с переданными ему параметрами, но может дополнительно обращаться к объекту, пославшему сообщение, посредством глобальной переменной. Поэтому не все реализации методов-слотов будут работать вне контекста передачи сообщения. Для тестируемого слота подбирается существующий в системе подходящий сигнал с таким же, как и у слота, набором параметров. В тесте конструируется объект, имеющий этот метод-сигнал, который при помощи макроса связывается со слотом. Тестовое воздействие производится путем вызова сигнала с проинициализированными параметрами.

    Спецификация

    Для решения задачи спецификации, генерации тестовых данных и оценки покрытия в качестве основы мы будем использовать технологию UniTESK [,]. В UniTESK проверка корректности производится на основе задания пред- и постусловий функций, а также инвариантов [,]. Дополнительно для обеспечения возможности спецификации параллельных и распределенных систем введено понятие отложенной реакции [], с помощью которых удается моделировать внутренний недетерминизм в системе. Для рассмотренного примера приведенные ограничения на систему могут быть сформулированы в виде инвариантов на состояние (Рис. 7). В модельном состоянии theCompanies хранятся описания компаний: статус компании и информация о родителях. Инвариант Rule1_InactiveParent проверяет первое ограничение, а Rule2_Level4Hierachy - второе. invariant Rule1_InactiveParent() { for (Company company: theCompanies) { if (company.isActive()) { if (company.parent!=null && !company.parent.isActive()) return false; } } return true; } invariant Rule2_Level4Hierarchy() { for (Company company: theCompanies) { if (company.computeLevel()>4) return false; } return true; } Рис. 7. Спецификация ограничений

    Среда исполнения тестируемого метода

    Одним из ключевых компонентов приложения, написанного с использованием библиотеки Qt, является объект класса QApplication. Он содержит основной цикл обработки сообщений и служит для глобальной инициализации и финализации программы, в частности, если требуется, обеспечивает приложению доступ к графической подсистеме, задавая активный дисплей, визуальный и цветовой контексты. Также класс QApplication позволяет обращаться к таким параметрам системы как шрифты, палитра, интервал двойного нажатия, и параметрам, переданным приложению. Каждое приложение с графическим интерфейсом, использующее Qt, должно иметь лишь один объект этого класса.
    Система Qt не позволяет создавать объекты классов, осуществляющих вывод какого-либо изображения, до тех пор, пока не проинициализирован объект QApplication. Существует также ряд методов, функциональность которых проявляется только после того, как программа начнет обрабатывать события, происходящие в ней самой и в операционной системе.
    Поэтому компоновщик добавляет в начало каждого теста конструктор класса QApplication, а в конце теста вызывает метод exec полученного объекта, который запускает цикл обработки сообщений данного приложения.
    Из этого правила существует несколько исключений. Поскольку в приложении может существовать лишь один объект класса QApplication, то при тестировании его конструкторов и деструкторов попытка создать дополнительный объект приведет к падению теста. Класс QEventLoop описывает основной цикл обработки сообщений, поэтому его объект должен быть сконструирован до объекта QApplication. Также, статический метод самого класса QApplication setColorSpec() влияет на инициализацию графической подсистемы и должен вызываться до начала ее работы, а значит до того, как будет вызван конструктор класса QApplication.
    Разумеется, многие классы, не затрагивающие непосредственно графический интерфейс, могут работать и без привлечения объекта QApplication, поэтому дополнительная инициализация и вход в цикл обработки сообщений для их тестирования излишни. Однако библиотека Qt преимущественно используется для создания приложений, имеющих графический интерфейс, поэтому подобная избыточность, напротив, приближает среду, в которой вызывается тестируемая операция, к реальной.

    Структуры данных алгоритма

    Структуры данных, используемые алгоритмом, показаны на Рис. 12. Каждый переход Transition обязательно включает идентификатор. Дерево путей Tree ссылается на корневой узел Node, который включает упорядоченный список переходов transitions, индекс последнего выполненного перехода в этом списке lastIndex и список дочерних вершин children. В начале списка transitions идут выполненные переходы до lastIndex, далее идут невыполненные переходы. Список children содержит дочерние вершины, соответствующие переходам transitions, в том же порядке, размер списка - lastIndex+1. public class Tree { public Node rootNode = new Node(); } public class Node { List transitions; int lastIndex = -1; List children;//size=lastIndex+1 boolean isEndState = false; } public class Transition { public int id; } Рис. 12. Структуры данных алгоритма поиска

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


  • В.В.Кулямин, ИСП РАН

  • Дэвид Лордж Парнас
    Перевод: Виктор Кулямин

  • , , Труды Института системного программирования РАН

  • , , , Труды Института системного программирования РАН

  • , , , Труды Института системного программирования РАН

  • , , Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • , , Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • , , , Труды Института системного программирования РАН

  • , , , , , Труды Института системного программирования РАН

  • Зацепин Д.В., Шнитман В.З., Труды Института системного программирования РАН

  • С.В. Зеленов, Н.В. Пакулин, Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • Гингина В.В., Зеленов С.В., Зеленова С.А., Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • Д.Ю. Кичигин, Труды Института системного программирования РАН

  • К.А. Власов, А.С. Смачёв, Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • А. С. Камкин, Труды Института системного программирования РАН

  • , Российско-Армянский (Славянский) государственный университет, Ереван, Армения
    Труды Института системного программирования РАН
  • Задачи верификации ОС Linux в контексте ее использования в государственном секторе
    , , Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • А.В. Демаков, С.В. Зеленов, С.А. Зеленова, Труды Института системного программирования РАН

  • , Труды Института системного программирования РАН

  • С.В. Зеленов, Д.В. Силаков, Труды Института системного программирования РАН


  • Препринт Института Системного Программирования РАН

  • , , ,
    , ,

  • , ,

  • Сергей Мартыненко

  • Сергей Белов, менеджер проекта компании StarSoft Development Labs


    , #21/2005

  • ,

  • С.В. Зеленов, С.А. Зеленова
    Труды Института Системного Программирования РАН

  • Калинов А.Я., Косачёв А.С., Посыпкин М.А., Соколов А.А.,
    Труды Института Системного Программирования РАН.

  • Вячеслав Панкратов, Software-testing.ru

  • А.А. Сортов, А.В. Хорошилов.
    Труды Института Системного Программирования РАН

  • В. В. Кулямин, Труды

  • Новичков Александр, Ематин Виктор, Закис Алексей, Шкляева Наталья, Подоляк Ольга,

  • , автор проекта "Тестер",

  • , автор проекта "".
    Статья была опубликована в Журнале для профессиональных программистов "argc & argv", Выпуск № 6 (51/2003)

  • А.В. Баранцев, И.Б. Бурдонов, А.В. Демаков, С.В. Зеленов, А.С. Косачев, В.В. Кулямин, В.А. Омельченко, Н.В. Пакулин, А.К. Петренко, А.В. Хорошилов
    Труды

  • Александр Петренко, Елена Бритвина, Сергей Грошев, Александр Монахов, Ольга Петренко,

  • , , , ,
    В статье предлагается концепция автоматизированного построения тестовых наборов и тестовых оракулов для тестирования оптимизаторов. Используется подход, основанный на генерации тестов из моделей. Основные идеи модельного подхода заключаются в следующем: 1) модельный язык неявно разбивает множество программ целевого языка на классы эквивалентности; 2) критерий тестового покрытия формулируется в терминах модельного языка; 3) в соответствии с выбранным критерием генерируется набор тестов. В работе описывается схема построения тестового оракула, который проверяет сохранение семантики программы после ее оптимизации.

  • , , , ,
    В статье представлен опыт разработки тестового набора для реализации протокола IPv6. Для разработки тестового набора использовался метод разработки тестовых наборов на основе формальных спецификаций UniTesK, развиваемый в Институте системного программирования РАН. В качестве объекта тестирования была выбрана реализация IPv6 от Microsoft Research. В статье подробно описывается устройство полученного тестового набора и обсуждаются результаты проекта.

  • Виктор Ематин, Борис Позин (),


  • В статье рассказывается о средстве управления изменениями Rational ClearQuest, которое позволяет совместно с инструментами тестирования тщательно документировать встречающиеся при испытаниях дефекты.


  • В статье рассказывается о программном продукте ClearQuest от компании Rational, который помогает разработчикам и тестировщикам находить и документировать ошибки в разрабатываемом ПО.



  • Удобство и функциональность интерфейса

    Инструмент разработчика тестов в первую очередь предназначен для доступа к базе данных с дополнительной информацией о тестируемых операциях, а именно позволяет создавать и редактировать специализированные типы, просматривать состояние работ в целом и для каждой операции в частности, а также собирать статистические данные. Скорость разработки тестового набора существенно зависит от удобства этого инструмента и его функций, позволяющих сократить время на выполнение рутинных задач.
    Написание теста можно существенно упростить, если предоставить разработчику легкий доступ к справочной и отладочной информации. Поэтому каждая операция сопровождается ссылкой на ее описание в стандарте. Инструмент также позволяет просматривать сгенерированный компоновщиком код, запускать тесты на целевой машине и получать подробную информацию об ошибках, возникших на этапах генерации, компиляции и исполнения.
    Возможны ситуации, когда несколько операций реализуют сходную функциональность и при этом имеют одинаковую сигнатуру, так что набор специализированных типов, которыми необходимо уточнить параметры, для них один и тот же. Разумно определить уточнения только для одной из таких операции, а затем автоматически их дублировать. Для этой цели существует инструмент импортирования, который позволяет производить поиск таких случаев сразу для всех методов класса и выбирать источник копирования из списка возможных кандидатов. Дополнительно, с помощью него можно автоматически создавать копии существующих специализированных типов с изменением типов уточняемых ими параметров.
    Быстрому поиску подходящих для подстановки специализированных типов служит система их названий. Название должно нести в себе краткую информацию об ограничениях, содержащихся в типе, например, содержать начальное значение параметра, если таковое имеется, или префикс R_, если выполняется проверка возвращаемого значения. Общеупотребительные типы принято называть с помощью имен, начинающихся с Create, в этом случае инструмент импортирования в одном из режимов работы дублирует их автоматически.

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

    При создании инициализирующей последовательности вызовов для специализированного типа ее параметры не обязательно конкретизировать. В этом случае там, где это нужно, указывается символическая конструкция, на место которой компоновщик подставляет правильно построенный объект, возможно используя при этом какой-нибудь специализированный тип. В некоторых случаях процесс описания специализированного типа можно упростить, используя шаблоны кода для часто встречающихся случаев. Например, применяется вставка вызовов всех методов, проставляющих атрибуты объекта, названия которых в Qt начинаются на set.

    в различных ситуациях остается одним

    Тестирование работы программы в различных ситуациях остается одним из самых широко используемых способов демонстрации ее корректности, особенно для достаточно сложного приложения. Но все существующие методики создания тестов, обеспечивающие их полноту в соответствии с некоторыми естественными критериями, требуют весьма значительных трудозатрат.
    Однако в ряде случаев, например, при тестировании же объемных интерфейсов, содержащих тысячи операций, полное и тщательное тестирование обходится слишком дорого, а иногда и вовсе не требуется для всех элементов интерфейса системы. Вместо этого проводится тестирование работоспособности, то есть проверяется, что все функции системы устойчиво работают хотя бы на простейших сценариях использования. Уже затем, для наиболее критической части интерфейса разрабатываются детальные тесты.
    Именно для решения таких типов задач предназначена технология Azov [1], созданная в 2007 году в Институте системного программирования. Технология нацелена на разработку тестов работоспособности, вызывающих тестируемые операции в правильном окружении с какими-нибудь допустимыми значениями параметров, характеризующими простейшие сценарии использования этих операций. При этом она позволяет существенно автоматизировать создание теста.
    Область применимости технологии включает те случаи, когда информация об интерфейсе доступна в хорошо структурированном, годном для автоматической обработки виде. При этом трудозатраты на создание одного теста существенно меньше, если тестируемый интерфейс содержит большое количество операций.
    В частности технология Azov была использована при создании тестового набора для бинарных операций библиотеки Qt3, предназначенной для разработки приложений с графическим интерфейсом [4] и входящей в стандарт Linux Standard Base [2,3] (LSB). Стандарт включает в себя около 10000 методов и функций библиотеки и хранит информацию об их синтаксисе в своей базе данных.
    Тестирование библиотек, написанных на языке C++, в частности Qt3 [4], является достаточно трудоемкой задачей. Поведение метода может зависеть не только от передаваемых ему параметров и глобального состояния системы, но и от состояния объекта, которому он принадлежит. Сама же библиотека графического интерфейса содержит дополнительные механизмы, которые также оказывают существенное влияние на работу методов.
    Поэтому базовая методика, предлагаемая технологией Azov, должна быть дополнена набором средств, позволяющих формировать работоспособные тесты на языке C++ во всевозможных ситуациях, возникающих при тестировании элементов интерфейса, и учитывающих особенности библиотеки Qt [3], в частности механизм передачи сообщений.


    Воздействия на систему требуется производить не только последовательно, но и асинхронно, т.е. подавать их из различных потоков, процессов или с разных машин. Результаты, выдаваемые системой после подачи воздействия, зависят от чередований событий внутри нее. В системах, взаимодействующих посредством удаленного вызова методов, такими событиями являются удаленные вызовы методов. Результат может зависеть от порядка вызова этих методов. Соответственно, метод оценки корректности должен обеспечивать проверку результатов для любых возможных чередований событий. Кроме того, для исчерпывающего тестирования необходимо проверить результаты для различных чередований, т.е. уже недостаточно проверить одно из возможных чередований. Эта особенность должна быть учтена при оценке полноты тестирования. Особо важную роль в распределенных системах играет задача перебора различных чередований событий. В силу того, что появление того или иного порядка событий зависит от многих факторов, таких как планирование процессов в операционной системе, управлять которыми разработчик системы не имеет возможности, нет гарантии того, что данный порядок появится в процессе воздействия на систему. Перебор только части возможных чередований оставляет возможность проявления ошибки на непроверенных чередованиях. Ошибки такого рода крайне тяжело выявлять и исправлять; даже если такая ошибка обнаружена, ее трудно повторить, так как вероятность появления порядка, на котором проявляется ошибка, может быть крайне мала. Целью данной работы является построение метода тестирования, позволяющего гарантировать, что будут проверены все различные чередования событий, приводящие к разным результатам. Задача перебора различных чередований событий традиционно решается в методах проверки моделей (model checking), которые позволяют найти последовательность событий, нарушающих данную темпоральную формулу или доказать, что такой последовательности не существует. Методы проверки моделей работают с замкнутой системой, т.е.


    системой, которая не принимает входных воздействий и не выдает реакций. Для верификации других систем, например, предоставляющих программный интерфейс, требуется предоставить окружение, взаимодействующее с системой. От размеров окружения напрямую зависит количество состояний, получающихся в процессе поиска. С другой стороны, от окружения зависит качество верификации, и далеко не всегда можно ограничиться простым окружением. Методы проверки моделей обладают рядом ограничений:
  • Взрыв количества состояний;
  • Требование функции сравнения состояний;
  • Требование отката. Взрыв состояний возникает из-за большого количества состояний в программных системах. Кроме того, если система не является замкнутой, то к взрыву часто приводит попытка проанализировать систему с наиболее общим окружением, которое воздействует на систему произвольным образом. Известны методы целенаправленной генерации окружений, например, [], однако данные методы не применимы для генерации окружений для распределенных систем. Для сокращения пространства перебора широко используются методы редукции частичных порядков []. Эти методы основаны на понятии зависимости событий. Зависимости можно выявить до выполнения системы или при выполнении одного из зависимых событий. Эксперименты, проведенные в [], показывают, что такие методы позволяют значительно сократить пространство перебора. Классические методы проверки моделей для осуществления поиска требуют наличия функции сравнения состояний. Для того чтобы сравнивать состояния, требуется хранить информацию об этих состояниях. Поиск без сохранения состояний (stateless search) [] позволяет осуществлять поиск для систем со сложными состояниями, которые сложно сохранять, а также для систем с большим количеством состояний. Однако поиск накладывает ограничения на систему - требуется, чтобы в ее пространстве состояний не было циклов. Широко используемой возможностью в методах поиска является возможность отката (возврата) в предшествующее состояние. Такая возможность может обеспечиваться за счет механизмов отката в реализации (например, с использованием специальной виртуальной машины []) или с помощью перевыполнения, т.е.


    сброса системы в начальное состояние и повторного выполнения. Мы сформулируем задачу для случая, когда в реализации нет механизма отката и нет возможности сброса в начальное состояние. Мы будем предполагать, что вместо отката алгоритму поиска предоставляется возможность возврата в одно из состояний, которое принадлежит некоторому множеству, называемому обобщенным состоянием. Мы предполагаем, что последовательность воздействий на систему строится на основе обхода графа состояний конечного автомата [,]. Состояния автомата являются множествами исходных состояний системы и называются обобщенными состояниями. Множества выбираются, как разбиения состояний системы на классы эквивалентности. Возможность обобщенного отката реализуется за счет требования сильной связности графа состояний, необходимого для построения обхода графа. Таким образом, обобщенный откат - это путь в графе состояний, ведущий в требуемое обобщенное состояние. В следующем разделе описан пример, на котором будет продемонстрирован метод тестирования. В разделе "Алгоритм поиска" описан алгоритм, позволяющий перебирать различные порядки вызовов методов. Далее следует описание архитектуры, необходимой для его работы.

    В статье описываются техники, позволяющие

    В статье описываются техники, позволяющие адаптировать общий подход технологии Azov для создания тестов работоспособности для классов языка C++ и библиотеки Qt3. Большинство из таких техник могут быть использованы и в других системах.
    На их основе была реализована система генерации тестов, позволившая с приемлемыми усилиями построить тестовый набор для 10000 методов и функций из библиотеки Qt3, описанных в стандарте LSB. При этом, несмотря на неполноту документации, производительность разработки тестов составила в среднем 70 операций в день на человека.
    При помощи простейших тестов работоспособности удалось обнаружить порядка 10 ошибок в реализации методов. В процессе разработки тестового набора удалось обнаружить и исправить достаточно большое количество ошибок в базе данных LSB, а также пополнить ее недостающей информацией.
    Таким образом, показана применимость на практике и высокая эффективность технологии Azov, пополненной набором описанных в данной работе техник.


    В работе разработан метод тестирования систем, которые построены на основе компонентов, взаимодействующих с помощью удаленного вызова методов. Метод позволяет использовать удачные решения технологии UniTESK, такие как формулировка требований в виде формальных спецификаций и построение тестовой последовательности на основе обхода автоматов. Вместе с тем, метод гарантирует, что для всех тестовых воздействий, выполняемых в каждом переходе автомата, будут проверены все различные порядки вызовов методов. В работе показано, что для систем, построенных по технологии EJB 3.0, этого достаточно для получения всех возможных результатов выполнения. Тем самым, удается гарантировать корректную работу системы в соответствии со спецификацией вне зависимости от "погодных" условий, в которых работает система, таких как планирование процессов и потоков в операционной системе, а также задержки при сетевых взаимодействиях. Метод тестирования был применен к нескольким системам. Были обнаружены проблемы, которые не проявляются или проявляются редко при запусках без перебора различных порядков. Благодаря тому, что одни и те же тесты можно использовать как с перебором, так и без него, отладку тестов можно проводить без перебора, а затем проводить более тщательное тестирование, используя возможность перебора. Такой подход в проведенных экспериментах позволил значительно сократить время разработки тестов. Использование возможности перебора не для всей системы вместе с окружением целиком, а для отдельных тестовых воздействий позволило значительно сократить пространство перебора. Использование поиска без сохранения состояний позволяет сократить использование памяти. В проведенных экспериментах время работы тестов не превышало нескольких минут.

    Защищенные методы

    Использование защищенных методов также требует некоторых дополнительных построений. Область видимости таких методов ограничена методами класса, которому принадлежит защищенный метод, или его наследников.
    При построении теста компоновщик генерирует описание класса, наследующего классу, содержащему защищенный метод. В нем определяется дополнительный общедоступный метод, являющийся оберткой защищенного. Параметры обоих методов совпадают, так что везде в тесте можно обращаться к построенному методу сгенерированного класса, подразумевая вызов требуемого защищенного метода.
    Из этого правила имеется исключение, а именно случай, когда метод имеет в качестве одного из своих параметров объект защищенного типа данных. В рамках Qt, например, встречаются защищенные перечисления (protected enum). В такой ситуации цепочка инициализации объекта нужного типа генерируется компоновщиком в теле метода-обертки, а сам этот общедоступный метод имеет на один параметр меньше.
    Следует также добавить, что при таком подходе защищенные in-charge конструкторы и деструкторы не могут быть протестированы, поскольку они предназначены для работы с объектом исходного класса, а вызвать их можно только из объекта наследующего класса.

    Завершение теста и отложенное выполнение целевого воздействия

    В подавляющем большинстве случаев приложение, осуществляющее обработку сообщений, завершает свою работу по получении некоторого внешнего сигнала. Например, приложение, имеющее графический интерфейс, в отсутствие исключительных ситуаций работает до тех пор, пока пользователь явно не даст команду на его закрытие.
    Симуляция такого воздействия выходит за рамки тестирования работоспособности, поэтому завершение работы осуществляется тестом самостоятельно. При помощи сигнала singleShot класса QTimer через определенное время после запуска приложения вызывается слот quit класса QApplication, что и приводит к выходу из цикла обработки сообщений. Тесты запускаются параллельно, поэтому значительных задержек из-за относительно большого времени жизни каждого из них в самом процессе тестирования не возникает.
    Таким образом, если он завершился самостоятельно в отведенные временные рамки, и при этом не возникло ошибок, то тест считается прошедшим. Если же он не завершился в нужное время, то тест уничтожается загрузчиком, и считается, что тест не прошел.
    Объекты классов-наследников QDialog имеют свой собственный локальный цикл обработки сообщений. Если локальный цикл получает управление до того, как будет осуществлен вход в основной цикл, то сигнал о завершении работы, испускаемый самим приложением, не будет обработан. Возникает необходимость воздействия на диалоговое окно со стороны внешнего источника. В дополнение, некоторые методы не могут работать в основном режиме до тех пор, пока не запущен механизм обработки сообщений приложения. Поэтому возникает необходимость тестировать ряд операций уже после того, как управление передано основному циклу.
    Все содержимое теста, необходимое для вызова такой целевой операции, переносится в метод-слот дополнительно сгенерированного класса. С помощью сигнала singleShot этот слот вызывается через некоторое время после запуска приложения, но до его завершения.


    Тестирование софта - статьи

    Два потока управления, работающих строго поочередно.

    Пример: тестовая конфигурация такого рода была построена для тестирования Verilog-моделей []. В данном случае, как и в случае 3, целевая система обладает собственным активным потоком управления, на который можно влиять извне; отличается только реализация теста. При этом и целевая, и тестовая система ведут себя каждая как единственный активный поток, что позволяет запускать целевую систему в обычном для нее режиме, пользуясь при этом классической для UniTesK архитектурой тестового сценария, рассчитанной на единственный поток с немедленными реакциями. Выполнение в каждый момент времени в точности одного из этих потоков достигается средствами межпоточной синхронизации. Такая реализация удобна тем, что оба потока управления (и целевой системы, и теста) обладают собственными стеками. Модель целевой системы в данном случае аналогична используемой в случае 1, различие только в реализации тестовой среды. Схема взаимодействия компонентов при этом выглядит следующим образом:
  • целевая система инициализируется и вызывает встроенный в нее медиаторный код, который запускает в отдельном потоке тестовый сценарий UniTesK;
  • тестовый сценарий инициализируется и переходит в режим ожидания сигнала;
  • целевая система вызывает медиаторный код, который посылает потоку тестового сценария сигнал, описывающий текущее состояние целевой системы, и переходит в режим ожидания сигнала;
  • тестовый сценарий получает сигнал от медиатора и анализирует его корректность в соответствии со спецификацией;
  • тестовый сценарий выбирает очередное тестовое воздействие по правилам, описанным для случая 1; если такое воздействие не найдено, то он завершает работу: иначе сценарий передает медиатору сигнал, описывающий воздействие, и переходит в режим ожидания.
  • получив сигнал, медиатор выполняет описанное в нем тестовое воздействие и получает ответную реакцию целевой системы;
  • медиатор посылает тестовому сценарию сигнал, описывающий реакцию целевой системы и ее состояние, и переходит в режим ожидания;
  • переходим к шагу 4. Более подробно об архитектуре тестовой системы, применяемой в рассматриваемой конфигурации, можно прочитать в [].

    Классификация тестируемых систем в соответствии с конфигурацией потоков управления

    В технологии UniTesK целевая система рассматривается как "черный ящик". На входы ей подаются стимулы, а на выходах наблюдаются реакции. Для целей тестирования "черного ящика" важны только возможные соотношения между подаваемыми стимулами и получаемыми реакциями, и неважно, что именно происходит внутри целевой системы. Именно на основе этого принципа определяются границы целевой системы, и строится предлагаемая классификация. Например, пусть имеются три различных системы. Система А обладает одним собственным потоком управления и может в ответ на полученный стимул начать некую собственную активность ограниченной продолжительности; в ходе этой активности она может как принимать новые стимулы, так и выдавать реакции на старые. Система Б аналогична по внешнему поведению, но внутри нее имеется несколько собственных потоков управления. Система В никакой собственной активности не ведет, но также может сразу выдать в ответ на стимул несколько реакций, причем между системой В и тестовой системой находится сеть, которая вносит недетерминированные задержки в передачу как стимулов, так и реакций (случай тестирования через сложную среду). С точки зрения тестирования все эти три системы эквивалентны: про них известно только то, что после подачи стимула в течение некоторого времени возможен как приём реакций от них, так и подача новых стимулов. Поэтому с точки зрения тестирования все они относятся к классу систем с отложенными реакциями []. В случае систем А и Б это определяется наличием их собственных активных потоков, а в случае системы В - функционированием сетевого оборудования и системного ПО. В последнем случае у теста может отсутствовать возможность определить, по чьей вине реакция была искажена, потеряна или задержана - системы В или промежуточной сети. Поэтому разумно включить в рамки целевой системы также и сеть и тестировать именно совместное поведение системы В и сети - вместе они составляют некую систему с отложенными реакциями В', правильность работы которой и проверяется.
    Если же среда допускает возможность сделать такую проверку (например, взаимодействие тестовой и целевой систем происходит по протоколу TCP, который маскирует все эти искажения), то система В может быть отнесена к более простому классу, позволяющему применять более простые модели и инструменты тестирования. В ходе исследований выделены следующие различающиеся с точки зрения тестирования "черного ящика" конфигурации потоков управления.
  • Целевая система может быть представлена в виде API, предполагающего однопоточное выполнение и не обладающего собственным потоком управления. Существует только один активный поток, принадлежащий тестовой системе. Целевая система не выполняет никаких действий и не меняет свое состояние вне вызовов из тестовой системы.
  • Аналогично п.1, но целевой API предполагает работу в многопоточной среде. Собственным потоком управления целевая система не обладает. Необходимо протестировать корректность работы при параллельном вызове методов целевого интерфейса. Существует несколько активных потоков, все они принадлежат тестовой системе. Целевая система не выполняет никаких действий и не меняет свое состояние вне вызовов из тестовой системы.
  • Существует единственный поток управления, не контролируемый тестом. Активный поток иногда передает управление тесту, но тот обязан быстро вернуть управление, и при этом он не может выполнить свою работу за доступное ему за один раз время. Реакции на все или некоторые стимулы поступают только при следующих вызовах теста. Никакие внешние сообщения в целевую систему не поступают.
  • Аналогично п. 3, но возможны внешние воздействия на целевую систему или ее собственная длительная активность.
  • Существуют два потока управления, один из которых принадлежит целевой системе, другой - тесту; при этом в любой момент времени активен ровно один из них.
  • Тест - активный поток, целевая система - один или несколько полуактивных потоков: при получении стимула она может выдавать на него реакции в течение ограниченного времени, после чего стабилизируется и уже не может менять состояние или выдавать реакции до получения следующего стимула.Во время активности целевая система продолжает принимать новые стимулы, причем реакции на них могут выдаваться в произвольном порядке, и получение новых стимулов может влиять на реакции, выдаваемые системой в ответ на старый стимул.
  • Кроме теста, существуют и другие активные потоки. Целевая система может менять состояние и выдавать сколь угодно много реакций в течение неограниченного времени в ответ на полученный стимул и даже при отсутствии стимулов. Других конфигураций активных потоков, существенно отличающихся с точки зрения тестирования, обнаружить не удалось. Прежде чем приступить к подробному рассмотрению перечисленных видов конфигураций активных потоков, введем несколько определений.

    Литература

    1. - сайт, посвященный технологии тестирования UniTesK и поддерживающим ее инструментам.
    2. В. В. Кулямин, А. К. Петренко, А. С. Косачев, И. Б. Бурдонов. Подход UniTesK к разработке тестов. Программирование, 29(6), стр. 25-43, 2003.
    3. В. П. Иванников, А. С. Камкин, В. В. Кулямин, А. К. Петренко. Применение технологии UniTesK для функционального тестирования моделей аппаратного обеспечения. Препринт 8. Институт системного программирования РАН. Москва, 2005.
    4. I. Bourdonov, A. Kossatchev, A. Petrenko, and D. Galter. KVEST: Automated Generation of Test Suites from Formal Specifications. FM'99: Formal Methods. LNCS 1708, pp. 608-621, Springer-Verlag, 1999.
    5. V. Kuliamin, A. Petrenko, I. Bourdonov, and A. Kossatchev. UniTesK Test Suite Architecture. Proc. of FME 2002, LNCS 2391, pp. 77-88, Springer-Verlag, 2002.
    6. V. Kuliamin, A. Petrenko, N. Pakoulin, I. Bourdonov, and A. Kossatchev. Integration of Functional and Timed Testing of Real-time and Concurrent Systems. Proc. of PSI 2003, LNCS 2890, pp. 450-461, Springer-Verlag, 2003.
    7. И. Б. Бурдонов, А. С. Косачев, В. В. Кулямин. Неизбыточные алгоритмы обхода графов: недетерминированный случай. Программирование, 30(1), стр. 2-17, 2004.


    Определение

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


    Целевая система называется системой с немедленными реакциями, если с точки зрения используемой модели она производит только немедленные реакции на все стимулы.


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

    Пассивные параллельные системы

    Примеры: Реализация любой подсистемы стандартной библиотеки libC, заявленная как устойчивая к многопоточности ("thread-safe"). Библиотека управления многопоточностью libThreads. В технологии UniTesK задача тестирования систем данного класса решается с помощью специальных сценариев, определяющих наборы параллельных воздействий [] Кроме проверок поведения целевой системы, выполняемых после применения каждого тестового стимула, проверяется следующий постулат сериализации: "Результат любого параллельного применения стимулов к целевой системе аналогичен некоторому последовательному их применению". Именно необходимость проверки данного постулата не позволяет отнести такие системы к классу 1. Для проверки постулата модель целевой системы включает в себя сериализацию событий (примененных стимулов и полученных реакций), которая строится следующим образом.
  • Все события (применение стимулов и получение связанных с ними реакций), произошедшие в ходе выполнения тестового сценария, протоколируются.
  • На множестве событий строится частичный порядок: линейный порядок определен для всех событий, произошедших в пределах одного потока управления; на основании дополнительных знаний о целевой и тестовой системах может быть определен частичный порядок событий, произошедших в разных потоках.
  • Строятся все возможные линейные порядки событий, удовлетворяющие построенному частичному порядку.
  • Работа целевой системы признается корректной, если найден хотя бы один такой линейный порядок событий, который удовлетворяет спецификации целевой системы. Замечание: В процессе выполнения теста существует несколько потоков управления, принадлежащих тестовой системе, и, соответственно, в используемой в это время модели целевой системы может иметься несколько временных линий. Однако после построения сериализации все события выстраиваются на единой временной линии, так что к моделируемой системе становится полностью применимым данное выше определение системы с непосредственными реакциями. Именно на этом основании системы данного класса отнесены к классу систем с немедленными реакциями.

    Пассивные последовательные системы

    Примеры: большинство подсистем стандартной библиотеки libC. Целевая система считается относящейся к данному классу, если она удовлетворяет следующим требованиям:
  • в ответ на каждый подаваемый стимул она выдает пустую или немедленную реакцию;
  • вне вызовов из тестовой системы целевая система не выполняет никаких действий и не меняет свое состояние;
  • поставленная задача тестирования не включает проверку работы целевой системы в многопоточном окружении, или такая работа невозможна. Исходно технология UniTesK разрабатывалась для тестирования именно данного класса систем. Рассмотрим вкратце способ тестирования, применяемый в данном случае. При создании тестового сценария используется абстракция обобщенных состояний, где каждое обобщенное состояние соответствует некоторому классу эквивалентности реализационных состояний целевой системы. Обычно в модели обобщенных состояний используется значительно более высокий уровень абстракции, чем в модели, используемой для проверки корректности работы целевой системы. В любой момент времени целевая система находится в некотором однозначно определенном обобщенном состоянии и может изменить его только в результате применения тестового стимула. После применения некоторого стимула в некотором обобщенном состоянии целевой системы анализируется полученная реакция и вычисляется новое обобщенное состояние. Задача тестового сценария состоит в обходе ребер ориентированного графа, вершины которого соответствуют обобщенным состояниям целевой системы, а ребра - применяемым к ней обобщенным стимулам, где каждый обобщенный стимул соответствует некоторому классу эквивалентности возможных стимулов, применяемых к целевой системе. Граф строится интерактивно, по мере выполнения теста, на основе правил вычисления текущего обобщенного состояния целевой системы и правил перебора стимулов, применяемых в зависимости от обобщенного состояния. Модель обобщенного состояния и обобщенных стимулов строится так, чтобы при повторном применении обобщенного стимула в некотором обобщенном состоянии целевая система всегда переводилась в то же самое обобщенное состояние, в которое она ранее переходила при применениях этого стимула в том же состоянии. Также накладывается дополнительное требование сильной связности графа обобщенных состояний. Практика тестирования реальных систем показывает, что для систем, удовлетворяющих минимальным требованиям к детерминизму поведения, построение модели, удовлетворяющей изложенным требованиям, возможно. Способы тестирования, применяемые в технологии UniTesK для данной конфигурации активных потоков, более подробно описаны в [, ]. Случай недетерминированного поведения целевой системы частично рассматривается ниже, но в целом выходит за рамки данной работы []

    Полуконтролируемое тестирование

    Пример: Аналогично случаю 3, но целевая система регулярно выполняет по расписанию какую-то работу, влияющую на ее состояние. В качестве такого дополнительного источника воздействий на целевую систему при этом выступает таймер, неподконтрольный тестовой среде. Если в данном примере тестовая среда имеет возможность изолировать таймер и создавать стимулы от него целевой системе тогда и только тогда, когда это необходимо для целей тестирования, то такую целевую систему следует отнести к классу 3. Из-за наличия неконтролируемых источников воздействий на целевую систему данный случай не может быть сведен к случаям 3, а тем более случаям 1 и 2, поскольку в рассмотренных выше случаях предполагается полная подконтрольность всех стимулов, получаемых целевой системой. При этом для целей тестирования неважно, сколько активных потоков реально существует и откуда поступают воздействия на целевую систему: от внешних активных потоков или от других объектов, существующих на аналогичных условиях под управлением единого потока управления. В любом случае, когда возможна неконтролируемая тестом активность целевой системы, сложность модели целевой системы времени тестирования будет аналогична сложности модели, используемой в случае 7, который обсуждается ниже. С точки зрения особенностей реализации тестовой системы, она аналогична случаю 3.
    Именно из доступных ограничений такого рода и нужно исходить при моделировании. Мы можем предложить только некоторые общие рекомендации по уменьшению степени недетерминизма модели, позволяющие свести систему такого рода к одному из более простых случаев.
  • По возможности изолировать целевую систему, помещая ее в "песочницу" ("sandbox"), в которой все связи с внешним миром заменены связями с соответствующими заглушками - компонентами тестовой системы. Это классическая техника, однако в ряде случаев она не может быть применена, например, если целевая система - большой монолитный компонент, обладающий собственными активными потоками управления, и отсутствует доступ к исходному коду.
  • Моделировать только ту часть целевой системы, поведение которой не зависит от неконтролируемых активных потоков. Корректность реакций целевой системы на воздействия, неподконтрольные тесту (и, возможно, непосредственно им не наблюдаемые), при этом не проверяется.
  • Конструировать модель целевой системы так, чтобы она отражала только те свойства целевой системы, которыми она должна обладать при любых сценариях ее работы, независимо от возможных последствий взаимодействия с неконтролируемыми потоками управления. При этом моделируемые аспекты являются инвариантами модели относительно неподконтрольных воздействий. Технология UniTesK изначально приспособлена для такого способа решения проблемы, потому что основана на вычислении предикатов над поведением целевой системы: спецификация описывает не точное поведение системы, а только условия, которым это поведение должно удовлетворять. Такой подход позволяет описывать и тестировать системы с достаточно высоким уровнем недетерминизма [, , ] С помощью приведенных методов в большинстве случаев задача тестирования может быть сведена к задаче тестирования системы более простого класса: 1, 2 или 6. Однако общей методики решения этой задачи не существует, как не существует и общего для любых систем способа автоматического построения их моделей для целей тестирования.Задача построения моделей целевой системы и преобразования моделей в более "удобный" для тестирования вид требует творческого подхода, и не существует единого метода, решающего эти задачи для любых целевых систем

    Система с отложенными реакциями

    Пример: FTP-сервер при одновременной работе нескольких клиентов. После получения запроса на сканирование каталога в течение какого-то времени выполняется сканирование, и клиент получает сетевые пакеты с результатами запроса. При этом параллельно другие клиенты могут модифицировать содержимое того же каталога. Поскольку стандарт не гарантирует однозначность результата сканирования при одновременных модификациях сканируемого каталога, результаты запроса могут различаться в зависимости от действий других клиентов, выполняемых в процессе обработки запроса. В данном случае для задачи тестирования неважно, сколько реально существует активных потоков. В любом случае, в течение некоторого времени после применения тестового стимула возможны как получение от целевой системы реакций на этот стимул, так и подача новых стимулов. Именно эти критерии заставляют относить целевую систему к данному классу систем. Поскольку в случаях 1, 2 и 5 предполагаются только немедленные реакции, к ним не могут быть сведены системы данного класса. Для систем с отложенными реакциями в технологии UniTesK разработана специальная архитектура теста []: В тестовом сценарии имеется один основной активный поток. Этот поток регулярно опрашивает входные каналы для анализа реакций, полученных от целевой системы. При необходимости создаются вспомогательные потоки управления, в которых выполняются так называемые "кэтчеры" (catcher). Задача кэтчера - регистрация реакций, полученных от целевой системы, и передача их во входные каналы основного потока управления тестового сценария. Кэтчеры могут также встраиваться в существующие потоки управления целевой системы. Модель целевой системы времени тестирования включает в себя информацию о тех ранее примененных стимулах, в ответ на которые еще возможно получение реакций (стационарным считается такое модельное состояние целевой системы, в котором это множество пусто). При получении реакции проверяется, допускает ли спецификация получение реакции такого рода в данный момент.
    Получение неожиданной реакции ( в том числе получение любой реакции в стационарном модельном состоянии системы), а также неполучение ожидаемой реакции в течение допустимого времени рассматриваются как ошибки. Тестовый сценарий перебирает серии тестовых стимулов и применяет их к целевой системе. После подачи такой серии сценарий ждет перехода целевой системы в стационарное состояние, анализируя при этом все полученные от нее реакции. Задача тестирования определяется как обход ребер ориентированного графа, вершины которого соответствуют обобщенным состояниям целевой системы, а ребра - применяемым к ней обобщенным стимулам, где каждый обобщенный стимул соответствует некоторому классу эквивалентности серий применяемых к целевой системе стимулов. Граф состояний строится интерактивно, по мере работы теста. Все события протоколируются, а после завершения теста строится их сериализация. Правила построения частичного порядка событий описываются спецификацией целевой системы и в общем случае могут быть более сложными, чем в случае 2. Работа целевой системы признается корректной, если найдена хотя бы одна сериализация событий, удовлетворяющая спецификации.

    Следствия из определений:

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

    Тестирование с отложенными реакциями

    Пример: Object Request Broker (ORB) RACE, разработанный для однопроцессорных встроенных систем. И тест, и целевая система - объекты, существующие под управлением этого ORB. Стимулы и реакции - сообщения ORB, причем сообщения передаются между объектами только в промежутке между получением ими управления. В данном случае для задачи тестирования неважно, сколько реально существует активных потоков (см. ниже обсуждение случая 6). В любом случае тест не обладает собственным потоком управления и может в течение некоторого времени после применения тестового стимула получать реакции на него от целевой системы и выдавать новые стимулы. Именно эти критерии заставляют относить целевую систему к данному классу систем. Если активный поток не является единственным, то требуется построение модели, аналогичной той, которая используется в случае 6 (см. ниже). Если такой поток - единственный, то данный случай проще случая 6, поскольку не требуется специальное построение сериализации событий. Однако случай 3 не может быть сведен к случаям 1 или 2, потому что возможны отложенные реакции целевой системы. Задача построения модели для систем данного класса решается аналогично случаю 6. Однако для системы с конфигурацией такого рода потребовалась особая реализация обходчика: обычный тест UniTesK выполняется в собственном потоке управления с собственным стеком, в котором хранятся вызовы методов и их локальные переменные; в данном же случае тест не обладает собственным стеком вызовов и не имеет возможности выполнять какие-либо длительные действия. Упрощенный алгоритм работы тестовой системы в случае 3 выглядит следующим образом:
    1.обеспечить регулярное получение управления в активном потоке (например, подписаться на получение сигналов от таймера);
    2.при очередном получении управления:
    2.1проверить реакции от целевой системы, полученные за время ее самостоятельной работы, и состояние, в котором она находится; при необходимости сообщить об ошибках;
    2.2если тест находится в состоянии ожидания реакций от целевой системы:
    2.2.1если получены не все ожидаемые реакции и время ожидания еще не вышло, вернуть управление;
    2.2.2если все ожидаемые реакции получены, перейти к шагу 2.4;
    2.2.3иначе сообщить об ошибке;
    2.3если тест находится в состоянии ожидания некоторого состояния целевой системы:
    2.3.1если не все ожидаемые реакции получены и время ожидания еще не вышло, вернуть управление;
    2.3.2если нужное состояние достигнуто, перейти к шагу 2.4;
    2.3.3иначе сообщить об ошибке;
    2.4вычислить очередной стимул, который следует применить к целевой системе в текущем состоянии;
    2.5если такой стимул найден, применить его, проанализировать немедленные реакции, при необходимости перейти в состояние ожидания реакций и вернуть управление;
    2.6если существует состояние целевой системы N, которое уже существовало в процессе выполнения теста, но в котором были применены не все возможные стимулы, то попытаться перейти в него; найти в уже обойденной части графа состояний путь из текущего состояния в искомое, вычислить и подать нужные стимулы, перейти в режим ожидания состояния N и возвратить управление;
    2.7если такое состояние не найдено, значит во всех обнаруженных состояниях применены все возможные стимулы; тест завершает работу.


    которое требуется для удовлетворения потребностей

    В связи с возрастанием размера и сложности программного обеспечения (ПО), которое требуется для удовлетворения потребностей пользователей и поддержки стабильного развития современного общества, задача автоматизации тестирования становится одной из ключевых в разработке качественного ПО. Одним из перспективных подходов к решению этой задачи является UniTesK - технология автоматизированного функционального тестирования на основе формальных методов, разработанная в Институте системного программирования РАН [, , , ]. Данная технология позволяет автоматизировать разработку и выполнение тестов, которые с высокой степенью надежности проверяют корректность поведения тестируемой системы. В дальнейшем в статье вместо термина "тестируемая система" используются термины "целевая система" или просто "система" - во избежание путаницы с похожим термином "тестовая система", который также будет использоваться. Большинство реальных систем обладает некоторым внутренним состоянием, которое может влиять на выдаваемые системой реакции и изменяться как при получении воздействий извне, так и в процессе собственной работы системы. Поскольку внутреннее состояние системы в общем случае неизвестно, и, кроме того, оно может содержать много несущественных с точки зрения поставленной задачи тестирования деталей, в технологии UniTesK строится модель целевой системы времени тестирования (в дальнейшем - просто "модель"), отражающая ее проверяемые свойства в удобном для тестирования компактном виде и скрывающая несущественные реализационно-зависимые детали. Повышение уровня абстракции позволяет использовать один и тот же комплект тестов для тестирования различных реализаций одного набора функций. Компонент, называемый медиатором, отображает реализацию и модель друг в друга: реализационное состояние целевой системы - в модельное состояние, модельные тестовые стимулы - в реализационно-зависимые воздействия на целевую систему, а реализационно-зависимые реакции целевой системы - в модельные реакции.
    В дальнейшем везде, где не оговорено специально, речь будет идти именно о модельных состояниях, стимулах и реакциях. Тестовый сценарий UniTesK перебирает состояния целевой системы и в каждом из них перебирает подаваемые ей на вход тестовые стимулы вплоть до достижения заданной степени тестового покрытия. Специальный компонент, получаемый на основе спецификаций и называемый оракулом, автоматически анализирует выходные реакции и выдает вердикт об их корректности. Корректность поведения целевой системы определяется его соответствием спецификации. Также автоматически оценивается степень достигнутого тестового покрытия [, ]. В технологии UniTesK последовательность тестовых воздействий строится интерактивно, по мере выполнения теста. Для этого используются специальные компоненты, называемые обходчиками. Задача обходчика - обход графа состояний системы, причем этот граф не задается заранее, а строится прямо в процессе взаимодействия тестовой и целевой систем. Очередное тестовое воздействие определяется на основе предыдущей истории взаимодействия. Такой подход обеспечивает простоту написания тестовых сценариев и гибкость тестирования. Интерактивная природа тестирования порождает ряд проблем, связанных с взаимной активностью тестовой и целевой систем: целевая система может иметь несколько взаимодействующих между собой потоков управления, взаимодействовать непредсказуемым образом с внешними системами, неподконтрольными тестовой среде, выдавать реакции на определенные воздействия по прошествии значительного времени и выполнять какую-то собственную деятельность. Все это может влиять на состояние целевой системы и, в результате, на наблюдаемые тестовой системой реакции. Кроме того, даже при полной пассивности целевой системы может возникнуть необходимость тестирования ее работы в окружении множества параллельно работающих активных потоков, создающих запросы к ней. Во всех случаях тест должен определять корректность поведения целевой системы и добиваться заданной степени тестового покрытия. Опыт использования технологии UniTesK для тестирования реальных систем показывает, что она подходит для тестирования практически любой конфигурации активных потоков теста, целевой системы и внешних систем, однако различные конфигурации требуют построения различных моделей целевой системы и использования различных инженерных решений в построении тестовой системы.Данная работа посвящена особенностям тестирования различных конфигураций активных потоков с помощью технологии тестирования UniTesK. Целями работы являются
  • исследование возможности использования технологии тестирования UniTesK для построения тестов с различной конфигурацией потоков управления, принадлежащих тестовой системе и существующих независимо от нее;самостоятельных.
  • систематизация опыта тестирования различных конфигураций активных потоков;
  • разработка подходы для случаев, на которые технология UniTesK в существующем на данный момент виде не рассчитана.

    В данной статье проведена классификация

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

    Тестирование софта - статьи

    Генерируемый SeC-код

    В первую очередь необходимо чётко определить, как должен выглядеть генерируемый код, что именно и как именно он будет проверять. За основу, разумеется, были взяты созданные к этому времени наработки, написанные вручную. Весь однотипный код удобно заменить набором макросов (они поддерживаются в языке SeC, т. к. он включает в себя все возможности языка C). Это сделало бы исходный текст более наглядным и более простым для написания и отладки. В стандарте LSB присутствуют несколько разных типов требований на поведение функций при возникновении ошибочной ситуации. Возможные варианты поведения функции в случае ошибки могут быть описаны следующим образом:
  • SHALL: в стандарте присутствует фраза "The function shall fail if …", т. е. стандарт требует, чтобы функция всегда обнаруживала описанную ошибочную ситуацию и возвращала в этом случае определённый код ошибки.
  • MAY: в стандарте присутствует фраза "The function may fail if …", т. е. стандарт не требует непременного обнаружения данной ошибочной ситуации, но в случае, если функция всё же реагирует на данную ошибку, её поведение должно быть таким, как описано в стандарте.
  • NEVER: в стандарте присутствует фраза "The function shall not return an error code …", т. е. данный код ошибки не может быть возвращён ни при каких условиях. Эти три типа требований и были взяты за основу. Проверка кодов ошибок должна производиться до начала проверки остальных требований (за исключением, быть может, самых базовых аспектов, обеспечивающих непосредственное функционирование самой тестовой системы - таких как проверки на NULL). Это связано с тем, что если произошла ошибка, то функция не выполнила требуемое от неё действие, и, следовательно, проверки функциональных требований сообщат, что функция работает некорректно, т. е. не удовлетворяет стандарту. Однако ошибка могла быть вызвана тестовым сценарием намеренно (например, специально для того, чтобы проверить, как поведёт себя функция в этом случае), и тогда сообщение о некорректности поведения функции окажется ложной тревогой.
    Именно поэтому в первую очередь должен отработать блок проверки ошибочных ситуаций, и только если ошибка не была возвращена (и не ожидалась!), управление передаётся дальше, на код проверки основных требований. Для удобства проверка кодов ошибок оформлена в виде блока, ограниченного операторными скобками ERROR_BEGIN и ERROR_END, которые являются макросами. ERROR_BEGIN при этом содержит параметры, глобальные для всего блока (например, в какой переменной хранится собственно код ошибки). Внутри блока располагаются индивидуальные проверки для каждого пункта, упомянутого в стандарте, которые также являются вызовами макросов с определённым набором параметров. Упомянутым выше трём основным типам требований соответствуют макросы ERROR_SHALL, ERROR_MAY и ERROR_NEVER. Помимо них, есть ещё некоторые дополнительные макросы, но о них мы расскажем ниже. Приведём небольшой пример, демонстрирующий, как выглядит блок проверки кодов ошибок в одной из подсистем: Текст стандарта: The pthread_setspecific() function shall fail if: [ENOMEM] Insufficient memory exists to associate the non-NULL value with the key. The pthread_setspecific() function may fail if: [EINVAL] The key value is invalid. These functions shall not return an error code of [EINTR]. Текст спецификации: ERROR_BEGIN(POSIX_PTHREAD_SETSPECIFIC, "pthread_setspecific.04.02", pthread_setspecific_spec != 0, pthread_setspecific_spec) /* * The pthread_setspecific() function shall fail if: * [ENOMEM] * Insufficient memory exists to associate the non-NULL value with * the key. */ ERROR_SHALL(POSIX_PTHREAD_SETSPECIFIC, ENOMEM, "!pthread_setspecific.05.01", /* условие */) /* * The pthread_setspecific() function may fail if: * [EINVAL] * The key value is invalid. */ ERROR_MAY(POSIX_PTHREAD_SETSPECIFIC, EINVAL, "pthread_setspecific.06.01", !containsKey_Map(thread->key_specific, key)) /* * These functions shall not return an error code of [EINTR]. */ ERROR_NEVER(POSIX_PTHREAD_SETSPECIFIC, EINTR, "pthread_setspecific.07") ERROR_END() Параметры макросов имеют следующий смысл: ERROR_BEGIN(ERR_FUNC, REQID, HAS_ERROR, ERROR_VAL):
  • ERR_FUNC - идентификатор функции, используемый в качестве префикса для конфигурационных констант.


    Например, с префикса POSIX_PTHREAD_SETSPECIFIC начинаются соответствующие конфигурационные константы, такие как:
  • POSIX_PTHREAD_SETSPECIFIC_HAS_EXTRA_ERROR_CODES
  • POSIX_PTHREAD_SETSPECIFIC_FAILS_WITH_EINVAL и др.
  • REQID - идентификатор проверяемого требования (строковая константа).
  • HAS_ERROR - предикат, определяющий условие возникновения ошибки (булевское выражение).
  • ERROR_VAL - код ошибки (обычно это переменная errno или возвращаемое значение функции). ERROR_MAY(ERR_FUNC, ERRNAME, REQID, ERROR_PREDICATE), ERROR_SHALL(ERR_FUNC, ERRNAME, REQID, ERROR_PREDICATE), ERROR_NEVER(ERR_FUNC, ERRNAME, REQID):
  • ERR_FUNC - то же, что и выше.
  • ERRNAME - имя константы, соответствующей ожидаемому коду ошибки. Например, ENOMEM, EINVAL.
  • REQID - идентификатор проверяемого требования.
  • ERROR_PREDICATE - предикат, определяющий условие возникновения ошибки. ERROR_END: параметров не требует.

    Конфигурационные константы.

    Стандарт LSB не всегда достаточно чётко определяет ситуации ошибочного завершения функции. В нем допускается, что функции могут возвращать коды ошибок, не описанные в стандарте, а также возвращать описанные коды ошибок в каких-то иных, определяемых реализацией случаях (см. [LSB, System Interfaces, Chapter 2, 2.3 Error Numbers]). На практике же такие случаи достаточно редки. К тому же вполне возможна ситуация, когда ошибочное завершение функции с кодом, не указанным в стандарте, является не предусмотренным разработчиком случаем, а ошибкой реализации. Поэтому была добавлена возможность выбрать в каждом конкретном случае желаемый уровень тестирования - в соответствии со стандартом, или более жёстко. Соответствующие константы именуются XX_HAS_EXTRA_ERROR_CODES и XX_HAS_EXTRA_CONDITION_ON_YY, где XX - идентификатор функции, обычно передаваемый в макросы как параметр ERR_FUNC, а YY - имя константы ошибки. Ситуация с требованиями типа MAY аналогична. В соответствии со стандартом, функция может не возвращать код ошибки, даже если условие выполняется. Однако в большинстве случаев разработчики "предпочитают ясность" и возвращают указанный код, хотя стандарт и не обязывает их к этому. Поэтому была введена специальная конфигурационная константа XX_FAILS_WITH_YY, определяющая, следует ли считать требование нарушенным, если функция не возвращает код ошибки при выполнении условия типа MAY. Как можно заметить, при включённой константе XX_FAILS_WITH_YY требования SHALL и MAY по сути перестают отличаться друг от друга. Фактически, сами макросы ERROR_SHALL и ERROR_MAY реализованы как один макрос ERROR_MAY_SHALL, принимающий на вход ещё один дополнительный аргумент с именем SHALL, который и определяет, должна ли функция возвращать код ошибки, если предикат ERROR_PREDICATE истинен. Соответственно, макрос ERROR_SHALL реализован как вызов ERROR_MAY_SHALL с параметром SHALL, равным true, а ERROR_MAY - как вызов ERROR_MAY_SHALL с параметром SHALL, зависящим от значения константы XX_FAILS_WITH_YY.

    Методика автоматизированной проверки

    К. А. Власов, А. С. Смачёв, Труды Института системного программирования РАН

    Оформление непроверяемых требований

    В силу различных причин проверка некоторых требований может быть затруднена или даже невозможна. Для таких ситуаций были введены два дополнительных макроса.
  • ERROR_UNCHECKABLE. Этот макрос используется в том случае, когда проверка требования не может быть выполнена по каким-либо причинам. Сам макрос просто сигнализирует о том, что данный код ошибки не противоречит стандарту.
  • TODO_ERR(errname). Этот макрос-заглушка используется вместо предиката, определяющего условия возникновения ошибки, в тех случаях, когда проверка в принципе возможна, но либо она чрезмерно сложна, либо её реализация по каким-то причинам на время отложена. Макрос TODO_ERR возвращает истину тогда и только тогда, когда возвращаемый код ошибки совпадает с проверяемым. Таким образом, вместо реальной проверки в данном случае мы имеем тавтологию. Этот макрос подставляется по умолчанию вместо конкретных условий в коде, который генерируется непосредственно из разметки. Это позволяет достаточно быстро и с минимальными усилиями создать компилирующийся и работающий проект для тестирования новой подсистемы.

    Пересечение требований

    Может оказаться, что один и тот же код ошибки встречается в нескольких требованиях. Хорошо, если эти требования лишь дополняют друг друга. Но может сложиться ситуация, когда эти требования противоречат друг другу, а именно, когда одно из них требует, чтобы функция вернула код ошибочного завершения, а другое - утверждает, что в этой ситуации данный код ошибки не может быть возвращён. Такая ситуация может и не встретиться в ходе выполнения тестов, но подобное противоречие условий в любом случае является ошибкой в стандарте. Другая ситуация: под одно и то же условие попадают разные коды ошибок, при этом стандарт для обоих кодов требует обязательного возврата этого кода. В этом случае при выполнении условий появления ошибки тесты зафиксируют некорректность в работе тестируемой системы, независимо от того, какой из этих кодов ошибки был возвращён функцией, так как проверка другого кода зафиксирует некорректность поведения. В такой ситуации обычно из текста стандарта понятно, какой код ошибки должен возвращаться в каждом конкретном случае, и, следовательно, условия возникновения ошибок должны быть дополнены так, чтобы не выполняться одновременно ни в каких ситуациях. Если же из контекста неясно, какой код ошибки должен быть возвращён, налицо явное противоречие в стандарте. Таким образом, пересечение требований является серьёзной проблемой, и разработчик тестов должен обращать на них особое внимание.

    Постановка задачи

    При написании тестов для пакета OLVER используется система CTesK []. Она включает в себя язык SeC [] - спецификационное расширение языка C, а также библиотеки для работы с различными типами данных, такими как массив, список, строка и т. д. Добавление новых тестируемых функций в тестовый набор обычно выглядит следующим образом. Сначала программист читает текст стандарта и размечает его, выделяя атомарные требования при помощи конструкций языка HTML и присваивая им уникальные идентификаторы. Затем при открытии в браузере веб-страницы с текстом стандарта автоматически запускается скрипт, написанный на языке JavaScript, который анализирует список отмеченных требований, строит по нему шаблонный код на языке SeC и отображает его на странице. Теперь программисту достаточно просто скопировать этот код в SEC-файлы проекта, и большая часть рутинной работы оказывается выполненной. Дальнейшая работа - это, в основном, создание тестовых сценариев и написание непосредственно кода проверки для всех атомарных требований, т. е. творческая работа, которая практически не поддаётся автоматизации. В процессе работы над тестовым набором выяснилось, что проверки кодов ошибок, возвращаемых различными тестируемыми функциями, имеют много общего, в результате чего существенная часть времени тратится на одну и ту же работу по организации взаимодействия проверок для разных кодов ошибок друг с другом и с проверками других требований. Поэтому было решено, насколько это возможно, попытаться автоматизировать и эту задачу. Наиболее удобный путь для такого рода автоматизации - это модификация существующего скрипта, чтобы он генерировал шаблонный код ещё и для проверки ошибок. Тем самым будет сохранена общая методика работы по разметке стандарта.

    Проверка

    В макросе ERROR_BEGIN в первую очередь проверяется, что если функция завершилась с ошибкой, то код ошибки не равен EOK (коду, обозначающему отсутствие ошибки). Обычно признаком ошибки является сам факт отличия кода от EOK, и эта проверка превращается в тавтологию. Но бывают и другие случаи, когда, например, признаком ошибки является возвращаемое значение функции, равное -1, и тогда эта проверка необходима. Проверка кодов ошибок не ограничивается случаем, когда функция завершилась с ошибкой. Условие типа SHALL должно обеспечивать также проверку в обратную сторону: если условия вызова функции ошибочны, то функция обязана вернуть ошибку, и если этого не произошло, поведение считается некорректным. Заметим, что независимо от того, успешно ли отработала функция, необходимо проверять все требования к кодам возврата, поскольку выполнение одного требования ещё не означает, что другие требования не нарушены. Таким образом, решение о правильной работе функции должно приниматься только в макросе ERROR_END, не раньше. На первый взгляд, это соображение очевидно, но именно поэтому его так легко упустить из виду. Для каждого требования имеется четыре базовых случая:
  • Ошибка ожидалась и произошла.
  • Ошибка не ожидалась, но произошла.
  • Ошибка ожидалась, но не произошла.
  • Ошибка не ожидалась, и её не было. В каждом из этих случаев есть свои тонкости, которые легко упустить из виду. Возьмём, например, первый случай, когда ошибка ожидалась и произошла. Первым побуждением будет выдать вердикт, что проверка завершилась успешно, и выйти из блока проверки ошибок. Однако этого делать ни в коем случае нельзя, т. к. должны быть проверены абсолютно все требования - то самое "очевидное" соображение, высказанное выше! Ведь даже в случае возврата ожидаемого кода ошибки есть вероятность, что другое требование окажется нарушенным. То же самое относится к последнему случаю, когда ошибка не ожидалась и не произошла. Случай второй, когда ошибка не ожидалась, но произошла. Если это требование типа NEVER, или если это MAY/SHALL, и соответствующая константа XX_HAS_EXTRA_CONDITION_ON_YY равна нулю, то можно констатировать неправильную работу функции.
    При ненулевом значении этой константы поведение функции не противоречит стандарту. Случай третий, когда ошибка ожидалась, но не произошла. Если это условие типа SHALL или если это MAY, и соответствующая константа XX_FAILS_WITH_YY не равна нулю, то можно констатировать неправильную работу функции. Если же функция вернула другой код ошибки, то поведение также является некорректным. Однако в этом случае дополнительно стоит обратить внимание на причины возникновения этой проблемы. Не исключено, что возвращённый код взялся не "с потолка", а явился следствием того, что оказались выполнены условия возникновения и этой второй ошибки наряду с первой. Если оба требования являются требованиями типа SHALL (или MAY с ненулевой константой XX_FAILS_WITH_YY), то можно утверждать, что в стандарте имеет место противоречие: для одних и тех же условий требуется вернуть одновременно два различных кода. На таких ситуациях мы сейчас остановимся несколько подробнее.

    Трёхзначная логика

    Иногда случается, что мы не во всех ситуациях можем определить, выполняется данное условие или нет. В этом случае логика становится трёхзначной: "да" - "нет" - "не знаю". Конечно, можно было бы поместить проверку внутрь условного ветвления, в котором условие всегда было бы проверяемым, но это значительно усложнило бы читаемость и понятность кода. К тому же для человека такая трёхзначная логика в достаточной мере "естественна". Поэтому к существующим макросам были добавлены ERROR_SHALL3 и ERROR_MAY3, в которых предикат может принимать одно из трёх значений: False_Bool3, True_Bool3, Unknown_Bool3. Наиболее используемы эти макросы в случаях, когда значение проверяемого параметра подсистемы может быть неизвестно в силу закрытости тестовой модели. Пример: /* * [EINVAL] * Invalid speed argument. */ ERROR_MAY3(LSB_CFSETSPEED, EINVAL, "cfsetspeed.04.01", (isKnown_Speed(speed) ? False_Bool3 : not_Bool3(isValid_Speed(speed))) ) Другими частыми случаями употребления этих макросов являются проверки на корректность параметров или на наличие определённых ресурсов. Зачастую стандарт не указывает, как определить некорректность нужного значения, а проверить наличие ресурсов (например, оперативной памяти) не представляется возможным, так как почти никогда не известно точно, какое их количество будет "достаточным". С другой стороны, про отдельные значения может быть известно, что они заведомо корректные или заведомо некорректные. Пример: /* * The confstr() function shall fail if: * [EINVAL] * The value of the name argument is invalid. */ /* [LSB: 18.1.1. Special Requirements] * A value of -1 shall be an invalid "_CS_..." value for confstr(). */ ERROR_SHALL3(POSIX_CONFSTR, EINVAL, "confstr.12", ((name == -1) ? True_Bool3 : Unknown_Bool3) ) В подобных случаях макросы с трёхзначной логикой позволяют естественным образом формализовать условие с помощью одной проверки, что обеспечивает читабельность и наглядность программного кода.

    В современном мире компьютеры играют

    В современном мире компьютеры играют всё большую роль, а с ними - и программное обеспечение. В настоящее время наблюдается тенденция ко всё возрастающему усложнению и увеличению программных комплексов, состоящих из различных модулей и подсистем. Но чем больше программа, тем сложнее её отлаживать и проверять на соответствие спецификационным требованиям. Есть области, где ошибки совершенно недопустимы, например, медицина, атомная энергетика. Легко представить, что может натворить небольшая ошибка в программе, управляющей атомным реактором. Поэтому важность задачи верификации программного обеспечения трудно переоценить. Данное исследование было проведено в рамках проекта OLVER (Open Linux VERification) [], задачей которого была разработка тестового набора, позволяющего выполнять автоматическую проверку дистрибутивов операционной системы Linux на соответствие стандарту LSB (Linux Standard Base). Тестовый сценарий заключается в вызове всех функций из тестируемой подсистемы с заданным набором параметров и проверке возвращаемых значений. Одним из важных пунктов в этом тестировании является проверка кодов ошибок, возвращаемых функциями, а именно:
  • проверка, что функция не возвращает код ошибки, когда она не должна этого делать;
  • проверка, что функция возвращает код ошибки, если это требуется по спецификации;
  • проверка, что в случае ошибки возвращается именно тот код ошибки, который функция должна вернуть согласно требованиям. В процессе работы над тестовым набором задача проверки кодов ошибок возникает для большинства функций, причём обычно эта задача решается однотипно. Поэтому было решено автоматизировать написание исходного кода проверок, насколько это возможно. В данной статье мы расскажем, как была решена эта задача. Во втором разделе будет дана исходная постановка задачи: какие конкретно действия должны быть автоматизированы, и как должна измениться работа по написанию тестов после внедрения этой автоматизации. В третьем разделе описаны технические подробности реализации, возникшие проблемы и их решение. И в заключении рассмотрены возможные пути развития системы в будущем.

    Несмотря на то, что работа

    Несмотря на то, что работа над пакетом OLVER в настоящее время завершается, описанная в данной статье методика будет применяться при написании тестовых наборов, расширяющих и дополняющих существующие сертификационные тесты на соответствие стандарту LSB, поскольку она очень проста в применении, и при этом генерируемый ей код достаточно универсален. Конечно, нужно понимать, что заранее учесть и продумать все мыслимые комбинации различных ошибочных ситуаций невозможно. Всегда может найтись какая-то очень специфичная функция, требующая нестандартного подхода. И именно эта возможность задаёт направление для дальнейшего развития системы. С одной стороны, в подобных случаях можно попросту подходить к каждой функции индивидуально и писать код, не опираясь на сгенерированные шаблоны. Однако если обнаруженная зависимость не единична, а встречается в нескольких функциях, то стоит сначала оценить трудозатраты: возможно, в такой ситуации имеет смысл доработать систему генерации кода так, чтобы она теперь учитывала и эти новые ситуации. Учитывая, что стандарт LSB активно развивается и включает в себя всё больше и больше функций, можно с уверенностью сказать, что описанная в данной статье система будет развиваться и дальше.

    Тестирование софта - статьи

    Аннотация.

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

    Извлечение тестов

    Инструмент SynTesK предоставляет возможность в полностью автоматическом режиме систематически генерировать наборы тестов, удовлетворяющих сформулированным выше критериям покрытия. В качестве входных данных генератор тестов принимает формальное описание грамматики целевого языка в форме BNF.
    Имеется инструментальная поддержка метода SemaTesK, которая позволяет в полностью автоматическом режиме систематически генерировать наборы тестов, удовлетворяющих сформулированным выше критериям покрытия. В качестве входных данных генератор тестов принимает формальное описание структуры абстрактных синтаксических деревьев целевого языка на языке TreeDL, а также описание контекстных условий целевого языка на языке SRL.


    Для извлечения тестов в рамках метода OTK требуется разработать генератор тестов. Генератор тестов состоит из двух компонентов. Первый, называемый итератором, отвечает за последовательную генерацию модельных структур в соответствии с выбранным критерием полноты. Второй компонент, называемый меппером, отвечает за отображение каждой модельной структуры в соответствующую корректную программу на языке входных данных компилятора. В рамках метода OTK предоставляется мощная инструментальная поддержка для формального описания модели данных, а также для разработки всех требующихся компонентов генератора тестов []. Генератор, разработанный с помощью OTK, позволяет для целевого аспекта back-end'а в автоматическом режиме систематически генерировать тестовые наборы с целью достижения выбранного критерия полноты.


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

    Критерии полноты тестирования

    Мы рассматриваем известные алгоритмы синтаксического анализа (LL-анализ и LR-анализ, построенные на основе стека - см. []) в качестве алгоритмов, моделирующих поведение парсера. Критерий полноты набора позитивных тестов для тестирования LL-анализатора: (PLL) Покрытие всех пар вида (нетерминал A, допустимый следующий токен t), где пара (A, t) считается покрытой тогда и только тогда, когда в тестовом наборе существует предложение языка, обрабатывая которое LL-анализатор придет в ситуацию, когда на вершине стека будет находиться символ A, а текущим входным символом будет токен t. Критерий полноты набора позитивных тестов для тестирования LR-анализатора: (PLR) Покрытие всех пар вида (символ s состояния конечного автомата,
    помеченный символом X переход из состояния s), где пара (s, X) считается покрытой тогда и только тогда, когда в тестовом наборе существует предложение языка, обрабатывая которое LR-анализатор придет в ситуацию, когда на вершине стека будет находиться символ s, а началом текущего входного потока будет последовательность токенов, отвечающая символу X. Критерий полноты набора негативных тестов для тестирования LL-анализатора: (NLLR) Покрытие всех пар (нетерминал A; "некорректный" токен t'), где пара (A, t') считается покрытой тогда и только тогда, когда среди тестов имеется последовательность токенов, не принадлежащая целевому языку, такая что LL-анализатор, обрабатывая эту последовательность, придет в ситуацию, когда после обработки как минимум R "правильных" токенов, на вершине стека будет находиться символ A, а текущим входным символом будет "некорректный" токен t'. Критерий полноты набора негативных тестов для тестирования LR-анализатора: (NLRR) Покрытие всех пар (символ s состояния конечного автомата; "некорректный" токен t'), где пара (s, t') считается покрытой тогда и только тогда, когда среди тестов имеется последовательность токенов, не принадлежащая целевому языку, такая, что LR-анализатор, обрабатывая эту последовательность, придет в ситуацию, когда после обработки как минимум R "правильных" токенов, на вершине стека будет находиться символ s, а текущим входным символом будет "некорректный" токен t'. Существует семейство алгоритмов генерации тестов, удовлетворяющих этим критериям. Их подробное описание можно найти в работе []. Следует особо отметить следующее важное свойство предложенных в этой работе алгоритмов генерации негативных тестов: алгоритмы гарантированно строят тесты, не принадлежащие целевому формальному языку. Это свойство позволяет отказаться при построении оракула от использования каких бы то ни было эталонных синтаксических анализаторов. Разработан инструмент SynTesK [], в котором реализованы алгоритмы, удовлетворяющие критериям PLL и NLL1, а также критерию NLR1 для некоторого специального вида грамматик []. Инструмент SynTesK успешно применялся для тестирования компиляторов реальных языков программирования, в т.ч. C и Java []. Практические результаты этого применения показали эффективность реализованных в SynTesK алгоритмов.

    Методы тестирования

    Метод SemaTesK [] автоматической генерации тестов для тестирования анализаторов контекстных условий основан на использовании модели атрибутированных деревьев для конструктивного описания статической семантики целевого языка. Метод позволяет получать наборы как позитивных , так и негативных тестов для тестирования анализатора контекстных условий. В методе SemaTesK используется формальное описание контекстных условий в форме зависимостей между вершинами абстрактного синтаксического дерева. Зависимость между некоторыми двумя вершинами индуцируется зависимостями между соответствующими атрибутами этих вершин. Одна из вершин, участвующих в контекстном условии, называется источником, а другая - целью в том смысле, что атрибуты вершины-цели зависят от атрибутов вершины-источника. Этот метод дает возможность локализовать описание одного неформального контекстного условия в пределах одного формального правила, а также позволяет в подавляющем большинстве случаев задавать контекстные условия декларативно . Построение тестов методом SemaTesK производится в соответствии со следующими критериями покрытия расположения контекстных условий в различных синтаксических контекстах. Критерий полноты набора позитивных тестов для тестирования анализатора контекстных условий: Для каждого контекстного условия R из множества всех контекстных условий набор тестов должен содержать тексты, покрывающие R и такие, что соответствующее множество деревьев содержит все возможные вершины "источник" и "цель" во всех возможных синтаксических контекстах. Критерий полноты набора негативных тестов для тестирования анализатора контекстных условий: Для каждой мутации R¯ каждого контекстного условия R из множества всех контекстных условий набор тестов должен содержать множество текстов, покрывающих R¯ и таких, что соответствующее множество деревьев содержит все возможные вершины "источник" и "цель" во всех возможных синтаксических контекстах. Более подробное описание метода SemaTesK можно найти в работе []. Следует особо отметить следующее важное свойство метода SemaTesK. Метод дает возможность непосредственно строить тесты, нацеленные на проверку корректности анализа контекстных условий. Это свойство позволяет в отличие от прочих известных методов отказаться при генерации тестов от использования фильтрации (селекции) позитивных/негативных тестов среди множества всех синтаксически корректных предложений данного языка, а также позволяет систематически генерировать тесты, удовлетворяющие адекватным критериям полноты. Метод SemaTesK успешно применялся для тестирования компиляторов реальных языков программирования, в т.ч. C и Java []. Практические результаты этого применения показали эффективность метода SemaTesK. Заметим, что особенно перспективным направлением использования метода SemaTesK является генерация тестов для разрабатываемых диалектов языков, так как вносить небольшие изменения в их описания достаточно просто, и таким образом количество ручного труда для приведения тестов в соответствие с очередной редакцией языка не велико.
    Метод OTK [] автоматической генерации тестов для тестирования back-end'а основан на построении модели его входных данных на основе из абстрактного описания алгоритма работы back-end'а. Метод OTK позволяет строить модели данных и разрабатывать на их основе генераторы тестов для компилятора, нацеленных на тестирование данного аспекта back-end'а. Модель данных строится на основе абстрактного описания алгоритма работы тестируемого аспекта. Алгоритм формулируется с использованием терминов, обозначающих сущности некоторого подходящего абстрактного представления входных данных, такого как граф потока управления, граф потока данных, таблица символов и пр. Back-end для осуществления своих действий ищет сочетания сущностей абстрактного представления программы, которые удовлетворяют некоторым шаблонам (например, наличие в программе циклов, наличие в теле цикла конструкций с определенными свойствами, наличие в процедуре общих подвыражений, наличие между инструкциями зависимости данных некоторого вида и пр.). При этом могут учитываться сущности лишь части терминов. Для построения модели данных рассматриваются только те термины, которые именуют сущности, задействованные хотя бы в одном шаблоне. Итак, в результате анализа алгоритма выделяются термины и шаблоны, используемые в алгоритме. Далее на основании этой информации описывается множество модельных строительных блоков:
  • каждому термину соответствует свой вид модельного строительного блока;
  • строительные блоки могут связываться между собой и образовывать структуры, соответствующие шаблонам. Модельной структурой называется граф, вершины которого - строительные блоки, а ребра - связи между строительными блоками. Модельная структура является абстракцией той части внутреннего представления структуры программы, которая существенна для целевого аспекта back-end'а. Критерий полноты множества тестов формулируется в терминах построенной модели на основе информации о тех шаблонах, которые были выделены при анализе алгоритма. Более подробное описание метода OTK можно найти в работе []. Метод OTK успешно применялся при тестировании оптимизирующих компиляторов GCC, Open64, Intel C/Fortran compiler (см. []), а также для тестирования оптимизаторов графических моделей [], реализации протокола IPv6, подсистемы построения отчетов в биллинговой системе, обработчиков XML-логов []. Практические результаты этого применения показали эффективность метода OTK.

    Методы верификации

    Верификация встроенных возможностей языка основывается на методе OTK (см. предыдущий подраздел). Целевые конструкции моделируются на формальном языке TreeDL, из формальной модели генерируются тестовые программы. Оракул строится на основе сравнения трассы исполнения тестовой программы, откомпилированной тестируемым компилятором, с трассой выполнения программы, откомпилированной на эталонном компиляторе. Далее мы будем рассматривать только вопросы тестирования библиотек, предоставляющих поддержку времени исполнения. Верификация библиотек времени исполнения основывается на методе функционального тестирования с использованием технологии автоматизированного тестирования UniTESK.[]. Рассмотрим основные особенности UniTesK в приложении к тестированию программных интерфейсов:
  • разделение построения тестовых воздействий и проверки правильности поведения целевой системы: тестовые воздействия строятся в тестовых сценариях, а проверка правильности поведения целевой системы осуществляется в тестовых оракулах;
  • автоматизированное построение тестовых воздействий;
  • представление функциональных требований к целевой системе в виде формальных спецификаций;
  • автоматическая генерация тестовых оракулов из спецификаций;
  • язык описания формальных спецификаций и тестовых воздействий "близок" к языку, на котором разработана целевая система;
  • автоматически генерируются критерии качества покрытия требований;
  • автоматически производится оценка качества покрытия требований при прогоне тестов. Метод тестирования с использованием UniTESK подробно рассмотрен в препринте []. Для работы с формальными спецификациями и спецификациями тестов разработаны пакеты инструментов CTesK [] и JavaTESK []. Представленный метод использовался при тестировании системы поддержки исполнения компилятора Sun Java 1.4.

    Моделирование

    Для формального задания модели целевого языка используется представление грамматики языка в форме BNF.
    Модель синтаксиса целевого языка задается в виде формального описания на языке TreeDL [] структуры корректных абстрактных синтаксических деревьев целевого языка. Модель статической семантики целевого языка задается в виде списка контекстных условий. Для их формального описания используется язык SRL (Semantic Relation Language), специально разработанный для этой цели [].


    Модель структуры входных данных задается в виде формального описания на языке TreeDL [].


    Требования к программному интерфейсу формализуются как контрактные спецификации. Такой подход к спецификации основан на представлении об описываемой системе как наборе компонентов, взаимодействующих с окружением и между собой. Контрактная спецификация состоит из модельного состояния, которое моделирует внутреннее состояние системы поддержки исполнения, и набора спецификационных функций. Спецификационные функции формализуют требования к обработке входящих и исходящих функциональных вызовов посредством логических выражений в постусловиях. Такой подход к спецификации программных интерфейсов близок к логике Т. Хоара и концепции "проектирования на основе контрактов" Б. Майера. Формальные спецификации программного интерфейса записываются на расширении языков программирования Си [] или Java []. Из формальных спецификаций автоматически генерируются тестовые оракулы - процедуры, которые выносят вердикт о соответствии наблюдаемого поведения системы спецификации.

    Регламентирующие документы. Анализ требований

    В рамках предлагаемого подхода под тестированием парсера понимается проверка соответствия парсера функциональным требованиям. Функциональные требования извлекаются, как правило, из описания стандарта языка. Основное функциональное требование для парсера определяется множеством корректных предложений целевого языка. Обычно это множество описывается при помощи контекстно-свободной грамматики. Часто в тексте стандарта языка уже присутствует соответствующее описание в формальном виде.
    В рамках метода SemaTesK под тестированием анализатора контекстных условий понимается проверка соответствия анализатора контекстных условий функциональным требованиям. Функциональные требования извлекаются, как правило, из описания стандарта языка. Основное функциональное требование для анализатора контекстных условий определяется множеством предложений целевого языка, корректных с точки зрения статической семантики. Обычно это множество задается в стандарте при помощи описания синтаксиса языка в виде контекстно-свободной грамматики, а также при помощи описания контекстных условий в виде неформального текста. Основной работой по анализу требований в рамках метода SemaTesK является извлечение из стандарта языка и составление списка всех контекстных условий.


    В рамках метода OTK под тестированием back-end'а в трансляторе понимается проверка соответствия семантики выходных данных, созданных back-end'ом, семантике соответствующих входных данных. Структура входных данных извлекается из абстрактного описания алгоритма работы back-end'а (см. выше описание метода тестирования).


    В рамках предлагаемого подхода под верификацией подсистемы поддержки времени исполнения понимается проверка соответствия этой подсистемы функциональным требованиям. Функциональные требования, как правило, извлекаются из спецификации, или стандарта языка или описания эталонной реализации языка. Так, в случае языка TTCN3 функциональные требования представлены в частях 1, 4, 5 и 6 спецификации языка []. Требования к подсистеме поддержки исполнения Java приводятся в Java Language Specification, спецификаций Java SDK и Java Native Interface, опубликованных фирмой Sun.

    Систематический подход к верификации функций компилятора

    Функции компилятора тесно связаны, поэтому при верификации компилятора необходимо проверять корректность каждой из них. В предлагаемом подходе задача верификации компилятора декомпозируется на основе декомпозиции функциональности компилятора. Мы выделяем следующие задачи:
  • Верификация синтаксического анализатора компилятора.
  • Верификация семантического анализатора компилятора.
  • Верификация оптимизаций и генерации кода.
  • Верификация библиотек поддержки времени исполнения (runtime). Некорректное функционирование подсистемы поддержки исполнения делает почти бесполезным весь компилятор - результат компиляции будет работать неверно из-за ошибок в этой подсистеме и, следовательно, станет непригодным для нужд пользователя. Тесная связь компилятора и подсистемы поддержки исполнения служит обоснованием того, чтобы включить верификацию подсистемы поддержки исполнения в общую задачу верификации компилятора. Корректность оптимизаций и генерации кода определяет корректность создаваемых транслятором выходных данных. Поэтому верификация этой функциональности является основной задачей верификации компилятора. От корректности синтаксического анализа и анализа контекстных условий зависит корректность основной функциональности транслятора - оптимизирующих преобразований, генерации кода. Поэтому решение задачи верификации анализа синтаксиса и контекстных условий является базой для решения задач верификации остальных функций компилятора. В литературе выделяют два вида верификации - статическая верификация средствами анализа кода и аналитической верификации и динамическая верификация посредством тестирования. Компилятор любого практически полезного языка представляет собой настолько сложную систему, что статической верификации поддаются только отдельные небольшие подсистемы, например, отдельные блоки оптимизации. Для решения задач верификации, поставленных выше, на практике используются методы динамической верификации. В связи с этим при проведении верификации компилятора и его функций необходимо решить следующий набор задач:
  • автоматизация построения тестов:
  • автоматизация генерации тестовых данных;
  • автоматизация проверки корректности обработки тестовых данных (проблема построения оракула);
  • определение критерия завершения верификации. В данной работе мы предлагаем решать эти задачи на систематической основе.
    Во всех задачах верификации методы верификации строятся по единой схеме (рис. 1). Систематический подход к верификации функций компилятора Рис. 1.Общая схема метода верификации Эта схема метода верификации состоит из нескольких этапов. На первом этапе требования извлекаются из нормативных документов и систематизируются. В результате получается каталог требований, в котором требования сформулированы максимально однозначно, требования классифицированы, и, возможно, установлены связи между отдельными требованиями. Каталог требований используется на последующих этапах. Второй этап нацелен на представление требований в формальном виде. Требования из каталога записываются с использованием того или иного математического формализма. Такая запись требований называется формальной спецификаций или формальной моделью. На третьем этапе на основе построенной модели автоматизированным образом генерируются тесты. В зависимости от задачи тесты могут либо быть просто тестовыми данными, либо дополнительно содержать оракул для автоматического вынесения вердикта о корректности наблюдаемого поведения компилятора. В результате исполнения тестов строятся отчёты о тестировании. В них содержится информация о том, насколько наблюдаемое поведение компилятора соответствует формальной модели. Вопросы анализа вердиктов, обнаружения дефектов и их исправления выходят за рамки данной статьи.

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

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


    В рамках метода OTK оракул для тестирования back-end'а в компиляторе автоматически проверяет сохранение семантики выполнения программы во время обработки back-end'ом. Для этого в качестве тестовых воздействий меппером строятся такие программы, семантика выполнения которых полностью представляется их трассой. Такое свойство тестов позволяет свести задачу проверки сохранения семантики к сравнению трассы с некоторой эталонной трассой. Работа такого автоматического оракула заключается в следующем:
  • каждый тест компилируется дважды - тестируемым компилятором и некоторым эталонным компилятором;
  • обе откомпилированные версии запускаются на исполнение;
  • полученные трассы сравниваются;
  • считается, что тест прошел успешно в том и только том случае, если трассы эквивалентны.


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

    Тестирование анализаторов контекстных условий

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

    Тестирование оптимизаций и генерации кода

    Back-end'ом мы называем функцию, которая принимает на вход структуру обрабатываемых входных данных в некотором внутреннем представлении и генерирует соответствующие ей требуемые выходные данные. Функциональность back-end'а касается следующих аспектов:
  • анализ внутреннего представления программы, проводящийся с целью обогащения его дополнительной информацией, которая требуется при генерации выходных данных; примерами подобного анализа являются, например, анализ графа потока управления программы, анализ потоков данных в программе и проч.;
  • трансформации (оптимизации) внутреннего представления программы, проводящиеся с целью улучшения качества выходных данных в соответствии с выбранным критерием (например, размер или скорость работы программы); примерами подобных оптимизаций являются, например, удаление неиспользуемых частей входных данных, вычисление константных выражений на этапе трансляции, оптимизация размещения аргументов и промежуточных результатов вычислений в регистрах процессора и проч.;
  • построение результирующего представления в выходном языке.

    Верификация компиляторов - систематический подход

    С.В. Зеленов, Н.В. Пакулин,
    Труды Института системного программирования РАН

    Верификация подсистемы поддержки исполнения (runtime support)

    Рассмотрим несколько примеров подсистем поддержки исполнения. Для низкоуровневых языков, таких как Си и Си++, поддержка исполнения ограничивается тем, что в состав поставки компилятора включаются стандартные библиотеки этих языков. Компилятор Java переводит исходный текст на языке Java в байт-код. Система поддержки времени исполнения включает в себя виртуальную машину Java, стандартную библиотеку классов Java SDK, в том числе, внутренние классы, обеспечивающие доступ к ресурсам операционной системы (ввод-вывод, потоки, и т.д.), набор средств взаимодействия Java-среды с окружением через JNI. Другой пример относится к языку спецификации тестов TTCN3. Все известные в настоящее компиляторы TTCN3 построены как трансляторы - исходный текст на языке TTCN3 преобразуется в набор файлов на целевом языке (Си или Java), которые затем компилируются соответствующими компиляторами. Система поддержки времени выполнения представляет собой набор библиотек на целевом языке (Си или Java), которые реализуют встроенные возможности языка (таймеры, порты обмена сообщениями, стандартные функции и т.п.), предоставляют интерфейсы расширения (TCI и TRI) как сгенерированному коду, так и окружению, и, в зависимости от поставщика, различные дополнительные возможности. Задачу верификации подсистемы поддержки исполнения можно разделить на следующие подзадачи:
  • Верификация программного интерфейса, предоставляемого сгенерированному коду.
  • Верификация встроенных возможностей языка и динамической семантики операций, предоставляемых подсистемой поддержки исполнения.
  • Верификация интерфейсов взаимодействия с окружением. Эта задача может быть разделена на две подзадачи:
  • Верификация интерфейсов, предоставляемых окружению для взаимодействия с исполняемой системой.
  • Верификация интерфейса, который предоставляет подсистема поддержки исполнения в исходном языке. Рассмотрим эти задачи на примере транслятора TTCN3, который преобразует исходный текст TTCN3 в набор файлов на языке Си. В этом случае задачи верификации можно сформулировать следующим образом:
  • Верифицировать библиотеки непосредственно через предоставляемый ими интерфейс Си/Си++.
  • Верификация встроенных возможностей языка заключается в том, чтобы проверить выполнение требований динамической семантики TTCN3 в той части, которая реализована в подсистеме поддержки исполнения - проверка требований к передаче сообщений через порты обмена сообщениями, обработке событий в инструкциях выбора alt, запуску и остановке компонентов и т.д.
  • Верификация интерфейсов расширения заключается в проверке выполнения требований к стандартным интерфейсам расширения:
  • Обращения к "скомпилированному тестовому набору" (Compiled Test Suite) извне через стандартные интерфейсы TCI и TRI, представленные в виде набора функциональных вызовов в программе на Си.
  • Обращения к окружению скомпилированного тестового набора, представленные инструкциями и выражениями в исходных текстах на TTCN3, через стандартные интерфейсы TTCN3. Аналогичные задачи можно сформулировать и для подсистем поддержки времени исполнения языка Java.
  • Верификация библиотеки поддержки исполнения. Необходимо учитывать, что почти все сервисы, предоставляемые поддержкой времени исполнения, представлены как классы Java, поэтому в данном случае верификация представляет собой тестирование Java-классов.
  • Верификация встроенных возможностей языка (например, синхронизации доступа) и динамической семантики (например, создания и уничтожения объектов).
  • Верификация интерфейсов расширения:
  • Обращения из Java-кода к ресурсам операционной системы через специализированные классы (потоки ввода-вывода, потоки исполнения Thread, класс System и т.д.), обращения из Java к внешним библиотекам, разработанным на других языках (JNI).
  • Верификация программного интерфейса виртуальной машины Java и доступа к объектам Java через JNI из программ на Си.

    Верификация синтаксического анализатора

    Синтаксический анализ входного текста является частью функциональности любого транслятора. Парсером мы называем булевскую функцию, заданную на множестве последовательностей токенов и принимающую значение "истина", если последовательность является предложением данного формального языка, и "ложь" - в противном случае. Задачу верификации парсера можно разделить на следующие подзадачи:
  • Проверка того, что парсер принимает предложения, принадлежащие целевому языку.
  • Проверка того, что парсер отвергает предложения, не принадлежащие целевому языку.

    Языки высокого уровня являются основным

    Языки высокого уровня являются основным средством разработки программных систем. Спецификация языка высокого уровня задаёт класс текстов, принадлежащих этому языку, и определяет семантику исполнения программ, написанных на этом языке. Задача перевода текстов с языка высокого уровня в представление, выполнимое на вычислительной системе, решается комплексами программ, которые по традиции называют компиляторами. Ошибки в компиляторах приводят к тому, что выполнение результирующих исполнимых модулей отличается от поведения, определяемого спецификацией языка. Дефекты исполнимых модулей, вызванные ошибками в компиляторе, очень сложно выявлять и исправлять, и в целом корректность исполнимых модулей, построенных некорректным компилятором, вызывает сомнения. Корректность компилятора является необходимым требованием корректной и надёжной работы программного обеспечения, разработанного на соответствующем языке высокого уровня. Тем самым, задачу верификации компилятора можно считать одним из важнейших средств обеспечения надёжности программного обеспечения. Основной источник трудностей при верификации компилятора заключается в том, что компилятор принимает на вход данные со сложной структурой, большим количеством внутренних связей и обладающие очень сложной семантикой времени исполнения. Для снижения сложности задачи верификации компилятора мы предлагаем декомпозировать её на отдельные подзадачи, которые в совокупности покрывают всю функциональность компилятора . Функциональность, типичную для большинства компиляторов, можно условно декомпозировать на следующие задачи:
  • Анализ синтаксической корректности исходного теста программы.
  • Анализ выполнения контекстных условий в исходном тексте .
  • Оптимизация внутреннего представления и генерация выходных данных. Результатом работы компилятора является объектный модуль на машинном языке или модуль на исполнимом промежуточном языке, таком как байт-код Java и Python или Intermediate Language платформы .NET. Для большинства компиляторов языков программирования, используемых на практике, генерируемый код исполняется в специализированном окружении, которое обобщённо называется run time support.
    В рамках данной статьи мы будем пользоваться термином "подсистема поддержки исполнения". Подсистема поддержки исполнения тесно связана с компилятором. Например, каждый компилятор Си и Си++ поставляется со своим набором стандартных библиотек, причём библиотеки одного компилятора нельзя использовать для построения исполнимых файлов другим компилятором. Даже в тех случаях, когда промежуточный язык и стандартные библиотеки стандартизированы, существуют тесные зависимости между компилятором и средой исполнения. Например, для исполнения байт-кода Java в виртуальной машине Java компании IBM лучше всего для компиляции использовать компилятор Java именно этой фирмы; аналогично для исполнения байт-кода в виртуальной машине компании Sun следует компилировать исходные тексты компилятором Java, разработанным в Sun. В настоящей работе представлен подход к верификации компиляторов, основанный на технологии автоматизированного тестирования UniTESK[]. Методы, составляющие представленный подход, построены по единому шаблону формализации требований [].

    В работе представлен систематический подход

    В работе представлен систематический подход к верификации компиляторов. Общая задача верификации компилятора декомпозирована на подзадачи верификации отдельных аспектов функциональности компилятора. Декомпозиция задачи верификации позволяет начинать верификацию на ранних этапах создания компилятора и проводить её параллельно разработке. Тем самым, сокращается продолжительность цикла разработки компилятора и снижаются суммарные затраты на тестирование компилятора. Для всех выделенных подзадач предложены методы верификации, построенные в рамках единого подхода тестирования на основе формальных спецификации и моделей. Для всех методов существуют их подробные описания и инструментальная поддержка для их практического применения. Представленный подход использовался при тестировании компиляторов Си и Java, а также трансляторов расширений языков программирования, разработанных в ИСП РАН [].

    Тестирование софта - статьи

    Формулировка задачи

    Будем представлять возможные тестовые последовательности словами в алфавите из возможных вызовов. Число возможных вызовов можно считать конечным — для этого из множества всех возможных вызовов нужно выбрать конечное множество представителей, например, выделив «наиболее интересные» комбинации значений параметров (см. выше). Такому подходу практически нет альтернатив, поскольку тестирование, будучи принципиально конечной процедурой, не может обеспечить качественную проверку работы системы, если имеется бесконечное множество существенно различных способов обратиться к ней. Далее будем рассматривать тестовые последовательности как слова в конечных алфавитах. Поскольку мы не обладаем дополнительной информацией о тестируемой системе, все вызовы (представители) для нас ничем не отличаются друг от друга. Можно обозначить их символами от 0 до (m-1) и рассматривать m-последовательности или m-слова — слова произвольной длины, состоящие только из таких символов. Предположим теперь, что помимо известного нам числа m различных воздействий на систему от пользователя мы можем получить только ограничение на суммарную длину теста. Стоящая перед нами задача может быть в итоге cформулировна так: как построить одно m-слово длины, не превосходящей N, имеющее «как можно более разнообразное» поведение? Возможные интерпретации «как можно более разнообразного» поведения рассматриваются ниже. Сразу отметим, что можно пытаться построить несколько m-слов, сумма длин которых не превосходит N, совместно обеспечивающих нужное разнообразие. Такая постановка задачи и возможные подходы к ее решению оставлены за рамками данной статьи.

    Максимизация числа различных подслов

    Наверно, наиболее очевидный способ интерпретации «как можно более разнообразного поведения» слова — это наличие у него как можно большего числа различных подслов. В слове длины N имеется всего N + (N-1) + (N-2) + … + 2 + 1 = N(N+1)/2                                 (*) подслов (слагаемые соответствуют подсловам длины 1, 2 и т.д.). Можно попытаться максимизировать количество разных подслов, сделав все подслова какой-то длины k различными. При этом все подслова большей длины тоже автоматически будут различны, т.е. мы получим как минимум (N-k+1)(N-k+2)/2 разных подслов длины k и больше. Значение k при этом можно выбрать так, чтобы максимизировать полученное выражение при фиксированном N, т.е. как можно меньше. При этом имеется всего mk разных m-слов длины k, а если они все реализуются как подслова одного слова, длина этого слова должна быть не меньше mk+k-1. Предположим, что существуют такие «наиболее плотные» слова, что все их подслова длины k различны и в то же время включают в себя все возможные m-слова длины k, и они нам известны. Тогда для заданного N можно найти минимальное k, такое, что N ? mk+k-1, т.е. m(k-1)+(k-1)-1 < N ? mk+k-1, и в качестве искомого слова взять начало длины N соответствующего «наиболее плотного» слова. Это гарантирует различие всех подслов длины k в нем. Для того, чтобы еще увеличить количество разных подслов в нашем слове, можно попытаться найти «наиболее плотное» слово для (k-1), которое продолжается в «наиболее плотное» слово для k. Взяв начало этого последнего мы получим наилучший результат — в полученном слове длины N все подслова минимально возможной длины k различны, и в то же время в нем в качестве подслов содержатся все возможные слова длины (k-1) и, соответственно, меньшие — все слагаемые в формуле (*) имеют максимальные возможные значения. Осталось выяснить два момента.
  • Существуют ли «наиболее плотные» слова для всех m и k, и если это так, можно ли их продолжать до «наиболее плотных» слов для m и (k+1)? Есть ли достаточно эффективные алгоритмы для построения таких слов?
  • Какой тестовой гипотезе, т.е.
    какому допущению относительно свойств тестируемой системы, соответствует выбранное понимание «наиболее разнообразного» поведения слова? Для какого рода систем этот подход дает действительно оптимальное покрытие различных возможных ситуаций?
  • Ответу на первый вопрос посвящен весь следующий раздел. В рамках данного раздела проще ответить на второй. Такой подход к построению тестов базируется на предположении о том, что поведение тестируемой системы целиком определяется последними k обращениями. При этом выбирая тестовую последовательность, содержащую как можно больше различных подпоследовательностей длины k, мы покрываем наибольшее количество различных «поведений» системы. В качестве реального примера такой системы можно привести кодовый замок, реагирующий на последние набранные k (обычно 4 или 5) цифр (автор сам пользовался такими замками, см. также статью [4], посвященную стратегии эффективного вскрытия замка в отеле Baltimore Hilton). Дополнительное условие, обеспечивающее содержание в тестовой последовательности всех возможных слов длины (k-1), по отношению к этой гипотезе является некоторой необязательной «оптимизацией».

    Продолжение слов де Бройна

    Нас, однако, интересует еще вопрос возможности продолжения слова де Бройна шага k до слова де Бройна шага (k+1). Ответ на этот вопрос дается следующим утверждением. Утверждение 5. 1) При m = 1 для всякого k ? 1 единственное слово де Бройна шага k представляет собой слово 0k, соответственно, оно может быть продолжено до слова де Бройна шага (k+1) 0k+1. 2) При m ? 3 для всякого k ? 1 любое слово де Бройна шага k может быть продолжено до слова де Бройна шага (k+1).
    Это значит, что для m = 1 или m ? 3 существуют бесконечные слова, каждое начало которых имеет максимальное среди слов той же длины число разных подслов. 3) При m = 2 ни для какого k ? 2 ни одно слово де Бройна шага k не может быть продолжено до слова де Бройна шага (k+1), но любое такое слово может быть продолжено до слова де Бройна шага (k+2).
    Для k = 1 слова де Бройна — 01 и 10; каждое из них продолжается до слова де Бройна шага 2 — 01100 и 10011. Поскольку пункт 1 достаточно очевиден, докажем основные утверждения из пунктов 2 и 3. Отдельные утверждения этих пунктов доказаны в [30-32]. В [32], кроме того, несколько иначе доказано утверждение, что именно продолжения слов де Бройна имеют максимально возможное количество разных подслов. Слово де Бройна шага k соответствует эйлерову циклу в графе B(m, k) и гамильтонову в графе B(m, k+1). Если выбросить все ребра этого гамильтонова цикла, при m > 2 граф B(m, k+1) останется связным (см. ниже), и эйлеровым, поскольку входящие и исходящие полустепени всех вершин уменьшатся на 1 и останутся равными. Поэтому можно дополнить выброшенный гамильтонов цикл до эйлерова цикла в B(m, k+1), который соответствует искомому продолжению. Связность B(m, k+1) не нарушится от выбрасывания ребер гамильтонова цикла, поскольку каждую его вершину v можно соединить с 0k+1 (m-1)-м непересекающимся путем и наоборот, 0k+1 можно соединить с v таким же количеством непересекающихся путей. Для доказательства этого достаточно рассмотреть следующую конструкцию. Пусть в v i ? 0 первых символов равны 0 и x — первый символ v, отличный от 0, т.е. (i+1)-й.
    Тогда (m-2) искомых пути соответствуют словам, полученным конкатенацией 0k+1, символа y, не равного 0 или х, и v. Еще один путь получается, если взять конкатенацию 0k+1 и конца v, начинающегося с индекса (i+1). В полученных словах все подслова длины k, кроме начального и конечного, различны. Аналогично показывается существование (m-1)-го обратного пути из v в 0k+1. Для m = 2 и k > 1 аналогичное выбрасывание ребер гамильтонова цикла оставит несвязанными с остальными вершины 0k и 1k, в каждой из которых имеется по петле. Значит, хотя бы одна из этих петель не может войти ни в какое продолжение исходного гамильтонова цикла, соответственно, никакое его продолжение не будет соответствовать слову де Бройна шага (k+1). Доказательство того, что 2-слово де Бройна шага k можно продолжить до 2-слова де Бройна шага (k+2), можно найти в [30]. Утверждение 6 (гипотеза). При m = 2 для всякого k ? 2 существует слово де Бройна шага k, которое можно продолжить до слова длины mk+1+(k+1)-2, содержащего все возможные 2-слова длины (k+1), кроме одного.
    Это значит, что при m = 2 некоторые (не все!) слова де Бройна (назовем их продолжающимися) можно продолжить почти до нужной длины (на 1 меньшей длины следующего слова де Бройна), а значит мы можем модифицировать предложенный выше способ построения слова длины N с максимально возможным числом разных подслов для m = 2 следующим образом: находим минимальное k, такое что 2k+k-1 ? N, строим продолжающееся слово де Бройна шага k и продолжаем его до длины N. Поскольку N ? 2k+1+(k+1)-2, мы сможем это сделать, и полученное слово будет иметь максимально возможное число разных подслов — для длин, не превосходящих k, это следует из того, что его начало является словом де Бройна шага k, а для больших длин из того, что все такие подслова в нем различны. Доказательство этого утверждения автору неизвестно, хотя оно выглядит истинным. Примеры продолжающихся 2-слов де Бройна шагов 2, 3, 4, 5, 6 приведены в Таблице 1. В этих примерах продолжающееся слово де Бройна отделено точкой от продолжения, которое дополняет его до слова, содержащего все слова длины (k+1), кроме 1k+1. Таблица 1. Продолжающиеся 2-слова де Бройна. Примеры можно искать как гамильтоновы пути в B(2, k+1), начинающиеся в 0k и заканчивающиеся в 10k-1.Предположительно, такой путь всегда можно выбрать так, чтобы он пересекался с каждым циклом графа B(2, k+1) (т.е. имел хотя бы одно общее с циклом ребро), за исключением петель 0k+1 и 1k+1, тогда он дополняется до «почти эйлерова» пути, который не покрывает только ребро 1k+1.

    Слова де Бройна

    Итак, что можно сказать про «наиболее плотные» слова для m и k, т.е. m-слова, содержащие в качестве своих подслов длины k все возможные m-слова длины k, причем ровно по одному разу каждое? Такие слова или последовательности известны под именем слов или последовательностей де Бройна (de Bruijn) шага k. Они были известны для случая m = 2 еще в 1894 г.[5], а впоследствии были независимо переоткрыты де Бройном [6] и еще несколькими авторами [7, 8, 9]. В статье [6] де Бройн поставил и решил задачу о том, сколько имеется циклов длины mk (цикл — это класс эквивалентности слов по циклическим перестановкам их символов, например, 0110, 0011 и 1001 относятся к одному циклу), содержащих все возможные m-слова длины k. Полученный им ответ Слова де Бройна, см. упражнение 2.3.4.2-23 в [10], показывает, что такие циклы существуют при всех m, k ? 1. Слово де Бройна получается из цикла разрезанием его в некотором месте и копированием (k-1) символа из начала в конец получившегося слова в обратном порядке, для того, чтобы сохранить подслова длины k. Обзор [11] дает наиболее полный экскурс в теорию слов де Бройна и историю их использования для решения различных задач. Там они названы циклами нелинейных сдвиговых регистров полной длины (full length nonlinear shift register cycles). Среди задач, связанных с комбинаторикой слов, в которых возникают слова де Бройна можно отметить построение псевдослучайных последовательностей [12], построение кодов [13, 14], кодирование образов [15, 16], построение машин на основе сдвиговых регистров [11, 17, 18], организацию CDMA-сетей. В связи с тестированием программного обеспечения они упоминаются в [19]. Слова де Бройна связаны с графами специального вида, называемыми также графами де Бройна. Граф де Бройна с параметрами m?1 и k?1 B(m, k) — это ориентированный граф с mk-1 вершинами V(m, k) = [0..(m-1)]k-1, являющимися всеми возможными m-словами длины (k-1), и ребрами E(m, k) = [0..(m-1)]k, являющимися всеми возможными m-словами длины k.
    При этом ребро x1x2…xk-1xk начинается в вершине x1x2…xk-1 и заканчивается в вершине x2…xk-1xk. Примеры графов де Бройна изображены на рис. 1. Достаточно легко убедиться в выполнении следующего утверждения. Утверждение 1. 1) Граф B(m, 1) имеет ровно одну вершину — пустое слово — и m ребер-петель. 2) Граф B(m, 2) изоморфен полному ориентированному графу с петлями на m вершинах. 3) Количества входящих и выходящих ребер для любой вершины B(m, k) равны m. "v Î V(m, k) in-deg(v) = out-deg(v) = m Слова де Бройна
    Рисунок  1. Графы B(3, 1), B(3, 2), B(2, 3), B(2, 4). Последний пункт непосредственно влечет следующее. Утверждение 2. 1) Для всех m, k ? 1 граф B(m, k) эйлеров, т.е. в нем существует цикл, включающий все ребра 2) Любой эйлеров путь в B(m, k) однозначно соответствует слову де Бройна, а значит такие слова существуют для всех m, k ? 1.
    Для построения слова, соответствующего пути в B(m, k), выпишем слово, соответствующее первому ребру пути, затем для каждого следующего ребра пути будем приписывать в конец полученного слова последний символ этого ребра. Определим для ориентированного графа G дуальный граф L(G) как граф, имеющий в качестве вершин множество ребер G, а в качестве ребер — множество пар смежных ребер G, т.е. таких, что конец первой является началом второй. При этом ребро L(G) начинается в вершине, соответствующей первому элементу пары, а кончается в вершине, соответствующей второму. Утверждение 3. 1) Для всех m, k ? 1 дуальный граф к B(m, k) изоморфен B(m, k+1).
    Это легко проверить, заметив, что паре смежных ребер B(m, k) соответсвует слово длины (k+1), построенное по правилу из второго пункта предыдущего замечания.
    Кроме того, все m-слова длины (k+1) могут быть получены таким способом. 2) Эйлеров путь или цикл на графе G соответствует гамильтонову (проходящему через каждую вершину ровно один раз) пути или циклу на графе L(G) 3) Для всех m, k ? 1 граф B(m, k) имеет гамильтонов цикл Сделанные замечания позволяют определить достаточно эффективный алгоритм построения слов де Бройна — для этого достаточно построить граф B(m, k), что требует объема памяти и времени O(mk), и найти в нем эйлеров цикл, что можно сделать за время, пропорциональное количеству ребер графа, т.е.


    опять за O(mk), и используя такой же объем памяти. Длина слова де Бройна равна mk+k-1, т.е. тоже O(mk), следовательно, такой алгоритм оптимален по порядку. Можно улучшить «внутренние» показатели эффективности такого алгоритма, т.е. уменьшить объем памяти, занимаемый внутренними структурами данных и время их обработки, не учитывая внешнюю память и время, используемые для вывода результата. Различные алгоритмы для порождения слов де Бройна довольно часто упоминаются в литературе, в том числе и алгоритмы с лучшими показателями «внутренней» эффективности. Часть из них основана на неожиданной связи между словами де Бройна и словами Линдона (Lyndon words). Слово Линдона длины k в алфавите мощности m — это лексикографически минимальный представитель m-цикла, т.е. класса эквивалентности m-слов по циклическим перестановкам их символов. Оказывается, что верно следующее утверждение. Утверждение 4. [11] Конкатенация лексикографически упорядоченной последовательности всех слов Линдона длин, делящих k, в некотором алфавите дает лексикографически минимальный цикл де Бройна шага k в том же алфавите (для получения из него слова де Бройна достаточно добавить первые k-1 символов из начала в конец). Эффективный (требующий ограниченного константой «внутреннего» времени на построение одного слова Линдона) алгоритм построения слов Линдона и слов де Бройна на их основе представлен в [20]. Другие алгоритмы можно найти в [21–28]. В работе [29] эмпирически сравнивается эффективность по времени алгоритмов из [20], [25] и [27], и последний демонстрирует наиболее высокое быстродействие.

    Универсальные покрывающие последовательности

    Теперь рассмотрим другой способ интерпретации «как можно более разнообразного» поведения m-слова. Можно считать, что тестируемая система является некоторым небольшим конечным автоматом и строить искомое слово как покрывающее все возможные конечные автоматы с числом состояний, меньшим некоторого k (нужно рассматривать только сильно связные автоматы, в которых все входные стимулы допустимы во всех состояниях, иначе их нельзя покрыть с помощью одного слова, кроме того, остановимся пока на детерминированных автоматах). Нужное k можно выбрать как максимальное, допускающее покрывающее все автоматы слово длины, не большей N. Таким образом, мы ищем m-слова, покрывающие все детерминированные сильно связные конечные автоматы с не более чем k состояниями и m входными символами, определенными во всех состояниях (автоматы, в которых во всех состояниях допустимы одни и те же m символов называют m-регулярными). Однако «покрывать» можно разные элементы автомата. Достаточно естественно такими элементами считать все состояния, все переходы, пары смежных переходов и пр., и рассматривать разные слова для этих случаев. Универсальной покрывающей m-последовательностью или универсальным покрывающим m-словом шага k ? 1  и глубины l ? 0 (universal covering word) назовем m-слово, которое, будучи подано на вход любому детерминированному сильно связному m-регулярному автомату с k состояниями, определит в нем маршрут, содержащий все возможные маршруты длины l данного автомата. При l= 0 считаем маршрутами длины 0 все его состояния. Обозначим множество универсальных покрывающих m-слов шага k и глубины l через UC(m, k, l). Можно усомниться в том, что наша гипотеза о тестируемой системе как автомате с не более чем k состояниями, хорошо согласуется с реальностью — ведь число состояний в большинстве реальных систем таково, что требующиеся последовательности будут иметь колоссальную длину. Однако такая гипотеза приобретает более глубокий смысл, если считать, что состояния системы разбиваются на не более чем k групп так, что переход по любому стимулу осуществляется из одной группы в другую или в ту же (т.е.
    отсутствуют такие стимулы, что из некоторой группы состояний переходы по этому стимулу ведут в состояния нескольких разных групп). При этом иногда можно считать, что различия между состояниями в рамках одной группы гораздо меньше, чем между состояниями различных групп, и поэтому прежде всего важно протестировать поведение системы относительно разных групп ее состояний. В имеющейся литературе универсальные покрывающие слова практически не упоминаются, в отличии от слов де Бройна. Некоторое количество работ посвящено аналогу универсальных покрывающих слов глубины 0 (т.е. покрывающих все состояния) для неориентированных графов под именем универсальных обходящих последовательностей (universal traversal sequences, введены Куком, S. A. Cook, в конце 70-х годов прошлого века). Фокусом внимания этих работ является не собственно построение таких последовательностей, а одна из проблем теории сложности алгоритмов — как соотносятся классы сложности P-log-SPACE детерминированных алгоритмов, требующих полиномиально-логарифмической памяти, и NP-log-SPACE недетерминированных алгоритмов с такими же требованиями к памяти. Дело в том, что задача построения пути между двумя произольными вершинами графа является примером NP-log-SPACE задачи, а универсальная обходящая последовательность дает детерминированный алгоритм решения для нее. Тем самым верхние и нижние границы длины универсальных обходящих последовательностей задают правила преобразования сложности NP-log-SPACE алгоритма для решения некоторой задачи в сложность P-log-SPACE алгоритма для нее же. В первой известной автору статье [33], в которой появилось понятие универсальной обходящей последовательности, было показано, что для любых m, k ? 1 существует универсальная обходящая последовательность для m-регулярных неориентированных графов с k вершинами длины O(m2k3log k), а при m = 2 даже O(k3). Для ориентированного случая, который нас интересует, все обстоит несколько сложнее — длина универсальных покрывающих слов как минимум экспоненциальна в зависимости от k.


    Для доказательства этого достаточно заметить, что такие слова, как минимум, не короче слов де Бройна (см. Утвержедние 7). Доказать, что универсальные покрывающие слова существуют для всех m, k ? 1 и l ? 0, достаточно просто. Для этого заметим, что m-регулярных автоматов с k состояниями конечное множество — число способов направить m переходов из одного состояния равно km, число возможных способов их компоновки в автомат kkm, а если учесть возможность произвольной перенумерации состояний, отличных от начального, остается kkm/(k-1)! неизоморфных автоматов. «Почти все» из них сильно связны (т.е. доля не являющихся сильно связными автоматов уменьшается с ростом k и m. Если строить универсальное покрывающее слово достаточно прямолинейно — покрывать один за другим пути длины l в одном автомате, затем в другом и т.д., при этом на проход в первую вершину очередного непокрытого пути тратится не более (k-1) шагов — то через максимум (kml)(l + k - 1)(kkm/(k-1)!) шагов все такие пути во всех автоматах будут покрыты — (l + k - 1) шаг делается для того, чтобы покрыть один путь, в каждом состоянии начинается ml путей, в одном автомате k состояний. Обозначим через US(m, k) множество m-слов, содержащих все возможные m-слова длины k в качестве подслов. Слова де Бройна являются наиболее короткими словами в US(m, k). Утверждение 7. 1) Для всех m ? 1 пустое слово лежит в UC(m, 1, 0).
    Для всех m ? 1 слово 012...(m-1) лежит в UC(m, 2, 0).
    Для всех m, l ? 1 UC(m, 1, l) = US(m, l-1), т.е. в качестве универсального покрывающего слова шага 1 и глубины l можно взять слово де Бройна шага (l-1). 2) Для всех m ? 1, k ? 2 UC(m, k, 0) Í US(m, k-1).
    Т.е. слово может быть универсальным покрывающим шага k и глубины 0, только если оно содержит в качестве подслов все возможные слова длины (k-1). Таким образом, длина такого слова не меньше mk-1+k-2. 3) Для всех m, k, l ? 1 UC(m, k+l, 0) Í UC(m, k, l).
    Зная универсальные покрывающие слова глубины 0 мы будем знать универсальные покрывающие слова для всех глубин, хотя, быть может, и не самые короткие. 4) Для всех m, k ? 1 UC(m, k+1, 0) = UC(m, k, 1).


    Т.е. универсальные покрывающие слова глубины 0 в точности совпадают с универсальными покрывающими словами глубины 1 для на единицу меньшего шага.
    Следствие: UC(m, k, 1) Í US(m, k). Для доказательства п. 2 рассмотрим семейство графов с k состояниями, изображенное на Рисунке 2. Универсальные покрывающие последовательности
    Рисунок 2. Семейство "плохих" графов. В каждом графе этого семейства некоторая выделенная последовательность (k-1) символов приводит из начального состояния в (k-1)-е, а все остальные символы во всех состояниях ведут в начальное. Всякая последовательность символов длины (k-1) встречается в качестве выделенной одном из графов семейства. Если в слове из UC(m, k, 0) нет какой-то последовательности длины (k-1) в качестве подслова, то состояние (k-1) соответствующего графа не будет покрыто. Для доказательства п. 3 предположим, что слово из UC(m, k+l, 0), будучи применено к некоторому автомату с k состояниями, не покрывает некоторый путь в нем длины l. Добавим в этот автомат новые l состояний так, чтобы этот путь начинался в том же состоянии, что и раньше, а дальше шел по новым состояниям. Переходы по всем символам из [0..(m-1)], не ведущим вдоль выделенного пути, из новых состояний направим в то состояние, которое было вторым на этом пути в исходном автомате. При этом получится m-регулярный автомат с (k+l) состояниями, по-пержнему сильно связный. Поскольку в исходном автомате наше слово не могло покрыть выделенный путь, а только по этому пути можно попасть в l-е состояние из добавленных, то в результирующем автомате наше слово не может покрывать это состояние, что противоречит его принадлежности UC(m, k+l, 0). Для доказательства утверждения п. 4 (осталось доказать включение UC(m, k, 1) Í UC(m, k+1, 0)) предположим, что слово из UC(m, k, 1) не покрывает некоторое состояние в некотором автомате с (k+1)-м состоянием. Поскольку автомат сильно связен, в это состояние ведет некоторое множество ребер и из него выводит хотя бы одно ребро. Перенаправим все ребра, ведущие в это состояние, в состояние, в которое входит это самое выводящее ребро.


    При этом сильная связность не нарушится, а в автомате останется k состояний. Значит, наше слово покрывает все ребра из числа перенаправленных. Рассмотрим то ребро из этого множества, которое покрывается первым. Поскольку оно первое из перенаправленных, путь, покрываемый до него словом в исходном и результирующем автоматах останется неизменным. Значит, в исходном автомате он должен далее пройти по этому ребру и попасть в непокрытое состояние. Полученная при доказательстве существования универсальных покрывающих слов верхняя оценка их длины слишком велика — на практике наиболее короткие покрывающие слова оказываются не намного длиннее соответствующих слов де Бройна. К сожалению, кроме приведенного выше замечания, автору не много известно о свойствах универсальных покрывающих слов и об алгоритмах их построения. То, что каждое универсальное покрывающее слово глубины 0 содержит все последовательности определенной длины в качестве подслов позволяет предположить, что можно строить такие слова на основе слов де Бройна. Сами по себе слова де Бройна не являются универсальными покрывающими в большинстве случаев (при k ? 3 в п. 2 Замечания 7). Например, минимальная длина элемента UC(2, 3, 0) = UC(2, 2, 1) равна 6 (001011 и 110100), а соответсвующие слова де Бройна имеют длину 5 (и универсальные покрывающие слова в данном случае — даже не продолжения слов де Бройна 00110, 01100, 10011, 11001). Неизвестно, выполнен ли аналог утверждения п. 4 Замечания 7 для глубин, больших 2 при k ? 2 (при k = 1 он точно не выполнен, поскольку UC(m, 1, l) совпадает с US(m, l), а, как только что было сказано, UC(m, 2, 1) уже отличается от US(m, 2)). Если это так, то можно было бы иметь дело либо только с универсальными покрывающими словами глубины 0 для разных шагов, либо только с универсальными покрывающими словами шага 2 для разных глубин (т.е. искать все универсальные покрывающие слова только на автоматах с двумя состояниями). Последнее свойство выглядит очень сильным, и поэтому есть сомнения в том, что оно выполнено. Все (с точностью до перестановок символов) известные автору универсальные покрывающие слова минимальных длин сведены в Таблице 2 (могут существовать универсальные покрывающие слова меньшей длины с заданными параметрами — те слова, про которые точно известно, что они имеют минимальную возможную длину, помечены звездочкой, в конце остальных слов стоит точка, кроме того, знаками вопроса помечены слова предположительно минимальной возможной длины). Таблица 2. Минимальные известные универсальные покрывающие слова.

    При тестировании систем, поведение которых

    При тестировании систем, поведение которых определяется не только последним обращением к ним, а и предшествующей историей работы, т.е. зависит от внутреннего состояния системы, необходимо строить тесты в виде последовательностей обращений, чтобы покрыть возникающие разнообразные ситуации. Если о системе известно немного, например, только список обращений, которые можно делать, построить ее тест в полном смысле этого слова нельзя, поскольку частью теста всегда является проверка правильности работы системы в ответ на тестовые обращения. Однако можно попробовать построить входные данные теста — определить какие операции с какими параметрами и когда вызывать, предположив, что проверка правильности работы системы производится отдельно. Ее может выполнять специальный модуль автоматической проверки, или же это может делать человек, понимающий, когда очередная реакция системы правильна, а когда — нет. При этом возникает две задачи: построение значений параметров вызовов и построение последовательности вызовов. Непосредственное решение этих задач путем построения всех возможных комбинаций значений параметров и всех возможных последовательностей обращений сразу приводит к комбинаторному взрыву, поэтому требуются методы построения небольшого числа тестов, которые, тем не менее, были бы достаточно качественны, то есть покрывали бы максимально возможное число различных ситуаций, связанных с поведением тестируемой системы. Первая задача при отсутствии дополнительной информации о тестируемой системе обычно решается на основе разбиений возможных значений параметров на конечное число групп и использования различных комбинаций значений из разных групп. Для построения этих комбинаций можно использовать покрывающие массивы (covering arrays) [1–3] дающие минимально возможные множества комбинаций, перебирающие все возможные сочетания пар, троек или другого числа значений отдельных параметров. Другие методы основываются на эвристических алгоритмах, вычисляющих приближения к минимальным покрывающим массивам, — это оправдано, поскольку построение такого массива с нужными параметрами является NP-полной задачей. Обзоры имеющихся результатов по построению покрывающих массивов см. в [1, 2]. Эта статья целиком посвящена возможным подходам к решению второй задачи — построения тестовых последовательностей, поскольку она в имеющейся литературе практически не затрагивается. В статье не излагается ее полное решение с каких бы то ни было позиций, а, скорее, рассматриваются несколько подходов к такому решению, основанных на похожих идеях, и освещаются известные автору результаты, полученные в рамках этих подходов.

    Данная статья представляет два возможных

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

    Тестирование софта - статьи

    Анализ и отображение результатов

    Когда Rational Robot закончит выполнение тестов, результаты тестирования будут отображены в наглядном виде в Rational TestManager[6]. Это программа для управления отчётами о тестировании, и, в случае ошибочной ситуации, выдаётся достаточная информация об ошибке для её нахождения и исправления.
    Типичный screenshot программы Rational TestManager представлен ниже на рисунке.
    Анализ и отображение результатов

    Автоматическая генерация тестов

    Калинов А.Я., Косачёв А.С., Посыпкин М.А., Соколов А.А.,
    Труды Института Системного Программирования РАН. В статье излагается метод автоматической генерации набора тестов для графического интерфейса пользователя, моделируемого детерминированным конечным автоматом с помощью UML диаграмм действий. Метод заключается в построении обхода графа состояний системы с применением неизбыточного алгоритма обхода [1,2] и компиляции построенного обхода в тестовый набор.

    Диаграмма состояний

    Диаграмма состояний – это формальная спецификация всех состояний системы и возможных переходов между ними с помощью диаграмм действий. Но во избежание так называемого "взрыва состояний" был использован метод расширенных конечных автоматов (EFSM – Extended Finite State Machine). При этом выделяется множество так называемых управляющих состояний и множество переменных. Реальные же состояния системы описываются управляющими состояниями и наборами значений переменных. Каждому переходу приписывается (кроме обычных стимулов и реакций) предикат от значений переменных и действия по изменению значения переменных. EFSM позволяют существенно сократить размер описания, сохраняя практически всю информацию о системе.
    На диаграммах имеются объекты типа State(Normal). Это управляющие элементы, или управляющие состояния. Переменные были введены как раз затем, чтобы не дублировать одно и тоже действие из разных состояний, а изобразить его один раз из одного управляющего состояния. Таким образом, каждому управляющему состоянию на диаграмме соответствует множество реальных состояний системы. То есть мы можем написать:
    Состояние = . (где UML-State – есть управляющее состояние) Соответственно, переходы между управляющими состояниями обретают предусловия (guard condition), определяющие, из каких конкретно состояний, соответствующих данному управляющему состоянию, возможен переход, и действия (action), определяющие, в какое конкретно состояние ведёт этот переход. Предусловия и действия записываются на простом языке, в котором есть следующие конструкции:
  • конструкция ::= список предусловий | список действий
  • список предусловий ::= предусловие | список предусловий, предусловие
  • список действий ::= действие | список действий, действие
  • предусловие ::= переменная = выражение
  • действие ::= переменная := выражение
  • выражение ::= переменная | константа
  • переменная ::= символ{символ}
  • константа ::= цифра{цифра}
  • символ ::= буква | цифра
  • Такой язык был выбран из-за простоты интерпретации, что упростило написание интерпретатора.
    Но, как оказалось, перечисленных возможностей вполне достаточно для описания большинства возможностей графического интерфейса.

    Ниже приведён пример диаграммы состояний с двумя переменными и одной поддиаграммой.

    Диаграмма состояний Поддиаграмма главной диаграммы, связанная с объектом типа Activity ‘Connect to Server’:

    Диаграмма состояний В этом примере используются две переменных – 'server' и ‘connect’. Значение переменной 'server' определяет состояние сервера (значение "1" означает, что сервер запущен, значение "0" означает, что сервер не запущен), а переменная 'connect' определяет наличие соединения с сервером (значение "1" означает, что есть соединение с сервером, а значение "0" означает, что соединения с сервером нет). Соответственно, интерпретировать эту диаграмму следует так:

  • Переход по событию ‘Connect to Server’ из управляющего состояния ‘mpCWorkshop’ означает переход в поддиаграмму, соответствующую объекту типа Activity ‘Connect to Server’, и дальнейший переход к состоянию ‘Connect to Server’.
  • Переход по событию ‘Close’ из управляющего состояния ‘Connect to Server’ ведёт в конечное управляющее состояние на этой поддиаграмме, что означает переход из Activity ‘Connect to Server’ на главной диаграмме в управляющее состояние mpCWorkshop.
  • Переход по событию ‘Connect’ из управляющего состояния ‘Connect to Server’ ведёт в объект типа Decision, в котором происходит разветвление в зависимости от значения переменной ‘server’.
  • Если сервер запущен (‘server=1’), то устанавливается соединение (‘connect:=1’), и, как и в случае 2, переход в конечное управляющее состояние этой поддиаграммы ведёт в управляющее состояние mpCWorkshop главной диаграммы.
  • Если сервер не запущен (‘server=1’), то переход ведёт в управляющее состояние mpCWorkshop этой поддиаграммы (это модальный диалог-предупреждение о том, что соединение не может быть установлено). Из этого управляющего состояния возможны два перехода – по событию ‘OK’ переход ведёт в управляющее состояние ‘Connect to Server’, а по событию ‘Cancel’, как и в случае 2, переход ведёт в управляющее состояние mpCWorkshop главной диаграммы.
  • Переход по событию ‘Exit’ из управляющего состояния ‘mpCWorkshop’ ведёт в конечное управляющее состояние главной диаграммы, что означает переход к начальному управляющему состоянию этой диаграммы, и дальнейший переход в состояние mpCWorkshop.
  • Итак, можно составить набор правил, определяющих переходы между состояниями:


  • Переход между состояниями осуществляется по событию и может состоять из нескольких переходов между управляющими элементами на диаграмме, к каковым относятся объекты типа State(Normal), Start State, End State, Activity, Decision, а также переход в поддиаграмму или возвращение из неё.
  • Событие есть непустое имя перехода.
  • Событие есть у всех переходов, начинающихся в управляющих элементах типа State(Normal), и только у них.
  • Переход из управляющего состояния типа State(Normal) происходит по событию.
  • Переход из управляющего состояния типа Start State, End State, Activity, Decision происходит без события.
  • Переход между состояниями начинается и заканчивается только в управляющих элементах типа State(Normal).
  • Переход между состояниями атомарный.
  • Каждый переход между управляющими элементами может иметь предусловия и действия. Наличие пустого предусловия по умолчанию означает его истинность. Пустое действие означает отсутствие изменения значений переменных.
  • Каждый переход может иметь несколько предусловий, разделённых оператором ",".
  • Каждый переход может иметь несколько действий, разделённых оператором ",".
  • Переходы детерминированы, то есть не может существовать двух переходов с одинаковым событием (или оба без события), начинающихся в одном управляющем состоянии и обладающих предусловиями, которые допускают одновременное выполнение.
  • На каждой диаграмме должны существовать начальное и конечное (не обязательно одно) управляющие состояния (на главной диаграмме конечное состояние может не существовать).
  • Поддиаграммы есть у объектов типа Activity, и только у них.
  • Если переход из какого-либо управляющего элемента ведёт в управляющий элемент типа Activity, это означает переход к начальному управляющему элементу поддиаграммы, соответствующей элементу типа Activity, и дальнейший переход без события.
  • Если переход из какого-либо управляющего элемента ведёт в конечный управляющий элемент какой-либо поддиаграммы, то это означает переход к управляющему элементу типа Activity, которому соответствует эта поддиаграмма, и дальнейший переход из этого элемента без события.
  • Если переход из какого-либо управляющего элемента ведёт в управляющий элемент типа Decision, это означает продолжение перехода от этого элемента без события.
  • Дальше диаграмма, составленная по таким правилам, подаётся на вход генератору тестов.

    Кроме этого с каждым объектом на диаграмме (модель, диаграммы, управляющие элементы, переходы) связаны требования (спецификации) к данному объекту. Эти требования размещаются в поле Documentation, предусмотренное Rational Rose для каждого объекта на диаграмме. Эта требования записываются в виде инструкций на языке SQABasic, которые выполняет Rational Robot. В большинстве случаев с переходами связаны инструкции, означающие какие-то действия пользователя, а с управляющими элементами связаны инструкции, означающие требования к данному состоянию, которые может проверить Rational Robot, По сути данный набор инструкций реализует оракул этого состояния.

    Генератор тестов

    Генератор строит по диаграмме состояний набор тестов. Условием окончания работы генератора является выполнение тестового покрытия. В данном случае в качестве покрытия было выбрано условие прохода по всем рёбрам графа состояний, то есть выполнения каждого доступного тестового воздействия из каждого достижимого состояния.
    Структурно генератор тестов состоит из следующих компонентов:
  • Интерпретатор
  • Итератор
  • Обходчик
  • Компилятор
  • Интерпретатор осуществляет переход между состояниями в соответствие с введёнными ранее правилами. В случае успешного перехода он останавливается и возвращает указатель на тот управляющий элемент, в котором он остановился, в случае неудачи он останавливается и выводит сообщение о возникшей ошибке (обычно это несоответствие введённым правилам). Для замыкания диаграммы вводится дополнительное условие: если переход из какого-либо управляющего элемента ведёт в конечный управляющий элемент главной диаграммы, то это означает переход к её начальному управляющему элементу и дальнейший переход без события.
    Связь интерпретатора с обходчиком выполняет итератор переходов. В соответствие с выбранным алгоритмом обхода, итератор возвращает событие, переход по которому ещё не был совершён из данного состояния. Если все переходы были совершены, итератор возвращает пустое событие.
    Обходчик есть ключевой компонент генератора тестов. Чтобы построить тестовый набор, он строит обход ориентированного графа состояний, проходя по всем дугам графа. Алгоритм его работы взят из [7,8,9]. Прибегнуть к специальному алгоритму обхода ориентированного графа заставило то обстоятельство, что граф состояний скрыт - на диаграмме действий изображён граф, состоящий из управляющих элементов. Это значит, что заранее неизвестны все возможные состояния графа, поэтому мы и воспользовались неизбыточным алгоритмом обхода ориентированного графа. В процессе работы обходчик вызывает итератор переходов для получения событий, соответствующих не пройденным переходам, и интерпретатор событий, последовательность которых формируется в соответствие с алгоритмом.
    Обходчик останавливается на любом графе, вынося вердикт, совершен обход или нет. В том случае, если нет, то выводится отладочная информация о причинах, не позволивших его совершить, чтобы можно было быстро их устранить (обычно это либо несоответствие правилам, либо тупиковое состояние).

    Компилятор (здесь термин компиляция подразумевается в его "не программистском" смысле – сборка). Как уже говорилось, с каждым объектом на диаграмме (модель, диаграммы, управляющие элементы, переходы) связан некоторый набор инструкций на языке SQABasic, которые выполняет Rational Robot. В большинстве случаев с переходами связаны инструкции, означающие какие-то действия пользователя, а с управляющими элементами связаны инструкции, означающие оракул этого состояния. В процессе обхода графа состояний, пройденный путь компилируется в тестовые файлы, содержащие инструкции для Rational Robot. Компиляция происходит путём записи информации, связанной с каждым пройденным объектом. Далее приведен фрагмент вызова функции записи документации:

    Автоматическая генерация тестов по диаграммам действий имеет следующие преимущества перед остальными подходами к тестированию графического интерфейса:Генератор тестов есть программа(script), написанная на языке ‘Rational Rose Scripting language’(расширение к языку Summit BasicScriptLanguage). В Rational Rose есть встроенный интерпретатор инструкций, написанных на этом языке, посредством которого можно обращаться ко всем объектам модели (диаграммы состояний).

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


  • Обработка ошибочной ситуации

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


  • Обзор имеющихся средств автоматического тестирования графического интерфейса

    Самым распространённым способом тестирования графического интерфейса является ручное тестирование. Естественно, у такого подхода имеются серьёзные недостатки, и самый главный из них – большая трудоёмкость, которая часто становится критичной при организации регрессионного тестирования. Другим недостатком такого подхода является отсутствие гарантий выполнения каждого доступного воздействия из каждого возможного состояния системы. Таким образом, приходится полагаться на однозначность реакции системы на одинаковые воздействия из разных состояний, то есть считать эти состояния эквивалентными для данного воздействия.
    Другим способом организации тестирования является автоматическое тестирование [2]. Существуют два раскрытия этого термина:
  • Ручное написание тестов и их автоматический прогон.
  • Автоматическая генерация тестов.
  • Из наиболее доступных инструментов для автоматизированного тестирования можно выделить следующие пять:
  • WinRunner
  • QA Run
  • Silk Test
  • Visual Test
  • Robot
  • Перечисленные инструменты применяются для автоматизированного тестирования, в том числе при тестировании GUI. В своей статье [3] Рэй Робинсон приводит сравнительную таблицу (см. таб. 1) этих инструментов по способности удовлетворять заданные потребности пользователя. При этом рассматриваются различные свойства этих инструментов:
  • Запись и проигрывание тестов
  • Возможности Web-тестирования
  • Возможности базы тестов и функции для работы с ней
  • Объектное представление
  • Сравнение рисунков
  • Обработка ошибочных ситуаций
  • Сравнение свойств объектов
  • Возможности расширения языка
  • И т.п.
  • (по 5-бальной шкале, наивысшая оценка - 1)
    WinRunner QA Run Silk Test Visual Test Robot
    Object Tests 1 1 1 2 1
    Support 1 2 2 2 2
    Ease of use 2 2 3 3 1
    Cost 3 2 3 1 2
    Integration 1 1 3 2 1
    Environment support 1 2 2 3 2
    Extensible Language 2 2 1 2 1
    Object Identity Tool 2 1 2 1 1
    Object Name Map 1 2 1 4 4
    Test/Error recovery 2 2 1 2 2
    Image testing 1 1 1 2 1
    Object Mapping 1 1 1 2 1
    Data functions 2 2 2 3 1
    Database tests 1 1 1 4 1
    Web Testing 1 2 2 3 2
    Record & Playback 2 1 1 3 1
    Таблица 1.

    Кроме самых распространённых, имеется много других инструментов для автоматического прогона тестов, созданных вручную. Но эти инструменты не предоставляют возможности автоматической генерации тестов (исключая процесс записи теста).

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

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

    Описание подхода

    Правильность функционирования системы определяется соответствием реального поведения системы эталонному поведению. Для того чтобы качественно определять это соответствие, нужно уметь формализовать эталонное поведение системы. Распространённым способом описания поведения системы является описание с помощью диаграмм UML (Unified Modeling Language) [4]. Стандарт UML предлагает использование трех видов диаграмм для описания графического интерфейса системы:
  • Диаграммы сценариев использования (Use Case).
  • Диаграммы конечных автоматов (State Chart).
  • Диаграммы действий (Activity).
  • С помощью UML/Use Case diagram можно описывать на высоком уровне наборы сценариев использования, поддерживаемых системой [5]. Данный подход имеет ряд преимуществ и недостатков по отношению к другим подходам, но существенным с точки зрения автоматизации генерации тестов является недостаточная формальная строгость описания.
    Широко распространено использование UML/State Chart diagram для спецификации поведения системы, и такой подход очень удобен с точки зрения генерации тестов. Но составление спецификации поведения современных систем с помощью конечных автоматов есть очень трудоёмкое занятие, так как число состояний системы очень велико. С ростом функциональности системы спецификация становится всё менее и менее наглядной.
    Перечисленные недостатки этих подходов к описанию поведения системы преодолеваются с помощью диаграмм действий (Activity). С одной стороны нотация UML/Activity diagram является более строгой, чем у сценариев использования, а с другой стороны предоставляет более широкие возможности по сравнению с диаграммами конечных автоматов. Следующие причины определяют пригодность диаграмм действий для моделирования графического интерфейса:
  • Графический интерфейс пользователя (GUI) в большинстве случаев удобно представить в виде конечного автомата, так как поведение системы зависит от состояния, в котором она находится, и воздействия пользователя (и, может быть, других внешних воздействий).
  • GUI имеет иерархичную структуру.
    Действительно, модальные диалоги (те, которые перехватывают управление) удобно моделировать с помощью отдельной диаграммы состояний (для верхней диаграммы это поддиаграммы), инкапсулируя тем самым их функциональность.
  • Диаграмма действий позволяет специфицировать систему таким образом, при котором отсутствует дублирование одинаковых событий из разных состояний. Достигается это использованием на диаграмме управляющих элементов, из которых возможны тестовые воздействия, а не самих состояний.
  • Спецификация диаграммами действий удобна для восприятия человеком.
  • Другими словами, спецификация графического интерфейса с помощью диаграмм действий является достаточно естественным способом описания требований к графическому интерфейсу. Причём при таком подходе сохраняется вся сила формальных спецификаций – как показывает опыт, большая часть ошибок выявляется именно на этапе составления спецификации системы.
    Итак, перейдём к детальному рассмотрению предлагаемого подхода.
    Для создания прототипа работающей версии данного подхода использовался инструмент Rational Rose [6]. Он использовался в первую очередь для спецификации графического интерфейса пользователя при помощи диаграмм действий UML.
    Для прогона сгенерированных по диаграмме состояний тестов использовался инструмент Rational Robot [6]. Из возможностей инструмента в работе мы использовали следующие:
    1. Возможность выполнять тестовые воздействия, соответствующие переходам между состояниями в спецификации.
    2. Возможность проверять соответствие свойств объектов реальной системы и эталонных свойств, содержащихся в спецификации. Из тех возможностей, которые доступны с помощью этого инструмента, используется проверка следующих свойств объектов:
  • Наличие и состояние окон (заголовок, активность, доступность, статус).
  • Наличие и состояние таких объектов, как PushButton, CheckBox, RadioButton, List, Tree и др. (текст, доступность, размер).
  • Значение буфера обмена.
  • Наличие в оперативной памяти запущенных процессов.
  • Существование файлов.
  • Общая схема генерации и прогона тестов выглядит следующим образом:
    Описание подхода Рассмотрим ее компоненты более подробно.

    Поставленная задача

    Разработка и реализация методов автоматизированной генерации набора функциональных тестов для GUI mpC Workshop[10,11].
    mpC Workshop – это среда для разработки приложений для параллельных вычислений на языке mpC[12,13,14] с функциональностью графического интерфейса типичной для такого типа систем. Ниже приведён типичный screenshot mpCWorkshop:
    Поставленная задача Для тестирования базовой функциональности была составлена базовая диаграмма состояний и переходов mpC Workshop. Ниже приведены фрагменты это диаграммы (включая поддиаграммы).
    Главная диаграмма выглядит следующим образом:
    Поставленная задача Такие объекты типа Activity, как "mpCWP", "mpCWB", "mpCWF", "mpcd", "Finished", "Workspace" означают наличие под собой поддиаграмм. Ниже приведены поддиаграммы, соответствующие объектам" mpCWP" и " mpCWB".
    Поставленная задача
    Поставленная задача С Activity "Connect to Server" связана следующая поддиаграмма:
    Поставленная задача Как видно из рисунков, на этих поддиаграммах тоже есть поддиаграммы. Фактически, каждый объект типа Activity скрывает под собой часть функциональности системы. Под "mpCWB" содержится описание функциональности, связанной с работой с сервером, под "mpCWP" содержится описание функциональности, связанной с работой с проектом, под "mpCWF" содержится описание функциональности, связанной с работой с файлами, и т.д.
    В этой базовой диаграмме используется 12 переменных с начальными значениями, приведёнными ниже:
  • connect, 0
  • connectbroken,0
  • mpcd, 0
  • exec, 2
  • build,0
  • compile,0
  • projectopen, 0
  • projectbuild, 0
  • projectexist, 0
  • projectpathenter, 0
  • projectpathcorrect, 0
  • openfilempcifproject,0
  • Практически с каждым объектом на диаграмме связаны тестовые инструкции на языке SQABasicScript. Ниже в таблице приведены примеры этих инструкций, связанных с управляющими состояниями и переходами:
    mpCWorskhop Window SetContext, "Caption={mpC Worksho*}; State=Enabled", "Activate=1; Status=MAXIMIZED"
    Result = ModuleVP (Exists, "Name = mpcworkshop.exe", "ExpectedResult = PASS")
    go mpCWP InputKeys "%p"
    Connect Window SetContext, "Caption={mpC Worksho*}; State=Disabled", "Activate=0; Status=MAXIMIZED"
    Window SetContext, "Caption=Connect to server; State=Enabled", "Activate=1"
    PushButton Middle_Click, "Text=Start server;State=Enabled"
    PushButton Middle_Click, "Text=Close;State=Enabled"
    PushButton Middle_Click, "Text=Advanced >>;State=Enabled"
    PushButton Middle_Click, "Text=Connect;State=Enabled"
    CheckBox Middle_Click, "Text=Once"
    CheckBox Middle_Click, "Text=Hidden"
    ListView Click, "Text=List1;\;ItemText=DIVER", ""
    uConnect PushButton Click, "Text=Connect;State=Enabled"

    И так далее….

    Генератор тестов для данной диаграммы генерирует тесты за время порядка 1-й минуты. Суммарный размер выходных тестов равен 1541024 байта, что примерно равно 1.5 мегабайта.

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

    Ниже приведен начальный кусок сгенерированного тестового кода. Видно, что тестовые воздействия перемежаются с оракулом заданных свойств состояний. Также вставляется строка: '------cutting line------.

    Она вставляется после каждого законченного набора тестовых инструкций, связанных с одним объектом диаграммы. Эта строка требуется для осмысленного разрезания сгенерированного скрипта, так как его размер примерно равен 1.5 мегабайта, а у прогонщика тестов Rational Robot есть ограничение на тест в размере 20 килобайт. Таким образом, большой тест разрезается на маленькие кусочки по линиям разреза для дальнейшего последовательного прогона.

    Sub Main ' copyright by ISP RAS 2004 ' implemented by A.Sokolov SQALogMessage sqaPass, "Starting test № 0", "" ' Initially Recorded: 07.06.2004 20:20:53 ' Class № 0 with name . ' Attributes: connect = 0, connectbroken = 0, mpcd = 0, exec = 2, build = 0, compile = 0, projectopen = 0, projectbuild = 0, projectexist = 0, projectpathenter = 0, projectpathcorrect = 0, openfilempcifproject = 0, IDState = 0, workDirectory$ = "E:\Alexey\Work\mpCGUITesting\mpCGUITestingProjects" '------cutting line------ StartApplication "mpcWorkshop" '------cutting line------ … Прогон сгенерированного теста длится около 50-ти минут. Скорость ввода символов и нажатия на объекты системы ограничена возможностями инструмента, но, тем не менее, она велика, и человеку практически невозможно совершать действия с такой скоростью.

    Практические результаты

    В ходе выполнения работы был обнаружен ряд ошибок в графическом интерфейсе mpC Workshop. Наибольшее количество ошибок было найдено на этапе составления спецификации системы. Это характерная черта использованного метода составления спецификации – по реальной работе системы. То есть на момент формализации графического интерфейса системы продукт mpC Workshop был уже разработан и реализован, таким образом сам процесс составления спецификации был уже своеобразным тестированием системы. На этом этапе было найдено порядка 10-15 ошибок в системе.
    После того, как была составлена спецификация основной функциональности тестируемой системы, был сгенерирован тестовый набор, ей соответствующий. После его прогона была выявлена одна трудноуловимая ошибка.

    Прогонщик тестов

    В качестве прогонщика тестов мы используем Rational Robot, который выполняет сгенерированные наборы инструкций. В случае удачного выполнения всех инструкций выносится вердикт – тест прошёл. В противном случае, если на каком-то этапе выполнения теста, поведение системы не соответствует требованиям, Robot прекращает его выполнение, вынося соответствующий вердикт – тест не прошёл.

    Развитие подхода

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


  • Список литературы

    1. С.Канер, Д.Фолк, Е.К.Нгуен, Тестирование программного обеспечения.
    2. Э.Дастин, Д.Рэшка, Д.Пол, Автоматизированное тестирование программного обеспечения.
    3. Ray Robinson, AUTOMATION TEST TOOLS, Date Created: 1st March 2001, Last Updated: 11th Sept 2001.
    4. UML. http://www.omg.org/technology/documents/formal/uml.htm
    5. С.Г.Грошев, Тестирование на основе сценариев, дипломная работа. МГУ, ВМиК.
    6. P.Kruchten. The Rational Unified Process. An introduction. //Rational Suite documentation. http://www.interface.ru/rational/rup01_t.htm
    7. И.Б.Бурдонов, А.С.Косачёв, В.В.Кулямин, "Неизбыточные алгоритмы обхода ориентированных графов. Детерминированный случай." "Программирование", -2003, №6–стр 59-69
    8. И.Б.Бурдонов, А.С.Косачёв, В.В.Кулямин, А.Хорошилов, Тестирование конечных автоматов. Отчет ИСП РАН.
    9. http://www.ispras.ru/~RedVerst/
    10. A.Kalinov, K.Karganov, V.Khatzkevich, K.Khorenko, I.Ledovskih, D.Morozov, and S.Savchenko, The Presentation of Information in mpC Workshop Parallel Debugger, Proceedings of PaCT-2003, 2003.
    11. http://mpcw.ispras.ru/
    12. A.Lastovetsky, D.Arapov, A.Kalinov, and I.Ledovskih, "A Parallel Language and Its Programming System for Heterogeneous Networks", Concurrency: Practice and Experience, 12(13), 2000, pp.1317-1343.
    13. A.Lastovetsky, Parallel Computing on Heterogeneous Networks. John Wiley & Sons, 423 pages, 2003, ISBN: 0-471-22982-2.
    14. http://www.ispras.ru/~mpc/
    15. И.Б.Бурдонов, А.С.Косачёв, В.В.Кулямин, А.К.Петренко "Подход UniTesK к разработке тестов", "Программирование", -2003, №6–стр 25-43

    Тестовый набор

    В процессе построения обхода, генератор тестов компилирует набор тестов - инструкции на языке SQABasic. Эти инструкции есть чередование тестовых воздействий и оракула свойств объектов, соответствующих данному состоянию.
    Для того, чтобы продолжать тестирование, когда один тест не прошёл, в генератор тестов встроена возможность выбора – генерировать один большой тест или набор атомарных тестов. Атомарный тест – тот, который не требует приведения системы в состояние, отличное от начального состояния.
    В связи с наличием ограничения инструмента прогона тестов на тестовую длину, в тесты после каждой законченной инструкции вставляется строка разреза. Во время прогона по этим строкам осуществляется разрез теста в случае, если его длина превышает допустимое ограничение. После прохождения части теста до строки разреза продолжается выполнение теста с первой инструкции, следующей за строкой разреза. Нарезку и сам прогон тестов осуществляет прогонщик тестов.

    Практически все программные системы предусматривают

    Практически все программные системы предусматривают интерфейс с оператором. Практически всегда этот интерфейс – графический (GUI – Graphical User’s Interface). Соответственно, актуальна и задача тестирования создаваемого графического интерфейса.
    Вообще говоря, задача тестирования создаваемых программ возникла практически одновременно с самими программами [1]. Известно, что эта задача очень трудоёмка как в смысле усилий по созданию достаточного количества тестов (отвечающих заданному критерию тестового покрытия), так и в смысле времени прогона всех этих тестов. Поэтому решение этой задачи стараются автоматизировать (в обоих смыслах).
    Для решения задачи тестирования программ с программным интерфейсом (API – Application Program Interface: вызовы методов или процедур, пересылки сообщений) известны подходы – методы и инструменты – хорошо зарекомендовавшие себя в индустрии создания программного обеспечения. Основа этих подходов следующая: создается формальная спецификация программы, и по этой спецификации генерируются как сами тесты, так и тестовые оракулы – программы, проверяющие правильность поведения тестируемой программы. Спецификации, как набор требований к создаваемой программе, существовали всегда, Ключевым словом здесь является формальная спецификация. Формальная спецификация – это спецификация в форме, допускающей её формальные же преобразования и обработку компьютером. Это позволяет анализировать набор требований с точки зрения их полноты, непротиворечивости и т.п. Для задачи автоматизации тестирования эта формальная запись должна также обеспечивать возможность описания формальной связи между понятиями, используемыми в спецификации, и сущностями языка реализации программы.
    В наиболее распространенных подходах используются специализированные языки формальных спецификаций, но существуют определенные трудности внедрения таких подходов в индустрию. Другой подход заключается в расширении обычных языков программирования (или других, привычных для пользователей языков) средствами спецификаций. Такой подход используется в методологии UniTesK [15]. В данной статье описана попытка применения такого подхода к автоматизации тестирования графических интерфейсов.

    в настоящей работе методика автоматизации

    Предложенная в настоящей работе методика автоматизации генерации тестов для GUI была разработана и апробирована с августа 2003 г. по май 2004 г. в ходе выполнения проекта по тестированию mpC Workshop. Исследование методов автоматизированной генерации тестов для GUI сейчас активно развивается.

    Тестирование софта - статьи

    Аннотация.

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

    Близкие работы

    Вопросу автоматического тестирования блока синтаксического разбора посвящены работы [], [], [], []. Автоматическое тестирование блока анализа статической семантики рассматривалось в [], []. Для этих блоков, понимаемых как булевские функции, в перечисленных работах предлагаются вполне удовлетворительные подходы к автоматизации их тестирования - как систематической генерации тестов, так и анализу результатов работы. Для решения задачи автоматической генерации тестов для back-end'а предлагаются следующие подходы. Один подход - это генерация различных синтаксически корректных предложений целевого языка с последующим отбором корректных программ методом проверки сгенерированного предложения на соответствие ASM спецификации (см. [], []). Достоинство этого подхода состоит в использовании формальной спецификации для описания класса корректных программ. Однако недостатком подхода является несистематичность и неэффективность процесса построения тестов. Другой подход состоит в генерации тестов на основе модели входных данных back-end'а в соответствии с некоторым критерием тестового покрытия, формулируемым в терминах модели (см. [], []). Достоинствами подхода являются использование формальных моделей данных и эффективная генерация тестов. В качестве недостатка можно указать необходимость ручной разработки некоторых компонентов генератора тестов. Для решения задачи построения автоматического оракула для back-end'а предлагаются следующие подходы. Ряд исследователей рассматривает способы построения оракулов для тестирования некоторых специальных видов оптимизаторов, основанные на анализе абстрактного внутреннего представления программы (см. [], []). Их достоинство состоит в четкой нацеленности на проверку оптимизатора. Однако недостатком подобных подходов является то, что, как правило, при тестировании реального компилятора внутреннее представление программы недоступно извне. Попытка избавиться от этого недостатка основана на идее инструментирования объектного кода компилятором []. Однако здесь проблема заключается в том, что для реализации этой идеи требуется вмешательство непосредственно в работу компилятора, что обычно невозможно сделать при тестировании реального коммерческого компилятора. Принципиально другим подходом к построению автоматического оракула для back-end'а является предлагаемый в ряде работ оракул, который проверяет сохранение семантики программы в процессе компиляции путем сравнения поведения откомпилированной программы с некоторым эталоном. Имеются исследования, в которых в качестве эталона рассматривается поведение той же программы, но откомпилированной другим "эталонным" компилятором []. Близкий подход состоит в сравнении трассы выполнения теста с эталонной трассой []. В других исследованиях в качестве эталона рассматривается поведение той же программы, интерпретируемой ASM машиной (см. [], []). Эти подходы являются вполне приемлемыми, однако они годятся лишь для случая тестирования компиляторов и интерпретаторов.

    Построение оракула для генератора кода

    В подходе к построению оракула для тестированию генераторов кода в трансляторах, предлагаемом в настоящей работе, реализуется классическая идея сравнения реального результата работы тестируемой системы с ожидаемым результатом []. В качестве основы для организации тестирования применяется модельный подход, описанный выше в предварительных сведениях. Однако в нашем случае из каждой модельной структуры генератор тестов, кроме собственно входных тестовых данных,ё должен создавать также и ожидаемый эталонный результат трансляции - либо полностью, либо лишь некоторую абстрактную выжимку, своего рода систему показателей, характеризующих ключевые свойства выходных данных транслятора. Для этого генератор тестов должен содержать два разных меппера: один для отображения модельной структуры во входные тестовые данные, другой для ее отображения в эталонную систему показателей выходных данных (см. Рис. 3). Построение оракула для генератора кода Рис. 3.Схема генерации входных данных и эталонного результата Для того чтобы было возможно организовать два разных отображения из одной модельной структуры, модель должна строиться с учетом информации не только о входном представлении данных (см. ), но и об их выходном представлении. В простейшем случае, когда транслятор лишь "переводит" термины входного представления в какие-то соответствующие термины выходного представления, оказывается, что одна и та же модель пригодна для моделирования как входных, так и выходных данных. В случае же, когда транслятор сначала осуществляет некоторый анализ входных данных, а лишь затем генерирует выходные данные, содержащие результаты проведенного анализа, модель следует обогатить дополнительными элементами, которые представляют собой соответствующие результаты анализа. При этом итерацию модельных структур следует организовать так, чтобы элементы модели, соответствующие "результатам анализа", конструировались непосредственно в процессе итерации - это возможно, например, если организовать итерацию одних элементов модели в зависимости от уже созданного значения другого элемента модели. Далее мы будем говорить о предложенной схеме генерации тестовых данных в контексте тестирования не всего генератора кода в целом, а лишь некоторого ограниченного его аспекта, поскольку это существенно упрощает модель и облегчает разработку итераторов и мепперов. Итак, генератор тестов вместе с входными данными генерирует также соответствующую эталонную систему показателей выходных данных.
    При использовании такой схемы генерации тестовых данных анализ правильности работы генератора кода в трансляторе состоит в проверке результата работы транслятора на соответствие созданной генератором тестов эталонной системе показателей (см. Рис. 4). Построение оракула для генератора кода Рис. 4. Схема работы оракула Таким образом, общий процесс тестирования состоит из следующих шагов:
  • Анализируется документация на генератор кода в трансляторе, и выделяются те аспекты его работы, которые требуется протестировать;
  • Для каждого аспекта выделяются термины и шаблоны входных данных, строится модель;
  • В рамках данного аспекта анализируется алгоритм генерации кода, модель обогащается дополнительной информацией для моделирования результатов трансляции;
  • Разрабатывается итератор модельных структур;
  • Разрабатываются мепперы:
  • для отображения модельных структур в предложения входного языка;
  • для отображения модельных структур в эталонную систему показателей выходных данных;
  • Производится автоматическая генерация тестовых данных:
  • входные данные транслятора;
  • эталонная система показателей ожидаемых выходных данных транслятора;
  • Для каждой сгенерированной пары тестовых данных в автоматическом режиме производится оценка правильности работы генератора кода в трансляторе: входные тестовые данные подаются на вход транслятору, полученный результат работы транслятора сравнивается на соответствие эталонной системе показателей.

    Предварительные сведения

    В работе [] предложен подход к автоматическому тестированию оптимизирующих компиляторов на основе моделей. Модель строится на основе абстрактного описания алгоритма заданной оптимизации. Алгоритм оптимизации формулируется с использованием терминов, обозначающих сущности некоторого подходящего абстрактного представления программы, такого как граф потока управления, граф потока данных, таблица символов и пр. Оптимизатор для осуществления своих трансформаций ищет сочетания сущностей абстрактного представления программы, которые удовлетворяют некоторым шаблонам (например, наличие в программе циклов, наличие в теле цикла конструкций с определенными свойствами, наличие в процедуре общих подвыражений, наличие между инструкциями зависимости данных некоторого вида и пр.). После анализа алгоритма строится модель: каждому термину соответствует свой элемент модели, причем элементы модели могут путем связывания друг с другом образовывать структуры, соответствующие шаблонам. Процесс генерации тестов осуществляется следующим образом. Перебираются (итерируются) различные модельные структуры, которые потом отображаются (меппируются) в соответствующие программы на входном языке компилятора (см. Рис. 2). Предварительные сведения Рис. 2. Общая схема работы генератора тестов Такой генератор дает возможность получать тесты на исходном языке, в которых присутствуют конструкции, соответствующие выделенным терминам, в различных комбинациях при минимальном содержании других конструкций языка. Таким образом, генератор оказывается "нацеленным" на создание тестов для тестирования именно заданного оптимизатора. В рамках этого подхода оракул проверяет только сохранение семантики выполнения программы во время оптимизации. Для этого в качестве тестовых воздействий на оптимизатор генерируются такие программы, семантика выполнения которых полностью представляется их трассой. Такое свойство тестов позволяет свести задачу проверки сохранения семантики к сравнению трассы выполнения откомпилированного теста с некоторой эталонной трассой. Описанный подход успешно применялся для генерации тестов не только для тестирования компиляторов языков программирования, но также для тестирования оптимизаторов графических моделей [], реализации протокола IPv6, подсистемы построения отчетов в биллинговой системе, обработчиков XML-логов [].

    A. UML-диаграммы моделей

    A. UML-диаграммы моделей Рис. 5. Модель структуры БД (без ограничений)

    A. UML-диаграммы моделей Рис. 6. Модель ссылочных ограничений в SQL
    1(к тексту)Этот транслятор разрабатывался в ИСП РАН в рамках проекта по созданию инструментальной поддержки для автоматической генерации тестовых данных сложной структуры [].


    В качестве примера применения предложенного

    В качестве примера применения предложенного подхода рассмотрим тестирование генератора кода в трансляторе , который отображает описание схемы БД на языке SQL в описание структуры таблиц на языке RelaxNG [] и в описание ограничений, наложенных на эту структуру, на языке XML. Ниже описаны модели для трех аспектов работы данного генератора кода:
  • Трансляция основных конструкций структуры БД (таблицы, столбцы, типы данных) без каких-либо ограничений;
  • Трансляция ссылочных ограничений;
  • Трансляция ограничений вида "CHECK". Модели структур входных данных задаются в виде формальных описаний на языке TreeDL []. Генераторы тестов разрабатывались на языке программирования Java с использованием инструмента OTK (см. [], []), который предоставляет поддержку описанного метода разработки тестов. В первой модели, описывающей структуру БД без ограничений, моделируются понятия: таблицы, столбцы, типы данных SQL и их параметры. UML-диаграмма для этой модели приведена в приложении (см. ). В качестве эталонной системы показателей для этого аспекта использовалось описание структуры БД на языке RelaxNG в том виде, в котором его должен сгенерировать транслятор. В данном случае одна и та же модель подходит для моделирования как входных, так и выходных данных: транслятор просто "переводит" такие понятия, как "таблица", "столбец" и т.д., в описание элементов с соответствующими тегами на языке RelaxNG. Работа оракула заключается в текстуальном сравнении RelaxNG-файла - результата работы транслятора с эталонным RelaxNG-файлом. Во второй модели, описывающей ссылочные ограничения, моделируются понятия: таблица, ссылка между двумя таблицами. UML-диаграмма для этой модели приведена в приложении (см. ). В качестве эталонной системы показателей для этого аспекта использовалось описание ограничений на языке XML в том виде, в котором его должен сгенерировать транслятор. Для обеспечения корректной генерации входных тестовых данных модель была дополнена информацией о столбцах, а также был введен объемлющий модельный элемент, моделирующий схему БД в целом. При итерации структур зависимостей между таблицами в генераторе тестов применялась вспомогательная абстрактная модель, которая моделирует ациклические графы.
    Эти графы использовались в качестве "макета" для построения конкретных модельных структур. Работа оракула заключается в текстуальном сравнении XML-файла - результата работы транслятора с эталонным XML-файлом. Третья модель, описывающая ограничения вида "CHECK", моделирует следующие понятия: логические выражения, SQL-предикаты (такие, как "LIKE", "BETWEEN" и т.д.). UML-диаграмма для этой модели приведена в приложении (см. Приложение А). В качестве эталонной системы показателей для этого аспекта использовалось описание ограничений на языке XML в том виде, в котором его должен сгенерировать транслятор. Для обеспечения корректной генерации входных тестовых данных модель была дополнена информацией о столбцах, таблицах и объемлющей схеме, а также, для обеспечения корректности применения конкретных SQL-предикатов к данным, были добавлены типы данных. Работа оракула заключается в текстуальном сравнении XML-файла - результата работы транслятора с эталонным XML-файлом. Отметим, что та часть третьей модели, которая отвечает за моделирование типов данных, была целиком переиспользована из первой модели. Более того, были переиспользованы и соответствующие компоненты генератора тестов. В Таб. 1 приведены размеры формальных описаний разработанных моделей, а также размер кода разработанных компонентов генераторов тестов. Отметим, что подавляющее большинство компонентов генераторов тестов было сгенерировано автоматически.

    Модель Количество элементов модели Размер модели (байт / строк) Количество классов Размер классов (Кбайт/строк) Всего Вручную Всего Вручную
    Структура БД 36 4519 / 314 48 2 95 / 2669 8 / 271
    Ссылочные ограничения 6 1165 / 65 16 7 25 / 847 10 / 378
    Ограничения вида "CHECK" 51 6456 / 444 73 7 148 / 4039 20 / 679
    Таб. 1. Размер кода разработанных моделей и генераторов тестов. В Таб. 2 приведены размеры переиспользования формальных описаний разработанных моделей, а также размер переиспользованного кода разработанных компонентов генераторов тестов.


    Как видно из этой таблицы, в модели, описывающей ограничения вида "CHECK", около 60% кода описания модели и около 40% кода компонентов генератора тестов переиспользуется из других моделей.

    Модель Количество элементов модели Размер модели (байт / строк) Количество классов Размер классов (Кбайт/строк) Всего Вручную Всего Вручную
    Ограничения вида "CHECK" 33 3816 / 276 37 2 60 / 1717 8 / 271
    Таб. 2. Размер переиспользованного кода разработанных моделей и генераторов тестов. В Таб. 3 приведены некоторые характеристики сгенерированных тестов для рассмотренных моделей. Как видно из этой таблицы, объем разработанного вручную кода компонентов генераторов тестов примерно на два порядка меньше объема тестов, сгенерированных этими генераторами.

    Модель Количество тестов Общий объем (Кбайт) Средний размер теста (байт / строк)
    Структура БД 151 826 5471 / 140
    Ссылочные ограничения 38 353 9297 / 235
    Ограничения вида "CHECK" 46 915 19891 / 455
    Таб. 3. Некоторые характеристики сгенерированных тестов. В результате тестирования рассмотренного в этом примере транслятора с помощью предложенного в данной статье подхода на тестах, сгенерированных на основе представленных в здесь моделей, было обнаружено несколько несоответствий между документацией и реализацией генератора кода в трансляторе. Эти несоответствия связаны с представлением в выходных данных транслятора параметров типов данных и параметров ограничений.

    Тестирование трансляторов: проблема построения оракула для генератора кода

    Гингина В.В., Зеленов С.В., Зеленова С.А.
    Труды Института системного программирования РАН

    к тому, что во все

    Развитие компьютерной индустрии и информационных технологий привело к тому, что во все большем числе областей бизнеса обработка документов производится автоматически с помощью компьютеров. Чтобы быть пригодными для компьютерной обработки, данные оформляются в виде формального текста, т.е. текста на некотором формальном языке. Транслятор - это программа, осуществляющая обработку формального текста и перевод его в некоторое другое представление. Примерами трансляторов являются компиляторы и интерпретаторы языков программирования, XML-процессоры, браузеры HTML-страниц, системы поддержки специфицирования и моделирования, текстовые процессоры и издательские системы, серверы запросов СУБД и проч. Трансляторы используются при разработке ПО, при организации работы сетевых приложений, при создании разного рода распределенных информационных систем и т.д. Широкое распространение трансляторов, их повсеместное как прямое, так и косвенное использование в различных областях компьютерной индустрии, включая критические, обуславливает высокие требования к качеству трансляторов. Существует много разных подходов к достижению качества программного обеспечения (ПО). Одним из подходов является проведение аналитического доказательства корректности ПО. Вопросам теоретического обоснования корректности поведения компиляторов на основе использования различных логических исчислений посвящены, например, работы [], [], []. Однако, при разработке программных систем высокой сложности, какими являются реальные трансляторы, более практичным подходом является тестирование []. При тестировании встают две основные проблемы:
  • Проблема систематического создания тестовых данных для испытания работы тестируемой системы во всех различных ситуациях.
  • Проблема построения оракула для вынесения вердикта о корректности работы тестируемой системы. Ввиду сложности и насыщенности современных формальных языков, для которых разрабатываются трансляторы, очень велики объемы добротных наборов тестов и сложность анализа результатов тестирования.
    Поэтому очень актуальна проблема автоматизации как процесса создания тестовых данных, так и процедуры вынесения вердикта. Наиболее многообещающим в плане автоматизации подходом является тестирование на основе формальных спецификаций и моделей []. В трансляторах традиционно выделяют следующие функциональные блоки: front-end, осуществляющий синтаксический разбор и семантический анализ входного текста и построение его внутреннего представления, и back-end, осуществляющий дополнительный анализ входных данных, возможно, некоторые оптимизирующие преобразования, а также собственно генерацию выходного представления (см. Рис. 1). к тому, что во все Рис. 1.Функциональные блоки транслятора Тестирование сложных программных систем нужно проводить как на системном уровне, так и на уровне отдельных модулей и блоков и на уровне проверки отдельных аспектов функциональности. В настоящее время известно большое количество исследовательских работ, посвященных тестированию отдельных функциональных блоков трансляторов (см. раздел 5). Можно утверждать, что для блоков, входящих во front-end транслятора, если рассматривать эти блоки как булевские функции, существуют приемлемые подходы к автоматизации их тестирования - систематической генерации тестов и анализу результатов работы тестируемой системы (см. [], [], [], [], [], []). В вопросе проверки качества работы back-end'а можно утверждать, что в настоящее время существуют удовлетворительные подходы к решению проблемы автоматического построения тестовых данных (см. [], [], [], []). Что же касается проблемы оракула при тестировании back-end'а, то существующие практически применимые подходы основаны на идее анализа наблюдаемого поведения выполнения результата трансляции, и таким образом применимы лишь к компиляторам и интерпретаторам (см. [], [], [], [], [], [], []). Однако существует большой класс трансляторов, которые продуцируют "неисполнимые" тексты, а значит, к ним не применимы имеющиеся подходы к решению проблемы оракула. В настоящей статье предлагается подход к автоматическому построению тестов для трансляторов, обеспечивающий решение проблемы оракула для генератора выходного представления. Статья состоит из введения, пяти разделов и списка литературы.В первом разделе приводятся предварительные сведения, касающиеся подхода к генерации тестов для оптимизаторов на основе моделей. Во втором разделе дается описание предлагаемого в настоящей статье подхода. В третьем разделе рассматривается пример применения предлагаемого подхода на практике. В четвертом разделе приводится обзор близких работ. В заключение работы обсуждаются достоинства и недостатки, а также направления дальнейшего развития предлагаемого подхода.

    В статье предложен общий подход

    В статье предложен общий подход к построению автоматического оракула для тестирования генераторов кода в трансляторах текстов на формальных языках, а также предложена инструментальная поддержка для практического использования этого подхода. Архитектура оракула основана на идее одновременной генерации как тестовых входных данных для транслятора, так и соответствующей эталонной системы показателей, характеризующих ключевые свойства выходных данных транслятора. Несмотря на то, что в ходе разработки необходимых компонентов генератора тестов фактически требуется разработка своего рода "эталонного генератора кода", количество требуемых для этого ресурсов, как правило, существенно меньше того количества ресурсов, которое требуется на разработку реального генератора кода в тестируемом трансляторе. Экономия достигается за счет следующих факторов:
  • модель, для которой требуется разработать "эталонный генератор кода", является абстракцией исходного языка выходных данных транслятора, для которого разрабатывается реальный генератор кода, и поэтому, как правило, модель существенно меньше и проще реального языка;
  • разрабатываемый "эталонный генератор кода" должен, на самом деле, генерировать не все выходные данные, а лишь абстрактную систему показателей, объем данных которой, как правило, гораздо меньше;
  • при разработке генераторов тестов для нескольких аспектов тестирования одного транслятора одни и те же термины часто присутствуют в моделях для нескольких аспектов тестирования, что обуславливает высокий процент переиспользования кода компонентами генераторов тестов. Направлениями дальнейшего развития предложенного подхода являются:
  • проработка методов построения модели на основе анализа входных и выходных данных транслятора;
  • разработка методов выделения эталонных систем показателей выходных данных;
  • разработка методов сравнения реального результата работы транслятора на соответствие эталонной системе показателей выходных данных для различных практически используемых частных видов языков выходных данных трансляторов.

    Тестирование софта - статьи

    Алгоритм построения тестового набора

    Разработанный алгоритм сначала проверяет полученный на вход граф состояний документа согласно следующим ограничениям целостности:
  • Граф должен содержать ровно одно начальное состояние и как минимум одно конечное состояние, достижимое из начального. При нарушении этого условия выдаётся сообщение об ошибке, и алгоритм прекращает работу.
  • Граф не должен содержать состояний, недостижимых из начального или из которых не достижимо ни одно конечное состояние. При обнаружении таких состояний выдаётся предупреждение, и они удаляются из графа вместе со всеми инцидентными им переходами. После проверки входного графа состояний документа генерируются все возможные на нём тесты. Каждый тест моделируется маршрутом по графу, ведущим из начального состояния в одно из конечных. Тесты генерируются без ограничения длины, но со следующим ограничением: рассматриваются только такие маршруты, которые проходят через каждое состояние не более 2 раз. Такое ограничение позволяет разумно ограничить количество тестов, из которых будет в дальнейшем конструироваться тестовый набор, гарантируя притом покрытие любого элементарного события (включая петлевые дуги, а также такие дуги и вершины, которые недостижимы на путях без циклов), но не гарантирует покрытие всех возможных упорядоченных пар событий. Применение данного алгоритма на графах реально используемых документов, даже с очень сложными критериями покрытия, показало, что такое ограничение оставляет непокрытыми только тестовые ситуации настолько нетривиальные, что ими в большинстве случаев можно пренебречь. При завершении работы инструмент выдаёт вместе с построенных тестовым набором список непокрытых тестовых ситуаций для дальнейшего анализа и, при необходимости, ручной доработки тестового набора. Далее строится полный набор тестовых ситуаций, которые необходимо покрыть, согласно правилам, описанным в главе «Критерии тестового покрытия», и из него удаляются тестовые ситуации некоторых особых видов, в частности:
  • Ситуации вида «Начальное состояние после любого события», как заведомо недостижимые.
  • Ситуации вида «Операция создания документа после любого события, кроме начального состояния», как заведомо недостижимые.
  • Ситуации вида «Любое событие не после начального состояния», как заведомо недостижимые.
  • Ситуации вида «Переход после своего начального состояния», как заведомо дублирующие ситуации «Переход после начального состояния документа», так как одна ситуация не может быть достигнута при не достигнутой другой.
  • Ситуации вида «Переход не после своего начального состояния», как заведомо недостижимые. Для каждого построенного на предыдущем шаге теста вычисляется множество тестовых ситуаций, покрываемых им.
    Все построенные тестовые ситуации считаются при дальнейшей оптимизации тестового набора равноценными. Далее из построенного множества путей строится тестовый набор минимальной сложности, покрывающий все возможные тестовые ситуации, с помощью нижеописанного эвристического алгоритма. В записи алгоритма используются следующие условные обозначения:
  • |S| - мощность множества S
  • Len(t) ? длина теста t, измеряемая в количестве тестовых воздействий
  • {} ? пустое множество
  • <> - пустой упорядоченный список Фиксируем ограничение длины тестов M, которое является параметром данного алгоритма. Полагаем множество непокрытых тестовых ситуаций C:= полный набор построенных ранее тестовых ситуаций, множество доступных тестов T:= полный набор ранее построенных тестов, а тестовый набор R:=<>. Тестовый набор строится в две итерации: на первой рассматриваются только тесты с длиной, не превышающей M, а на второй рассматриваются все оставшиеся тесты. На каждой итерации применяется следующий «жадный» алгоритм:
  • Для каждого теста t из T вычисляем Cov(t):= множество тестовых ситуаций из C, покрываемых им. Если Cov(t)={}, то удаляем t из T.
  • Перебираем в произвольном порядке все тесты t из всего множества T на второй итерации, и только те тесты, длина которых не превышает M, на первой. Если перебираемое множество пусто, то завершаем текущую итерацию. Ищем среди перебираемых тестов лучший следующим образом:
  • Первый встреченный в ходе перебора тест объявляем лучшим.
  • Каждый следующий тест в ходе перебора сравниваем с текущим лучшим по следующим критериям сравнения, по порядку:
  • если Len(t1) ≤ M и Len(t2) > M, то t1 лучше;
  • если тесты t1 и t2 оба укладываются в ограничения по длине и при этом |Cov(t1)| > |Cov(t2)|, то t1 лучше;
  • если |Cov(t1)| ≥ |Cov(t2)| и Len(t1) < Len(t2), то t1 лучше;
  • если |Cov(t1)| > |Cov(t2)| и Len(t1) ≤ Len(t2), то t1 лучше;
  • если оба теста t1 и t2 не укладываются в ограничения по длине и (Len(t1 - Len(t2))*< (|Cov(t1)|-|Cov(t2)|), где P ? параметр алгоритма, то t1 лучше.
  • Если проверки 2.2.1?2.2.5 показывают, что проверяемый тест лучше предыдущего лучшего, то объявляем лучшим его, и далее сравниваем другие перебираемые тесты уже с ним.


    Если проверяемый тест признан хуже или ни одна проверка не выносит вердикт, что один тест лучше другого, то продолжаем считать лучшим тот же тест, что и раньше.
  • Добавляем найденный лучший тест t в конец списка R и удаляем из множества непокрытых тестовых ситуаций C все элементы множества Cov(t).
  • Переходим к шагу 1. Правило сравнения 2.2.1 гарантирует, что в первую очередь все возможные тестовые ситуации будут покрываться тестами с длиной, не превышающей максимальную. Правило 2.2.2 выбирает тесты с максимально возможным (в пределах ограничения длины) тестовым покрытием ? в большинстве случаев эта эвристика позволяет уменьшить суммарную длину тестов в наборе за счёт уменьшения их количества. Правила 2.2.3 и 3.2.4 отдают предпочтение тестам, улучшающим покрытие или длину и не ухудшающим притом другую из этих двух характеристик. Согласно правилу 2.2.5 из двух тестов, превышающих максимальную длину, один предпочтительнее другого, если увеличение длины компенсируется (с учётом весового параметра P) увеличением покрытия. Поскольку алгоритм эвристический, и в нём используется перебор по неупорядоченному множеству, в общем случае результаты его работы не детерминированы: при разных запусках на одних и тех же данных возможна генерация различных тестовых наборов; однако в большинстве случаев сгенерированные таким образом наборы имеют одинаковые метрики. Для настройки алгоритма используются параметры M и P. M: задаёт максимальную длину тестов генерируемого набора, превышение которой допускается только для покрытия недостижимых иными способами тестовых ситуаций. Допустимые значения: целое положительное число или +∞. P: задаёт вес превышения максимальной длины тестов по отношению к повышению покрытия. При больших значениях параметра алгоритм в первую очередь выбирает из тестов, превышающих максимальную длину и добавляющих хотя бы какое-то тестовое покрытие, самые короткие, что позволяет уменьшить максимальную длину тестов, но может приводить к существенному увеличению суммарной сложности тестового набора; при малых значениях алгоритм в первую очередь выбирает тесты, покрывающие большее количество тестовых ситуаций, что позволяет минимизировать суммарную сложность тестового набора за счёт добавления в него очень длинных тестов.Допустимые значения: неотрицательные числа (можно нецелые). Оптимальные значения обоих параметров зависят от размера и структуры графа состояний документа, от требований к тестовому покрытию и от возможностей тестировщиков исполнять длинные тесты. В ходе апробации алгоритма установлено, что в большинстве случаев оптимальное значение для параметра M немного превосходит длину самого длинного возможного в графе маршрута без циклов, ведущего из начального состояния в конечное, а для параметра P лежит в диапазоне [0; 2].

    Генерация готового к исполнению тестового набора

    Основным результатом работы инструмента является набор тестов, в котором каждый тест представляет собой пошаговую инструкцию для тестировщика. Каждая такая инструкция представляет собой последовательный список, состоящий из воздействий на документ и проверок, которые надо выполнить. Инструмент генерирует проверки следующих видов:
  • Проверка успешности выполнения воздействия.
  • Проверка состояния документа. Проверка считается успешной, если состояние документа соответствует ожидаемому.
  • Проверка списка воздействий, доступных для определённой роли. Проверка считается успешной, если список доступных для данной роли воздействий совпадает с указанным списком. Список воздействий может быть пустым.
  • Проверка списка воздействий, доступных для всех остальных ролей, включая не описанные в используемой для генерации тестов модели документа. Если в ходе исполнения теста какая-то из проверок не прошла успешно, то мы считаем, что тест обнаружил ошибку. Для генерации инструкции тестировщику в построенном, отфильтрованном и отсортированном тестовом наборе для каждого теста строится последовательность происходящих в нём элементарных событий: воздействий и состояний. Далее для каждого события определяются тестовые ситуации, добавляемые им к множеству тестовых ситуаций, покрытых в рамках тестового набора ранее, то есть, предыдущими тестами из набора, а также предыдущими элементарными событиями данного теста. Для каждого выполняемого в ходе теста воздействия генерируется указание тестировщику выполнить его (с указанием ролей, которыми можно его выполнять, или указанием, что роль может быть любой) и проверить успешность выполнения. Для достигнутых в ходе теста состояний проверки генерируются в зависимости от того, добавляет ли достижение данного состояния покрытие новых тестовых ситуаций: если попадание в состояние (с учётом предыстории элементарных событий данного теста) не даёт нового покрытия, то генерируется только указание проверить полученное состояние документа; если тестовое покрытие увеличилось (то есть, мы впервые попали в данное состояние или впервые попали в него с такими существенными элементами предыстории), то дополнительно генерируются проверки воздействий, доступных различным ролям. Для каждой роли «правильное» множество доступных воздействий состоит из:
  • Всех доступных согласно спецификации в текущем состоянии воздействий, не помеченных списком допустимых ролей.
    В частности, сюда относятся воздействия, которые выполняются не пользователями системы: например, тайм-аут можно выполнить, войдя в систему под любой ролью или даже вообще не входя в неё.
  • Всех доступных воздействий, помеченных списком допустимых ролей, таких что в их список входит данная роль. Проверки генерируются для каждой роли, встречающейся в пункте 2. Также генерируется проверка воздействий, доступных всем остальным ролям ? в соответствующий список входят все доступные в текущем состоянии воздействия, не помеченные списком допустимых ролей. Поскольку проверки возможных воздействий генерируются только при достижении новых в рамках тестового набора элементов покрытия, сортировка тестов по длине позволяет сбалансировать сложность их исполнения: первые тесты набора коротки, но на каждом шаге дают новые элементы покрытия, а следовательно, и проверки; последние тесты набора длинны, но новые элементы покрытия в них возникают лишь иногда. Разумеется, после того, как тестовый набор сгенерирован и сбалансирован, составляющие его тесты можно без изменений исполнять в произвольном порядке, не теряя при этом тестового покрытия.

    Генерация оптимизированных для

    , ,
    Труды Института системного программирования РАН

    Критерии тестового покрытия

    Элементарными событиями будем называть события следующих видов:
  • Попадание документа в заданное состояние.
  • Применение к документу в заданном состоянии заданного тестового воздействия. Поскольку нам необходимо протестировать все состояния и воздействия, возможные в ЖЦ документа, то естественным образом возникает первый критерий тестового покрытия:достижение всех элементарных событий. Каждое элементарное событие принадлежит некоторому тесту, моделирующему ЖЦ одного документа. События внутри теста линейно упорядочены. Событие B будем считается наступившим после события A, если оба эти события произошли внутри одного теста, и событие B произошло в нём позже события A. Событие B будем считать наступившим не после события A, если в рамках теста, которому оно принадлежит, ему не предшествовало событие A. В состоянии документа возможны скрытые составляющие, которые влияют на поведение системы. Они могут изменяться при попадании документа в некоторые критичные состояния или при применении к нему некоторых критичных воздействий. Например, если документ был удалён, а потом восстановлен, это может отразиться на его дальнейшем поведении. Элементарные события, которые предположительно могут влиять таким образом на поведение, будем называть существенными в истории событиями. Для тестирования таких возможных аномалий поведения введем второй критерий тестового покрытия: достижение каждого элементарного события отдельно после и не после каждого события, существенного в истории. В общем случае все существенные в истории события могут соответствовать различным взаимно независимым скрытым элементам состояния документа, и различные возможные комбинации наличия/отсутствия в истории документа таких событий соответствуют потенциально различным состояниям. Однако, чтобы избежать комбинаторного взрыва состояний и излишней сложности тестирования, существенные в истории события рассматриваются независимо друг от друга, и покрытие возможных их комбинаций не учитывается. Для особых случаев введем третий критерий тестового покрытия: разработчик тестов имеет возможность на основании дополнительных знаний о системе дополнительно отмечать интересующие его пары элементарных событий и требовать покрытия одного события в паре после и/или не после другого. Элементы всех трёх критериев покрытия могут быть представлены как достижение некоторого элементарного события B после или не после некоторого элементарного события А.
    События такого вида мы будем называть тестовыми ситуациями. При этом первый критерий покрытия порождает все возможные тестовые ситуации вида «B после A», где в качестве элементарного события A выступает начальное состояние графа, а в качестве элементарного события B ? все определённые в графе состояний документа элементарные события. Второй критерий покрытия порождает все возможные тестовые ситуации видов «B после A» и «B не после A», где в качестве A выступают существенные в истории элементарные события, а в качестве B ? определённые в графе состояний документа элементарные события. Элементы третьего критерия покрытия переводятся в тестовые ситуации очевидным образом. При разработке тестов документооборота для оценки полноты тестового покрытия используется составной критерий достижения всех тестовых ситуаций, построенных для модели ЖЦ документа по вышеописанным правилам из трех вышеописанных критериев. В дальнейшем под критерием покрытия имеется ввиду только этот критерий покрытия.

    Модель приложения

    Документ в системе документооборота может находиться в различных состояниях. Переход между состояниями осуществляется при помощи воздействий на документ через ГИП. В системе документооборота определены роли пользователей, при этом воздействия делятся на 3 класса в зависимости от того, кем они могут осуществляться:
  • определённым набором ролей;
  • любым пользователем системы, независимо от его роли:
  • не пользователями системы (например, тайм-аут). Предполагается, что при тестировании мы имеем возможность входить в систему документооборота в любой определённой в её спецификации роли, проверять возможность или невозможность осуществления воздействий данной ролью, а также осуществлять над документом любое описанное в спецификации воздействие. В противном случае соответствующие роли и воздействия исключаются из используемой для тестирования модели документа. Входными данными задачи тестирования документооборота являются список ролей пользователей системы и описание жизненного цикла (ЖЦ) документа. Описание ЖЦ представляется в виде графа состояний Конечного Автомата (КА), начальное состояние которого соответствует началу ЖЦ, переходы из начального состояния ? различным способам создания документа данного типа, конечное состояние (их может быть несколько) ? завершению ЖЦ, прочие состояния ? промежуточным состояниям документа, а переходы ? воздействиям на документ. Если воздействие относится к первому классу, то соответствующий переход КА помечен списком ролей, которым разрешено выполнять соответствующее воздействие. В используемой нами модели также предусмотрена возможность пометить некоторые воздействия (желательно, но не обязательно оставляющие документ в том же состоянии), которые мы считаем надёжными или по каким-то другим причинам не хотим тестировать. Например, редактирование текста документа, которое доступно (возможно, не всем ролям) практически в любом состоянии документа и не переводит притом его в другое состояние. Для таких воздействий мы проверяем возможность их осуществления, но не выполняем их в ходе теста. Используя такую модель, мы получаем возможность использовать для тестирования систем документооборота методы тестирования КА, адаптированные к особенностям предметной области. В выбранном нами методе тестирования каждый тест описывает ЖЦ некоторого документа от создания до уничтожения и, соответственно, моделируется некоторым маршрутом по графу состояний модельного КА, ведущим из начального его состояния в конечное.

    Оптимизация и балансировка тестового набора

    Далее производится фильтрация и сортировка построенного набора тестов R; при этом используется упорядоченность тестового набора по времени добавления в него тестов. Для фильтрации вычисляется полный набор тестовых ситуаций, покрываемых каждым тестом из построенного тестового набора, и для каждого теста проверяется, добавляет ли он какое-либо тестовое покрытие к суммарному покрытию тестов, добавленных в набор после него (напоминаем, что при добавлении «поздних» тестов в набор учитывалось только то покрытие, которое они добавляли к уже имеющемуся на момент добавления); если все тестовые ситуации, покрываемые тестом, покрываются объединением более поздних тестов, то он удаляется из построенного набора. Отфильтрованный таким образом набор тестов сортируется по длине: самые короткие (по количеству тестовых воздействий) тесты ставятся в начало набора, а самые длинные ? в его конец; это позволяет в дальнейшем сбалансировать количество проверок, выполняемых в различных тестах набора.

    Оптимизация тестового набора

    Нами выделены следующие критерии оптимизации, которые необходимо учитывать при создании тестов для ручного тестирования, в порядке понижения их приоритета:
  • Максимизация тестового покрытия.
  • Минимизация суммарной трудоёмкости тестового набора.
  • Ограничение трудоёмкости отдельных тестов: поскольку тесты исполняются не машиной, а человеком, то при превышении тестом некоторого порога сложности сильно возрастает вероятность ошибки тестировщика, в результате которой этот тест придётся исполнять заново. Эти критерии оптимизации противоречат друг другу: так, увеличение полноты тестового покрытия вызывает увеличение трудоёмкости отдельных тестов и тестового набора в целом, а ограничение длины отдельных тестов обычно приводит к увеличению количества таких тестов и, как следствие, суммарной сложности тестового набора, так как зачастую в различных тестах одного набора приходится заново выполнять одни и те же действия. Исходя из приоритетов поставленной задачи тестирования, была поставлена задача многокритериальной оптимизации набора тестов, для которой выбрана следующая стратегия решения:
  • Фиксируем некое разумное (с учётом размера и структуры графа состояний документа) ограничение на длину отдельных тестов. Длиной теста здесь и далее считается количество применяемых в нём тестовых воздействий.
  • Строим тестовый набор с минимальной суммарной длиной тестов, удовлетворяющий следующим ограничениям:
  • Каждый тест в составе тестового набора имеет длину не выше заданной.
  • Тестовый набор должен обеспечивать покрытие всех возможных при ограничении 2.1 тестовых ситуаций.
  • Если некоторые тестовые ситуации могут быть покрыты только тестами длины большей, чем заданное в п.1 ограничение, то строим тесты минимальной суммарной длины, покрывающие все такие тестовые ситуации, и добавляем их к построенному тестовому набору.
  • В этом случае проверяем, не покрывают ли добавленные на шаге 3 длинные тесты сразу все тестовые ситуации, покрываемые какими-то из ранее построенных коротких тестов, и при обнаружении таких «лишних» коротких тестов удаляем их из тестового набора.

    Пример применения

    Инструмент реализован на языке Java версии 1.5 и предоставляет программный интерфейс, позволяющий сконструировать граф состояний документа, разметить в нём переходы списками ролей, пометить некоторые переходы как не тестируемые, задать критерии покрытия и параметры работы главного алгоритма, после чего запустить генерацию тестов. Ввод данных может осуществляться специально написанной для данного графа программой на языке Java (которую можно считать текстовой формой представления графа) или специально разработанными конвертерами, которые будут обходить граф в некотором другом представлении и вызывать соответствующие методы данного интерфейса. Граф представленный на Рис. 1 был создан на основе анализа требований к реально существующему приложению системы документооборота, после чего дополнительно усложнён для получения нетривиальных путей (например, добавлено состояние «Отложен», недостижимое на путях без циклов). Почти в каждом состоянии документа возможны также его редактирование и комментирование, причём оба этих воздействия не меняют состояние документа с точки зрения данной автоматной модели. Для сокращения трудоёмкости тестирования в большинстве состояний их решено не тестировать, а учесть в списке не тестируемых воздействий, для которых при тестировании только проверяется возможность их выполнения; в результате на рисунке фигурируют только те петлевые дуги этих двух видов, которые решено тестировать дополнительно. Чтобы не загромождать излишне приведённый рисунок, на нём также не отображены списки ролей, которыми помечены переходы графа. В результате применения генератора тестов к приведённому на Рис. 1 графу с параметром M=12 (параметр P в данном случае несущественен, так как все сгенерированные тесты укладываются в ограничение по длине) и без дополнительных требований к покрытию он выдаёт тестовый набор следующего вида (приводится с сокращениями): Test #0: [+] <Начать тест> [+] Действие: Создать 2; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Черновик [ ] Проверка: доступные действия: [ ] Действия, доступные для роли Автор: [ ] Отправить в секретариат [ ] Редактировать [ ] Удалить [ ] <Других нет> [ ] Действия, доступные для всех остальных: <пусто> [+] Действие: Удалить; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Удаленный 1 [ ] Проверка: доступные действия: [ ] Действия, доступные для роли Автор: [ ] Восстановить [ ] <Завершить тест> [ ] <Других нет> [ ] Действия, доступные для всех остальных: [ ] <Завершить тест> [ ] <Других нет> [+] Действие: <Завершить тест>; роль: <любая> [ ] Проверка: действие завершено успешно [ ] Проверка: жизненный цикл документа завершен ------------------------ Test #1: [+] <Начать тест> [+] Действие: Создать 2; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Черновик [+] Действие: Отправить в секретариат; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Согласование [ ] Проверка: доступные действия: [ ] Действия, доступные для роли Автор: [ ] Редактировать [ ] Комментировать [ ] <Других нет> [ ] Действия, доступные для роли Визирующий: [ ] Комментировать [ ] Согласовать [ ] <Других нет> [ ] Действия, доступные для роли Подписывающий: ... [ ] Действия, доступные для роли Секретарь: [ ] Редактировать [ ] Отправить на доработку [ ] Комментировать ... [ ] Действия, доступные для всех остальных: <пусто> [+] Действие: Согласовать; роль: [Визирующий, Подписывающий, Секретарь] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Визирование [ ] Проверка: доступные действия: ... [+] Действие: Отправить на доработку; роль: [Визирующий, Секретарь] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Доработка [ ] Проверка: доступные действия: ... [+] Действие: Удалить; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Удаленный 2 [ ] Проверка: доступные действия: ... [+] Действие: Восстановить; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Доработка [+] Действие: Удалить; роль: [Автор] [ ] Проверка: действие завершено успешно [ ] Проверка: состояние документа = Удаленный 2 [+] Действие: <Завершить тест>; роль: <любая> [ ] Проверка: действие завершено успешно [ ] Проверка: жизненный цикл документа завершен ------------------------ Test #2: ... Также выдаются метрики построенного тестового набора и список непокрытых тестовых ситуаций: Total: 5 pathes, sum length = 45 transitions Maximal path length = 12 Uncovered elements: Ручной анализ приведённого графа ЖЦ документа и сгенерированного для него тестового набора показывает, что тестовый набор меньшей длины, покрывающий все переходы, невозможен. Тестовый набор может быть распечатан в таком виде и передан тестировщику. Значок «[ ]» служит местом, куда тестировщик может ставить отметки об успешном выполнении проверок.

    Тестовые проверки

    В первую очередь тестируется живучесть системы документооборота и её способность выполнять заявленные воздействия. Проверяется, что система не завершает аварийно работу при выполнении осуществляемых над документом воздействий. Поскольку спецификация ЖЦ документа представлена в виде графа состояний, при тестировании мы проверяем изоморфизм наблюдаемого графа состояний документа модельному графу. Для этого в каждом состоянии мы проверяем список доступных в нём воздействий (соответствующих переходам из состояния), а после выполнения каждого тестового воздействия проверяем, соответствует ли полученное состояние документа ожидаемому. Расхождение между ожидаемым и полученным состоянием считается ошибкой и требует исправления тестируемой системы или пересмотра спецификации. Согласно используемой модели некоторые воздействия могут быть выполнены только определёнными ролями; для тестирования этого требования мы проверяем соответствие между ролями и доступными им воздействиями. Обнаруженная в ходе тестирования невозможность осуществить некоторой ролью воздействие, которое описано в спецификации как возможное для неё, равно как и наоборот, считается ошибкой. Другие проверки в данную модель не входят, но могут быть добавлены при ручной доработке сгенерированного по ней тестового набора.

    и прочно занял доминирующее положение

    Графический интерфейс пользователя (ГИП) давно и прочно занял доминирующее положение среди способов взаимодействия с прикладными программами (приложениями) общего назначения. Взаимодействие пользователей с офисными и корпоративными приложениями в подавляющем большинстве случаев заключается в совершении некоторых последовательностей действий с графическим интерфейсом. Поэтому для обеспечения качества современных программных продуктов важной задачей является проверка корректности внешнего поведения приложений при взаимодействии пользователя с ГИП. Такая проверка предполагает выполнение различных сценариев, состоящих из последовательностей воздействий на приложение через ГИП, с проверкой промежуточных и конечного результатов. Выполнение таких последовательностей вручную представляет из себя рутинную ресурсоемкую деятельность, особенно при необходимости неоднократного воспроизведения одних и тех же сценариев, например, при регрессионном тестировании. Поэтому существующие инструменты автоматизации тестирования приложений с ГИП прежде всего нацелены на автоматизацию выполнения тестов: воспроизведение заранее заданных (как правило, вручную) сценариев взаимодействия с приложением через ГИП. Для автоматизации выполнения тестов необходимо иметь возможность через какой-то интерфейс подавать стимулы, то есть воздействовать на тестируемую программу, и принимать реакции, то есть наблюдать за тем, что делает программа в ответ на эти воздействия. Для программ с ГИП подача стимулов на логическом уровне означает эмуляцию действий пользователя, таких как нажатие кнопок, ввод данных в поля ввода, выбор элементов из списков и так далее. На физическом уровне ввод данных требует от инструмента возможности эмулировать нажатие кнопок на клавиатуре, а нажатие кнопок и выбор элементов из списка могут быть выполнены как с клавиатуры, так и эмуляцией действий с мышью ? перемещение и нажатие кнопок манипулятора. Для наблюдения реакций необходимо распознавать элементы ГИП, то есть определять наличие тех или иных элементов и получать значения их атрибутов.
    Эмуляция действий с мышью также требует умения распознавать элементы ГИП, поскольку координаты объекта на экране являются частным случаем этих атрибутов. Во многих случаях для распознавания элементов ГИП и получения их атрибутов есть возможность использовать специальные программные интерфейсы такие, как, например, интерфейсы графических библиотек Swing/AWT или SWT/RCP/Eclipse на платформе Java , Windows Presentation Framework для приложений на платформе .NET , COM-интерфейсы для программ в Microsoft Word или Microsoft Excel . Однако имеется ряд программ, для которых распознавание элементов ГИП невозможно в силу технологических причин, так как используемая для создания ГИП библиотека не реализует ни один из механизмов доступа к атрибутам объектов. Такие программы позволяют пользователям разработать собственные формы, но используемые при этом элементы ГИП не позволяют получить доступ к их атрибутам. В результате вся созданная пользователем форма распознаётся как один большой элемент (например, типа RichTextBox для IBM Lotus Notes), а находящиеся внутри него элементы распознать не удаётся. Иногда в таких случаях возможно решить проблему автоматизации подачи стимулов с помощью с помощью внедрения внутрь тестируемой программы агентов, которые обеспечивают доступ к требуемой информации об элементах ГИП (например, перекомпиляция Delphi-приложений как Open Application для тестирования с помощью инструмента AutomatedQA TestComplete ). В остальных случаях единственным способом автоматизации подачи стимулов через ГИП является запись и последующее воспроизведение взаимодействий пользователя с интерфейсом в терминах экранных координат и нажатия кнопок мыши и клавиатуры. Основные недостатки такого способа в том, что он позволяет создавать набор регрессионных тестов только для практически законченного и уже функционирующего приложения, и любые изменения не только в функциональности, но и в дизайне ГИП, как правило, требуют переработки тестов. Иногда при такой записи можно использовать определение координат элементов ГИП методом поиска и сравнения фрагментов снимков экрана или поиска распознанного со снимков экрана текста , что несколько снижает зависимость тестов от внешнего расположения элементов, но не позволяет сделать тесты независимыми даже от незначительных изменений дизайна графического интерфейса, при которых инструмент не сможет сопоставить функционально одинаковые элементы в новой и старой версиях интерфейса.


    Также снизить зависимость тестов от обновления внешнего вида ГИП иногда позволяет полный отказ от использования мыши и управление тестируемой программой только с помощью клавиатуры, но не во всех приложениях есть возможность доступа ко всем элементам ГИП при помощи клавиатуры. В таких условиях автоматизация тестирования становится экономически невыгодна. Если в обычных условиях, когда распознавание элементов ГИП не вызывает затруднений, расходы на автоматизацию превышают расходы на один цикл ручного тестирования в 3 раза , то усложнение создания и особенно сопровождения тестов, связанное с трудностями распознавания элементов ГИП, по нашим оценкам может увеличить эту цифру ещё в 2-3 раза. Кроме того, в случае приложений с ГИП к значимому для пользователя поведению кроме собственно функциональности приложения относится так же внешний вид интерфейса с точки зрения практичности, соответствия некоторому заданному стандарту, эстетичности и т. д. Автоматизированные тесты, как правило, не предназначены для оценки такого рода характеристик качества. В них обычно программируется только небольшая часть наиболее важных с точки зрения функциональности проверок. Большая часть дефектов, связанных с визуальным представлением ГИП и практичностью, не обнаруживается автоматизированными тестами, например, «выползание» текста за границы, «наползание» элементов ГИП друг на друга, неудобное расположение элементов интерфейса, приводящее к частым ошибкам пользователей и т. п. Поэтому, независимо от наличия или отсутствия автоматизированных тестов, необходимо также иметь тестовый набор для ручного выполнения. При этом он должен обеспечивать заданный уровень полноты тестирования и быть достаточно прост в сопровождении. Отдельный тест из такого ручного набора представляет собой сценарий, состоящий из последовательности действий и проверок, которые должен выполнить тестировщик. В целом тестовый набор должен обеспечивать некоторую полноту тестирования. Поскольку человек более подвержен ошибкам, чем компьютер, сценарии не должны быть слишком длинными, так как в результате ошибки тест придётся выполнять сначала.


    В свою очередь, «измельчение» тестов приводит к тому, что некоторые действия приходится выполнять неоднократно, в каждом тесте заново, что также увеличивает общую трудоёмкость. В то же время, нужно стремиться сократить общую трудоёмкость всех тестов, то есть не количество тестов, а суммарную длину последовательностей действий, из которых состоят отдельные тесты, сохраняя при этом приемлемую полноту тестирования. Поэтому при поиске решения необходимо найти способ сохранить баланс между этими противоречивыми требованиями. В статье рассматривается подход, при котором тесты для ручного выполнения генерируются автоматически из некоторой модели, описывающей поведение приложения при взаимодействии с ним пользователя. Такой подход гарантирует полноту покрытия по построению (способ генерации зависит от выбранного критерия полноты) и дает возможность быстро перегенерировать тесты в случае изменения модели (в связи с изменением функциональности приложения или дизайна ГИП). В целях проверки предложенного подхода создана прототипная реализация инструмента генерации тестовых последовательностей для проверки интерфейса приложений, реализованных в системе документооборота на основе Lotus Notes. Платформа Lotus Notes включает в себя интегрированную среду разработки, предоставляющую разработчикам средства для разработки приложений с ГИП, поддерживающих управление документами и данными, а так же рабочими потоками внутри организации. В среде Lotus Notes отсутствуют возможности программного доступа к элементам ГИП. Типичным сценарием взаимодействия с приложением Lotus Notes является последовательность воздействий на некоторый документ, который в результате этих воздействий может переходить в новые состояния. В качестве формализованной модели поведения таких приложений удобно использовать диаграммы состояний и переходов, так как, с одной стороны, при помощи таких диаграмм достаточно естественно описываются состояния документа и переходы между ними при воздействиях пользователя, а с другой стороны, эти диаграммы достаточно формальны для автоматической обработки с целью генерации на их основе тестовых сценариев с заданными свойствами.

    в данной работе подход не

    Предложенный в данной работе подход не уменьшает и не увеличивает количество работы на этапе создания тестов по сравнению с ручным проектированием тестов, поскольку ручное проектирование в любом случае предполагает создание некоторой модели, может быть чуть менее формализованной, для обеспечения полноты тестирования. В то же время этот подход имеет ряд дополнительных достоинств:
  • гарантированная по построению полнота тестового набора относительно выбранного критерия;
  • минимизация размера тестового набора;
  • простота сопровождения (перегенерации) тестового набора при внесении изменений в функциональность тестируемого приложения или дизайн его ГИП. Сгенерированные сценарии могут использоваться как для ручного выполнения тестов (функционального тестирования, пользовательского приемочного тестирования, тестирования практичности), так и для создания набора регрессионных автоматизированных тестов. Но здесь есть серьёзная проблема. Когда в модель вносятся изменения, вызванные изменениями в реализации, новые сгенерированные тестовые сценарии могут существенно отличаться от тех, которые были сгенерированы по предыдущей модели. Это означает, что автоматизированные тесты придётся создавать заново. Для преодоления этого ограничения планируется разработать механизм генерации непосредственно исполнимого кода тестовых сценариев. Подобный подход генерации исполнимых тестовых сценариев для ГИП по диаграмме состояний и переходов используется, например, в . Но все известные нам решения предполагают интеграцию с уже существующими инструментами автоматизации, то есть неявно накладывают требование на то, чтобы элементы ГИП были распознаваемы и была возможность получения их атрибутов. К сожалению, как мы уже отмечали в начале статьи, требование доступности элементов ГИП выполняется не всегда. Чтобы работать с такими интерфейсами, необходимо помимо логической модели поведения в виде диаграммы состояний и переходов построить также физическую модель ГИП. Используя сочетание этих двух моделей можно достичь следующего уровня автоматизации создания и выполнения тестов. Это является предметом дальнейших исследований и развития описанного в данной статье подхода к разработке тестов.

    Тестирование софта - статьи

    Архитектура тестовой системы AVM

    Тестовые системы, создаваемые с использованием технологии AVM, должны быть разбиты на несколько слоев (см. Рис. 1). Самый нижний уровень — это RTL-модель тестируемого устройства (DUT, Design Under Test). Над ним находится прослойка транзакторов (transactors) — компонентов тестовой системы, осуществляющих преобразование потока входных транзакций во входные сигналы тестируемой модели и наоборот преобразование выходных сигналов тестируемой модели в поток выходных транзакций. Примерами транзакторов являются:
  • драйвер (driver) — конвертирует поток транзакций во входные сигналы тестируемой модели;
  • ответчик (responder) — отвечает на запросы со стороны тестируемой модели относительно подачи дополнительных данных;
  • монитор (monitor) — осуществляет наблюдение за выходами тестируемой модели, не оказывая на нее влияния. Следующим слоем является так называемый слой операций (operational layer). Компоненты этого уровня образуют тестовое окружение необходимое тестируемой модели. К ним относятся:
  • генератор стимулов (stimulus generator) — создает поток входных транзакций (стимулов) на тестируемую модель. Для генерации стимулов используется подход на основе разрешения ограничений с использованием рандомизации (CRV, Constraint-Random Verification). Связь генератора с тестируемой моделью осуществляется через драйвер.
  • главный модуль (master) — элемент окружения тестируемой модели, инициирующий взаимодействие с ней. Главный модуль посылает последовательности транзакций (случайные или направленные на достижение какой-либо ситуации) и может использовать обратную связь для определения своих следующих действий. Связь главного модуля с тестируемой моделью осуществляется через драйвер;
  • подчиненный модуль (slave) — как и главный модуль моделирует окружение тестируемой модели, но, в отличие от него, является пассивным компонентом, отвечающим на запросы тестируемой модели. Связь подчиненного модуля с тестируемой моделью осуществляется через ответчик. Далее следует слой анализа (analysis layer), компоненты которого проверяют корректность поведения тестируемой модели, оценивают полноту тестирования и выполняют некоторые другие функции.
    Основными компонентами этого уровня являются:
  • компонент проверки (scoreboard) — проверяет корректность поведения тестируемой модели в ответ на входные транзакции.
  • сборщик покрытия (coverage collector) — оценивает полноту тестирования путем подсчета числа событий заданных типов, возникающих в процессе тестирования. На самом верху находится слой управления, включающий в себя один единственный компонент — контроллер теста (test controller), который осуществляет координацию работы других компонентов тестовой системы. Рис. 1 иллюстрирует многослойную структуру тестовой системы. Каждый прямоугольник соответствует определенному слою. В левой части указано его название, в правой — основные компоненты тестовой системы, образующие данный слой. Стрелки показывают взаимодействие между слоями. Архитектура тестовой системы AVM
    Рисунок 1. Слои тестовой системы AVM

    Архитектура тестовой системы OVM

    В рамках технологии OVM тестовая система собирается из специальных блоков, называемых OVC (Open Verification Component). Каждый блок инкапсулирует тестовое окружение для отдельного модуля аппаратуры и состоит из полного набора компонентов, обеспечивающих генерацию стимулов, проверку правильности поведения и оценку полноты тестирования. Если тестируемая модель аппаратного обеспечения получается путем интеграции нескольких модулей, тестовая система для нее строится путем объединения соответствующих OVC-блоков и построения над ними специального синхронизирующего контроллера (virtual sequencer). Структура OVC-блоков практически полностью соответствует архитектуре тестовой системы AVM. Пример блока тестовой системы приведен на Рис. 2. Архитектура тестовой системы OVM
    Рисунок 2. Блок тестовой системы

    Архитектура тестовой системы UniTESK

    Тестовая система UniTESK состоит из следующих основных компонентов (см. Рис. 3):
  • Обходчик является библиотечным компонентом тестовой системы. В его основе лежит алгоритм обхода графа состояний конечного автомата, моделирующего целевую систему на некотором уровне абстракции.
  • Итератор тестовых воздействий работает под управлением обходчика и предназначен для перебора в каждом достижимом состоянии конечного автомата допустимых тестовых воздействий. Он автоматически генерируется из тестового сценария, который представляет собой описание конечно-автоматной модели тестируемой системы.
  • Тестовый оракул оценивает правильность поведения тестируемой системы в ответ на единичное тестовое воздействие. Он автоматически генерируется на основе формальных спецификаций, описывающих требования к системе в виде пред- и постусловий интерфейсных операций и инвариантов типов данных.
  • Медиатор связывает формальные спецификации с конкретной реализацией тестируемой системы. Медиатор преобразует единичное тестовое воздействие из спецификационного представления в реализационное, а полученную в ответ реакцию — из реализационного представления в спецификационное. Также медиатор синхронизирует состояние спецификации с состоянием тестируемой системы.
  • Трасса теста отражает события, происходящие в процессе тестирования. На основе трассы можно автоматически генерировать различные отчеты, помогающие в анализе результатов тестирования. Архитектура тестовой системы UniTESK
    Рисунок 3. Архитектура тестовой системы UniTESK Помимо основных компонентов в тестовой системе выделяются части, отвечающие за интеграцию с конкретным HDL-языком. Например, для языка Verilog такими компонентами являются VPI-модуль (включая VPI-медиатор) и Verilog-окружение.

    Обзор технологии AVM

    Технология AVM (от англ. advanced verification methodology — передовая методология верификации) была создана в компании Mentor Graphics и распространяется под лицензией Apache 2.0. На настоящий момент она никак не стандартизирована и вся информация по ней находится в «книге рецептов»  и пользовательской инструкции по эксплуатации.

    Обзор технологии OVM

    Технология OVM (от англ. open verification methodology — открытая методология верификации) является совместной разработкой компаний Mentor Graphics и Cadence Design Systems. OVM представляет собой первый открытый промышленный метод разработки тестов на основе языка SystemVerilog . Предшественниками OVM являются подходы AVM (от Mentor Graphics) и URM (от Cadence Design Systems). Технология OVM пока не стандартизирована, но является главным претендентом на звание стандарта.

    Обзор технологии UniTESK

    Технология UniTESK (от англ. unified specification and testing tool kit — унифицированный инструментарий для спецификации и тестирования) разработана в Институте системного программирования РАН. В отличие от рассмотренных ранее технологий AVM и OVM, созданных в коммерческих организациях, UniTESK является академической разработкой, которая, тем не менее, основана на реальном опыте тестирования программного и аппаратного обеспечения.

    Основные понятия

    Под моделями аппаратного обеспечения в данной статье понимаются программные модели цифровых устройств, описанные на специальных языках. Такие языки можно разбить на две разновидности:
  • языки описания аппаратуры (HDLs, Hardware Description Languages), с помощью которых разрабатывается модель уровня регистровых передач (RTL, Register Transfer Level). Среди HDL-языков можно выделить Verilog  и VHDL ;
  • языки проектирования системного уровня (system-level design languages), позволяющие описывать аппаратную систему на более высоком уровне абстракции и поддерживающие разработку программной части системы. К таким языкам относятся SystemC  и SystemVerilog . В дальнейшем будем называть языками описания аппаратуры или HDL-языками обе разновидности языков. Рассматриваемые в статье технологии нацелены на автоматизацию разработки тестов для моделей аппаратного обеспечения и на создание тестовых систем с высоким уровнем повторного использования и устойчивых к изменениям реализации. Перед описанием технологий AVM, OVM и UniTESK (технология URM не описывается в виду почти полного отсутствия документации по ней) отметим следующее. В названии подходов AVM и OVM содержится слово «методология», в то время как UniTESK, по мнению ее создателей, является технологией. В рамках данной статьи мы не различаем эти два термина и понимаем под ними совокупность принципов, методов и инструментов для разработки набора тестов.

    Основные принципы AVM

    С помощью технологии AVM реализуется возможность разработки сложных тестовых систем на SystemVerilog или SystemC. Основным средством AVM являются библиотеки классов, разработанные на этих языках. При разработке тестовых систем используются следующие базовые принципы: объектно-ориентированный подход, моделирование на уровне транзакций (TLM, Transaction-Level Modeling) и генерация случайных стимулов на основе ограничений (constraint-random generation). TLM — это подход к моделированию пересылки данных в цифровых системах, при котором организация связи между функциональными модулями отделена от их реализации. Под транзакцией понимается единичная пересылка управляющей информации или данных между двумя модулями. Например, в проектах, использующих шины передачи данных, чтение и запись в шину могут быть описаны транзакциями; в системах с коммутацией пакетов транзакцией является посылка пакета. Использование объектно-ориентированного подхода и моделирования на уровне транзакций позволяет технологии AVM добиться высоких показателей повторного использования тестов. Использование механизмов наследования и перегрузки методов позволяет переопределять поведение компонента тестовой системы. За счет этого сокращается время, затрачиваемое на разработку и отладку тестов.

    Основные принципы OVM

    Базовые принципы технологии OVM во многом схожи с AVM. Основным средством OVM является библиотека классов на языке SystemVerilog (в дальнейшем планируется поддержка SystemC), позволяющая создавать модульные тестовые системы, допускающие многократное использование. Одним из достоинств технологии является нацеленность на тестовое покрытие (CDV, Coverage-Driven Verification), что позволяет четко задавать цели тестирования и лучше управлять процессом разработки тестов.

    Основные принципы UniTESK

    При разработке тестовых систем по технологии UniTESK используются следующие базовые принципы:
  • определенность набора компонентов с ясным разделением функций и четкими интерфейсами;
  • использование формальных спецификаций в форме пред- и постусловий интерфейсных операций и инвариантов типов данных для автоматической генерации тестовых оракулов (компонентов проверки);
  • применение конечно-автоматных моделей для построения последовательностей тестовых воздействий (стимулов). В качестве основного средства тестирования моделей аппаратного обеспечения используется инструмент CTESK, использующий для разработки тестовой системы язык SeC (Specification extension of C) — расширение языка программирования C.

    Сравнение технологий разработки тестов

    Сравним рассмотренные ранее технологии AVM, OVM и UniTESK. Подходы AVM и OVM разработаны коммерческими организациями с учетом большого опыта тестирования моделей аппаратного обеспечения. Основной акцент в этих технологиях сделан на повторном использовании тестов, которое достигается средствами объектно-ориентированного программирования и моделирования на уровне транзакций. Дополнительно к этому в подходе OVM заложен механизм построения тестовых систем из готовых блоков, отвечающих за тестирование отдельных модулей аппаратной системы. Эта концепция хорошо отражает тенденции современной индустрии, когда основная работа сосредотачивается не на проектировании принципиально нового аппаратного обеспечения, а на интеграции аппаратуры из готовых модулей. Сравнение технологий разработки тестов
    Рисунок 4. Сопоставление архитектур тестовых систем В технологии UniTESK, являющейся академической разработкой, основной упор сделан на автоматизации создания тестов. В UniTESK используются формальные спецификации поведения в форме пред- и постусловий операций и инвариантов типов данных, а также конечно-автоматные модели для построения последовательностей стимулов. Все это позволяет разрабатывать высококачественные тесты, тщательно проверяющие функциональность тестируемой модели. Из плюсов UniTESK можно также отметить возможность разработки спецификаций с потактовой точностью на основе пред- и постусловий стадий операций. Однако, что касается средств повторного использования тестов, в технологии UniTESK они менее развиты по сравнению с AVM и OVM. Во многом это связано с тем, что инструмент CTESK, используемый для тестирования моделей аппаратного обеспечения, основан на языке программирования C. Реализация основных принципов UniTESK для языков SystemVerilog и SystemC позволила бы улучшить показатели повторного использования тестов. Сопоставим архитектуру тестовой системы AVM (она такая же, как у OVC-блока) и тестовой системы UniTESK (см. Рис. 4). Слой транзакторов в тестовой системе AVM приблизительно соответствует слою медиаторов в тестовой системе UniTESK. Соответствие в вышележащих слоях установить сложнее. Генератору стимулов AVM соответствует итератор тестовых воздействий UniTESK (в паре с обходчиком), компонентам проверки — тестовый оракул. Наиболее близким компонентом UniTESK, соответствующим контроллеру тестов AVM, является обходчик. Заметим, что для некоторых компонентов AVM нет явных соответствий в технологии UniTESK, например, там нет таких сущностей, как главные и подчиненные модули.

    Сравнительный анализ современных технологий разработки тестов для моделей аппаратного обеспечения

    , ,
    Труды Института системного программирования РАН

    Увеличение сложности аппаратного обеспечения неизбежно

    Увеличение сложности аппаратного обеспечения неизбежно влечет за собой развитие технологий разработки тестов. В настоящее время сложность аппаратуры достигла такого уровня, что ее тестирование немыслимо без применения методов автоматизации. Такие методы позволяют разрабатывать тестовые системы, которые автоматически генерируют тестовые последовательности, оценивают правильность поведения системы и собирают информацию о достигнутом уровне тестового покрытия. Однако следует понимать, что автоматизация это далеко не все, что требуется от современной технологии разработки тестов. В условиях постоянного изменения требований и непрерывной доработки проектов огромное значение приобретают такие характеристики технологии, как возможность повторного использования тестов и возможность создания тестов, устойчивых к изменениям реализации. Существующие подходы к построению тестовых систем, такие как AVM (Advanced Verification Methodology), URM (Universal Reuse Methodology), OVM (Open Verification Methodology) и UniTESK (Unified TEsting and Specification tool Kit), в той или иной степени решают обозначенные задачи, но многообразие существующих подходов усложняет взаимодействие между разными группами разработчиков. Стандартизация технологий разработки тестов является необходимым шагом в развитии полупроводниковой индустрии. Появление стандартов создаст предпосылки для отчуждения тестов, что окажет стимулирующее воздействие на рынок готовых компонентов (IPs, Intellectual Property Cores). Если тесты к компоненту аппаратного обеспечения разработаны с использованием общепринятого подхода, сторонние инженеры могут без труда разобраться в них и при необходимости дополнить, а это повышает доверие к компоненту, поставляемому третьей стороной. В последнее время в области тестирования моделей аппаратуры все большее распространение получает технология OVM, совместно разработанная компаниями Mentor Graphics и Cadence Design Systems. Предшественниками OVM являются два основных подхода: AVM (от Mentor Graphics) и URM (от Cadence Design Systems). В данной работе технология OVM вместе со своими предшественниками сравнивается с технологией UniTESK, разработанной в Институте системного программирования РАН. В статье анализируются сильные и слабые стороны указанных подходов, сопоставляются архитектуры тестовых систем, даются рекомендации по развитию инструментов UniTESK и их унификации с подходом OVM. Статья состоит из семи разделов, включая введение и заключение. Во втором разделе вводятся основные понятия, используемые в различных технологиях. Третий, четвертый и пятый разделы посвящены обзору технологий AVM, OVM и UniTESK соответственно. В этих разделах описываются основные принципы и архитектуры тестовых систем для каждого подхода. Шестой раздел содержит сравнительный анализ рассматриваемых технологий. В седьмом разделе делается заключение, в котором даются рекомендации по развитию технологии UniTESK.

    в области разработки тестов для

    Мы считаем, что в области разработки тестов для моделей аппаратного обеспечения в скором времени должны появиться стандарты. Главным претендентом на звание стандарта является технология OVM, которая продвигается такими крупными компаниями, как Mentors Graphics и Cadence Design Systems. Эта технология основана на хорошо зарекомендовавших себя подходах AVM и URM, поэтому ее начинают активно использовать в индустрии. Мы выступаем за унификацию технологии UniTESK (и других методов автоматизации разработки тестов для моделей аппаратного обеспечения) c подходом OVM. Лучше использовать один подход, который будет понятен разным группам разработчиков. Под унификацией здесь понимается следующее. Основные принципы, на которых основана UniTESK, прежде всего, использование формальных спецификаций в форме пред- и постусловий для описания поведения системы и применение конечно-автоматных моделей для генерации тестовых последовательностей, хорошо зарекомендовали себя на практике. Ничто не мешает реализовать эти принципы в терминах технологии OVM, на основе библиотеки базовых классов. Это позволит создавать тесты по технологии UniTESK, но которые при этом приобретают новое качество — их можно отчуждать, повторно использовать при построении более крупных тестовых систем и т.д.

    Тестирование софта - статьи

    Алгоритмы уточняющего обхода графов

    В технологии тестирования UniTESK тестовая последовательность строится в результате обхода графа состояний обобщенной конечно-автоматной модели целевой системы. Для тестирования в условиях неполной информации мы предлагаем использовать определяемые ниже алгоритмы уточняющего обхода. Определение. Алгоритмом движения по графу называется алгоритм, который в процессе своей работы строит маршрут в графе. Определение. Алгоритмом уточняющего движения по графу с уточняемыми вершинами называется алгоритм, который в процессе своей работы строит уточняющий маршрут в графе. Как и в работе [], будем считать, что алгоритму предоставляются две специальные внешние операции status(), возвращающая идентификатор текущей вершины, и call(x), которая осуществляет переход из текущей вершины по дуге, помеченной стимулом x. Для детерминированного графа такая дуга, если существует, то единственная. Предусловием операции call(x) является допустимость стимула x в текущей вершине. Маршрут строится алгоритмом как последовательность дуг, проходимых последовательными вызовами операции call(). Определение. Для маршрута в графе вершину будем называть пройденной, если этот маршрут содержит хотя бы одну инцидентную ей дугу. Определение. Для маршрута в графе вершину будем называть завершенной, если этот маршрут содержит все выходящие из вершины дуги. Определение. Неизбыточным алгоритмом называется алгоритм движения по графу, который зависит только от пройденной части графа и допустимости стимулов в текущей вершине. Как и в работе [], будем считать, что допустимость стимулов алгоритм может определить с помощью специальной внешней операции next(), которая возвращает стимул, неспецифицированным образом выбираемый среди не выбранных ранее стимулов, допустимых в текущей вершине (осуществляя тем самым итерацию стимулов в вершине). Если все стимулы, допустимые в текущей вершине, уже выбирались, будем считать, что next() возвращает специальный стимул . Определение. Алгоритмом уточняющего обхода графа называется алгоритм, который в процессе своей работы строит уточняющий обход графа. Нас будут интересовать только такие алгоритмы, которые останавливаются через конечное число шагов. Рассмотрим общую схему неизбыточных алгоритмов обхода графов. // пока есть незавершенные вершины while (!сompleted()) { // если текущая вершина незавершена if (next(status()) != ) { // подать очередной стимул call(next(status())); } else { // попасть в пройденную незавершенную вершину rollback(); } } Операция completed() возврашает true тогда и только тогда, когда в графе все вершины завершены, то есть не существует вершин графа, из которых ведут не пройденные дуги. Операция rollback() строит маршрут из текущей вершины в вершину, в которой есть еще не пройденные дуги.
    Подходы к реализации этой операции могут быть различными. Алгоритмы, основанные на обходе остова графа, выбирают самую дальнюю от корня (поиск в глубину) или самую ближнюю к корню (поиск в ширину) незавершенную вершину, достижимую из текущей []. Другим подходом (жадный алгоритм) является выбор ближайшей достижимой незавершенной вершины, но это требует больших затрат памяти. Утверждение. Для графов с уточняемыми вершинами, являющихся:
  • конечными,
  • монотонными,
  • детерминированными,
  • открытыми,
  • сильно-связными существует неизбыточный алгоритм уточняющего обхода. Доказательство. Рассмотрим следующий алгоритм, точнее его идею, в рамках определенной ранее схемы. Поскольку граф является монотонным, любой маршрут в нем является уточняющим, следовательно, для него определены уровни неопределенности. Алгоритм хранит в памяти некоторую структуру данных, связанных с текущим уровнем неопределенности, например, подобную описанной в работе []. Операция complete() возвращает true тогда и только тогда, когда все пройденные вершины на текущем уровне неопределенности завершены, то есть операция next() для них возвратила ε . Операция rollback() строит маршрут из текущей вершины в незавершенную вершину текущего уровня неопределенности, в которой есть еще не пройденные дуги. Например, это можно сделать на основе остова []. Поскольку различные вершины одного уровня неопределенности несравнимы в отношении уточнения, пройденный на текущем уровне неопределенности граф является подграфом некоторой информационной проекции исходного графа, а следовательно, является детерминированным и сильно-связным что и требуется в []. В случае, если в операции call(x) происходит выбор дуги в вершину, уточняющую одну из вершин текущего уровня неопределенности, текущий уровень неопределенности изменяется, структуры данных освобождаются, алгоритм работает так, как если бы новое состояние было начальным. Поскольку граф конечен, детерминирован и сильно-связен, любая его информационная проекция является конечной, детерминированной и сильно-связной, поэтому внутри любого уровня неопределенности, за исключением последнего, можно гарантированно достичь вершину, в которой допустим стимул, такой что операция call обязательно выберет дугу, выводящую из этого уровня неопределенности.Такие вершина и стимул существуют, поскольку граф является открытым. Таким образом, за конечное число шагов алгоритм достигнет последнего уровня неопределенности, состоящего из полностью определенных вершин исходного графа. Обход которого завершит уточняющий обход исходного графа.

    Архитектура тестовой системы UniTESK

    Архитектура тестовой системы UniTESK была разработана на основе многолетнего опыта тестирования промышленного программного обеспечения из разных предметных областей и разной степени сложности. Учет этого опыта позволил создать гибкую архитектуру, основанную на следующем разделении задачи тестирования на подзадачи.
  • Построение последовательности тестовых воздействий (тестовой последовательности), нацеленной на достижение нужного покрытия.
  • Создание единичного тестового воздействия в рамках тестовой последовательности.
  • Установление связи между тестовой системой и реализацией целевой системы.
  • Проверка правильности поведения целевой системы в ответ на единичное тестовое воздействие. Для решения каждой из этих подзадач предусмотрены специальные компоненты тестовой системы (см. Рисунок 1): для построения тестовой последовательности и создания единичных тестовых воздействий - обходчик и итератор тестовых воздействий, для проверки правильности поведения целевой системы - оракул, для установления связи между тестовой системой и реализацией целевой системы - медиатор. Рассмотрим подробнее каждый из указанных компонентов. Обходчик является библиотечным компонентом тестовой системы UniTESK и предназначен вместе с итератором тестовых воздействий для построения тестовой последовательности. В основе обходчика лежит алгоритм обхода графа состояний обобщенной конечно-автоматной модели целевой системы (конечного автомата, моделирующего целевую систему на некотором уровне абстракции). Обходчики, реализованные в библиотеках инструментов UniTESK, требуют, чтобы обобщенная конечно-автоматная модель целевой системы, была детерминированной и имела сильно-связный граф состояний. Итератор тестовых воздействий работает под управлением обходчика и предназначен для перебора в каждом достижимом состоянии конечного автомата допустимых тестовых воздействий. Итератор тестовых воздействий автоматически генерируется из тестового сценария, представляющего собой неявное описание обобщенной конечно-автоматной модели целевой системы. Архитектура тестовой системы UniTESK Рисунок 1.Архитектура тестовой системы UniTESK. Оракул оценивает правильность поведения целевой системы в ответ на единичное тестовое воздействие. Он автоматически генерируется на основе формальных спецификаций, описывающих требования к целевой системе в виде пред- и постусловий интерфейсных операций и инвариантов типов данных. Медиатор связывает абстрактные формальные спецификации, описывающие требования к целевой системе, с конкретной реализацией целевой системы. Медиатор преобразует единичное тестовое воздействие из спецификационного представления в реализационное, а полученную в ответ реакцию - из реализационного представления в спецификационное. Также медиатор синхронизирует состояние спецификации с состоянием целевой системы. Трасса теста отражает события, происходящие в процессе тестирования. На основе трассы можно автоматически генерировать различные отчеты, помогающие в анализе результатов тестирования.

    Генерация тестов на основе неопределенных моделей

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

    Графы с уточняемыми вершинами

    В данном разделе вводится понятие графа с уточняющими вершинами, используемое для формального представления неопределенных обобщенных моделей. Вершины графа интерпретируются как обобщенные состояния целевой системы, дуги - как переходы между обобщенными состояниями, а раскраска дуг - как стимулы, инициирующие соответствующие переходы. Определение. Ориентированным графом или просто графом G называется совокупность трех объектов (GV, GX, GE): GV - множество вершин, GX - множество раскрасок, GE Графы с уточняемыми вершинами и являются полными, то есть в них для любой не полностью определенной вершины существует хотя бы одна уточняющая ее полностью определенная вершина. Определение. Подграф G' = (GV', GX', GE') графа с уточняемыми вершинами G = (GV, GX, GE) называется полностью определенным, если все его вершины полностью определены, то есть GV' Графы с уточняемыми вершинами GVc. Определение.
    Подграф G' = (GV', GX', GE') графа с уточняемыми вершинами G = (GV, GX, GE) называется его информационной проекцией, если:
  • вершины из GV' попарно несравнимы в отношении Графы с уточняемыми вершинами и содержащий все дуги максимального полностью определенного подграфа G. Определение.


    Строго уточняющий маршрут {(vi, xi, vi+1)}i=0,n-1 в графе с уточняемыми вершинами G называется простым, если никакой его префикс {(vi, xi, vi+1)}i=0,m-1, где m < n, не является строго уточняющим маршрутом. Рассмотрим уточняющий маршрут P = {(vi, xi, vi+1)}i=0,n-1 в графе с уточняемыми вершинами G, начинающийся с неопределенной вершины: v0 = Графы с уточняемыми вершинами . Его можно представить в виде конкатенации: P = P0 … PN-1 PN (Pj={(vi, xi, vi+1)}i=kj,kj+1-1, 0 = k0 < … < kN+1 = n), в которой маршруты Pj для 0 ≤ j < N являются строго уточняющими, а маршрут PN является уточняющим, но не является строго уточняющим маршрутом, то есть различные вершины маршрута PN несравнимы в отношении уточнения. Определение. Пусть GVj={vkj,…,vkj+1-1}, где 0 ≤ j < N, - все вершины маршрута Pj кроме последней, GVN={skN,…,skN+1} - все вершины маршрута PN. Множество вершин GVj, где 0 ≤ j ≤ N, будем называть уровнем неопределенности j уточняющего маршрута P. При этом для j < N будем говорить, что маршрут Pj уточняет вершины с уровня неопределенности j до уровеня неопределенности j+1. Заметим, что различные вершины одного уровня неопределенности несравнимы в отношении уточнения, поэтому они являются подмножеством множества вершин некоторой информационной проекции. Рассмотрим следующий рисунок. Сплошные стрелки изображают обычные дуги графа; жирные стрелки изображают дуги внутри одного уровня неопределенности; стрелки, изображенные пунктиром, связывают уточняющие вершины с уточняемыми. Горизонтальные линии разделяют уровни неопределенности, которых на рисунке три. Графы с уточняемыми вершинами Рисунок 5.Уточняющий обход графа с уточняемыми вершинами. Заметим, что уровни неопределенности могут пересекаться. Пример. Рассмотрим граф с множеством вершин {true, false, Графы с уточняемыми вершинами }× {true, false, Графы с уточняемыми вершинами}, каждая пара из которых соединена дугой. Маршрут P={( Графы с уточняемыми вершинами, Графы с уточняемыми вершинами), (false, Графы с уточняемыми вершинами), (true, Графы с уточняемыми вершинами), (false, false), (true, Графы с уточняемыми вершинами)}, очевидно, является уточняющим. Ему соответствуют три уровня неопределенности:
  • GV0 = {( Графы с уточняемыми вершинами, Графы с уточняемыми вершинами )};
  • GV1 = {(false, Графы с уточняемыми вершинами ), (true, Графы с уточняемыми вершинами )};
  • GV2 = {(false, false), (true, Графы с уточняемыми вершинами )}. Видно, что уровни неопределенности GV1 и GV2 уточняющего маршрута P имеют общую вершину (true, Графы с уточняемыми вершинами ).

    Инструмент разработки тестов CTesK

    Инструмент CTesK является реализацией концепции UniTESK для языка программирования C. Для разработки компонентов тестовой системы в нем используется язык SeC (specification extension of C), являющийся расширением ANSI C. Инструмент CTesK включает в себя транслятор из языка SeC в C, библиотеку поддержки тестовой системы, библиотеку спецификационных типов и генераторы отчетов. Для пользователей Windows имеется модуль интеграции в среду разработки Microsoft Visual Studio 6.0. Компоненты тестовой системы UniTESK реализуются в инструменте CTesK с помощью специальных функций языка SeC, к которым относятся:
  • спецификационные функции - содержат спецификацию поведения целевой системы в ответ на единичное тестовое воздействие, а также определение структуры тестового покрытия;
  • медиаторные функции - связывают спецификационные функции с тестовыми воздействиями на целевую систему;
  • функция вычисления обобщенного состояния - вычисляет состояние обобщенной конечно-автоматной модели целевой системы;
  • сценарные функции - описывают набор тестовых воздействий для каждого достижимого обобщенного состояния.

    Краткий обзор технологии тестирования UniTESK

    Технология тестирования UniTESK [-] была разработана в Институте системного программирования РАН [] на основе опыта, полученного при разработке и применении технологии KVEST (kernel verification and specification technology) []. Общими чертами этих технологий являются использование формальных спецификаций в форме пред- и постусловий интерфейсных операций и инвариантов типов данных для автоматической генерации оракулов (компонентов тестовой системы, осуществляющих проверку правильности поведения целевой системы), а также применение конечно-автоматных моделей для построения последовательностей тестовых воздействий (тестовых последовательностей). В отличие от технологии KVEST, в которой для спецификации требований использовался язык RSL (RAISE specification language) [], технология UniTESK использует расширения широко известных языков программирования.

    Неопределенность и трехзначная логика Клини

    Для не полностью определенных состояний целевой системы, не исключены ситуации, когда из-за недостатка информации некоторые логические условия нельзя отнести ни к истинным, ни к ложным. В таких случаях использование двузначной логики для описания свойств целевой системы представляется не совсем адекватным. Для того чтобы описать логическое условие P, которое может быть неопределенным, с помощью двух значений истинности нужно использовать специальные модальные функции возможности и необходимости: ◊P и ?P и описать условие парой (◊P, ?P). Тогда истинное условие описывается парой (true, true) (необходимо), ложное - парой (false, false) (невозможно), неопределенное - парой (true, false) (возможно, но не необходимо). Описывать логические условия парой модальностей не очень удобно. Естественней положить, что функция истинности может принимать три значения: true (истина), false (ложь) и Неопределенность и трехзначная логика Клини(неопределенность), а модальные функции возможности и необходимости рассматривать как функции {true, false, Неопределенность и трехзначная логика Клини } → {true, false}, определяемые следующими таблицами истинности:

    ?
    false false false false
    true true true true
    Неопределенность и трехзначная логика Клини true Неопределенность и трехзначная логика Клини false
    Таблица 1. Определение модальных функций ◊ и ?. Впервые модальные функции возможности и необходимости подобным образом определил Лукасевич (Lukasiewicz), в трехзначной логике L3 []. В логике L3 неопределенное значение Неопределенность и трехзначная логика Клини интерпретируется как промежуточное между ложью и истиной. В нашем случае неопределенное значение следует интерпретировать иначе - как неизвестное значение, которое может быть как истиной, так ложью, но какое оно именно - неизвестно. Подобным образом неопределенное значение интерпретируется в трехзначной логике Клини (Kleene), называемой K3. При получении дополнительной информации значение логического условия может измениться с неопределенного значения Неопределенность и трехзначная логика Клини на false или true, но невозможно, чтобы значение изменилось с true на false или наоборот.

    Неопределенные обобщенные модели

    Для генерации тестовых последовательностей обычно используют не сами модели тестируемых систем, а их обобщения, называемые обобщенными моделями. Например, в технологии тестирования UniTESK используются обобщенные конечно-автоматные модели, описанные в тестовых сценариях. Поскольку состояния модели могут быть неопределенными, не исключено что и состояния обобщенной модели (обобщенные состояния) окажутся неопределенными. Пусть M - уточняемый тип, представляющий состояния модели, S - уточняемый тип, представляющий обобщенные состояния, а f: M → S - функция обобщения модели - функция, ставящая в соответствие каждому состоянию модели обобщенное состояние. Будем считать, что функция f является регулярной и полной. В этом случае она задает факторизацию на множестве полностью определенных значений типа M. Для удобства будем считать, что f( Неопределенные обобщенные моделиM) = Неопределенные обобщенные моделиS.

    Неопределенные значения и уточняемые типы

    Поскольку в процессе тестирования возможна ситуация, когда состояние целевой системы не полностью определено, для моделирования состояния системы удобно использовать типы, поддерживающие неопределенные значения. Будем считать, что на множестве значений типов заданы отношения уточнения, являющиеся отношениями частичного порядка. Отношения уточнения позволяют сравнивать информативность значений: чем "больше" значение в отношении уточнения, тем больше информации оно несет (см. Рисунок 3). Введем несколько понятий. Неопределенные значения и уточняемые типы Рисунок 3.Отношение уточнения значений типа. Определение. Тип T с заданным на на нем отношением уточнения Неопределенные значения и уточняемые типыT или просто Неопределенные значения и уточняемые типы и называть неопределенным значением типа T. Также будем считать, что любое значение типа T можно получить за конечное число уточнений неопределенного значения Неопределенные значения и уточняемые типыT. Определение. Максимальные значения уточняемого типа T по отношению Неопределенные значения и уточняемые типыT называются (полностью) определенными значениями типа T. Значения уточняемого типа T, не являющиеся полностью определенными, называются неопределенными или не полностью определенными значениями типа T. Определение. Пусть T - уточняемый тип, тогда через Tc будем обозначать (полностью) определенный подтип типа T, то есть подтип, состоящий из всех полностью определенных значений типа T. Если некоторое свойство P имеет не полностью определенное значение x, это означает, что на самом деле значением свойства P является одно из полностью определенных значений, уточняющих x, но какое именно - неизвестно. Нужно быть аккуратным при сравнении не полностью определенных значений на равенство. С одной стороны, разным неопределенным значениям может соответствовать одно и то же полностью определенное значение, с другой, одному неопределенному значению могут соответствовать разные полностью определенные значения. Пример.
    Рассмотрим пример уточняемого типа ST, представляющего нечеткое множество значений типа T. Нечеткое множество s определяется трехзначной функцией принадлежности fs: T → {true, false, Неопределенные значения и уточняемые типы}. fs(x) интерпретируется следующим образом: если fs(x) = true, то x принадлежит множеству s, если fs(x) = false, то x не принадлежит множеству s, если fs(x) = Неопределенные значения и уточняемые типы, то неизвестно, принадлежит x множеству s или нет. Отношение уточнения естественно определить таким образом: s1 Неопределенные значения и уточняемые типыsT s2 тогда и только тогда, когда из того что fs1(x) = true, вытекает, что fs2(x) = true, а и из того, что fs1(x) = false, вытекает, что fs2(x) = false. Определение. Пусть T1, …, Tn - уточняемые типы. Определим на декартовом произведении T1× … ×Tn отношение уточнения: (x1, …, xn) Неопределенные значения и уточняемые типыT1× ... ×Tn (y1, …, yn) тогда и только тогда, когда x1 Неопределенные значения и уточняемые типыT1 y1, …, xn Неопределенные значения и уточняемые типыTn yn. От функций, определенных на уточняемых типах будет требовать регулярности и полноты: чем определеннее значение аргумента, тем определеннее значение функции, причем полностью определенному значению аргумента соответствует полностью определенное значение функции. Определение. Функция f: T1 → T2 называется регулярной, если из того, что x Неопределенные значения и уточняемые типыT1 y следует, что f(x) Неопределенные значения и уточняемые типыT2 f(y). Определение. Функция f: T1 → T2 называется полной, если f(T1с) Неопределенные значения и уточняемые типы T2с, то есть из того, что x Неопределенные значения и уточняемые типы T1c следует, что f(x) Неопределенные значения и уточняемые типы T2c. Обычно в языках программирования и спецификаций есть базовые типы и есть составные типы, значения которых строятся на основе значений других типов. Определение. Составной тип называется регулярным, если все его конструкторы являются регулярными функциями. Определение. Составной тип называется полным, если все его конструкторы являются полными функциями.

    Неполнота информации в тестировании

    При разработке тестов часто возникает ситуация, когда нет прямого доступа к внутреннему состоянию целевой системы. Такое тестирование называется тестированием со скрытым состоянием (hidden state testing). В отличие от тестирования с открытым состоянием (open state testing), когда состояние целевой системы можно получить непосредственно через ее интерфейс, при тестировании со скрытым состоянием разработчику тестов необходимо четко представлять как изменяется состояние целевой системы при том или ином воздействии на нее. Для возможности определить состояние целевой системы после обработки тестового воздействия важную роль играет детерминизм требований. Неполнота информации в тестировании Рисунок 2.Виды неполноты информации, возникающие при тестировании. Другим видом неполноты информации в тестировании является неопределенность начального состояния целевой системы. Такое тестирование называется тестированием с неизвестным начальным состоянием. В отличие от тестирования с известным начальным состоянием, при тестировании с неизвестным начальным состоянием на целевую систему сначала нужно подать последовательность тестовых воздействий, приводящую ее в некоторое известное состояние. Здесь важную роль играют сценарная полнота и детерминизм требований. Подведем итог относительно различных видов неполноты информации, которые могут возникнуть при тестировании программных систем (см. ). Во-первых, это может быть контрактная неполнота требований. Во-вторых, при тестировании может быть скрыто состояние целевой системы, что не позволяет непосредственно получать состояние целевой системы через ее интерфейс. В-третьих, может быть не определено начальное состояние целевой системы. В-четвертых, требования могут не определять как изменяется состояние целевой системы при выполнении того или иного воздействия или определять это недетерминированным образом. Наконец, реализация целевой системы может быть недетерминированной.

    Полнота функциональных требований

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

    Простое расширение инструмента CTesK

    Для описания предикатов, которые из-за неопределенности состояния целевой системы могут принимать неопределенное значение, предлагается добавить в библиотеку CTesK перечислимый тип Bool3, представляющий значения истинности в трехзначной логике, вместе с основными функциями, реализующими отрицание, дизъюнкцию и конъюнкцию: typedef enum { True_Bool3 = 1, // истина False_Bool3 = 0, // ложь Unknown_Bool3 = -1 // неопределенное значение } Bool3; // отрицание Bool3 not_Bool3(Bool3 arg); // дизъюнкция Bool3 or_Bool3(Bool3 lhs, Bool3 rhs); // конъюнкция Bool3 and_Bool3(Bool3 lhs, Bool3 rhs); Для удобства работы со значениями типа Bool3 также в библиотеку CTesK можно добавить модальные функции возможности и необходимости: // модальная функция возможности bool may_Bool3(Bool3 arg); // модальная функция необходимости bool shall_Bool3(Bool3 arg); Для задания отношения уточнения на множестве значений спецификационных типов предлагается добавить в спецификационные типы поле .refines, которое можно инициализировать функцией вида: bool refines(Object *lhs, Object *rhs); Функция возвращает значение true тогда и только тогда, когда спецификационный объект rhs уточняет спецификационный объект lhs. Используя функцию refines(), обходчики в процессе построения тестовой последовательности могут учитывать изменения уровня неопределенности, тем самым подавая вспомогательные инициализирующие тестовые воздействия, которые сейчас приходится писать вручную. Поскольку значение, возвращемое постусловием спецификационной функции, имеет двузначный логический тип, а определить правильность или ошибочность поведения целевой системы в условиях неполной информации не всегда возможно, предлагается при определении структуры тестового покрытия выделять специальные ветви функциональности Unknown: { "Описание ветви функциональности", Unknown }; Выделенные ветви функциональности описывают ситуации, в которых оракул тестовой системы не обладает достаточной информацией, чтобы вынести однозначный вердикт о правильности или ошибочности поведения целевой системы. При попадании в одну из таких ветвей можно не проверять постусловие. Так как ветви Unknown носят вспомогательный характер, информация об их покрытии не должна попадать в отчеты о достигнутом покрытии. Если после выполнения теста некоторые ветви функциональности, отличные от Unknown, оказались непокрытыми, это может быть связано со сценарной неполной требований. Возможно, что требования не определяют как достичь или идентифицировать соответствующие тестовые ситуации. Предположим, что оракул в качестве вердикта может возвращать неопределенное значение. Если после выполнения теста для некоторых ветвей функциональности вердикт оказывается неопределенным, это может быть связано с контрактной неполнотой требований. Цель тестирования - покрыть все ветви функциональности, вынеся при этом определенные вердикты, и достижение этой цели как правило связано с уточнением требований к целевой системе.

    Простое расширение технологии UniTESK

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

    Спецификация в условиях неполной информации

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

    Тестирование в условиях неполной информации. Подход к разработке спецификаций и генерации тестов

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

    Требования и оценка правильности поведения системы

    Теперь рассмотрим определение полноты требований в контексте разработки спецификаций. Одна из основных задач разработчика тестов - определить, какое поведение целевой системы следует считать правильным, а какое ошибочным. Ясно, что требования, претендующие на полноту, должны позволять проводить такую классификацию. Определение. Будем говорить, что требования к целевой системе являются контрактно полными, если на их основе можно однозначно классифицировать поведение целевой системы на правильное или ошибочное на любом допустимом сценарии взаимодействия с ней. В противном случае будем говорить, что требования являются контрактно неполными. Контрактная неполнота требований означает, что в оракуле, оценивающем поведение целевой системы, есть "дыры" - возможны ситуации, в которых он не может вынести однозначный вердикт о правильности или ошибочности поведения целевой системы. Проиллюстрируем понятие контрактной полноты требований на следующем примере. Пример. Рассмотрим целевую систему из предыдущего примера. Пусть требования к функции pop() формулируются следующим образом: Вызов функции pop для непустого стека возвращает последний добавленный в него элемент. Эти требования не являются контрактно полными, так как в них не описано, как должна вести себя функция pop для пустого стека. Если добавить в требования описания того, что вызов функции pop для пустого стека должен возвратить NULL, требования становятся контрактно полными.

    Требования и сценарии взаимодействия с системой

    Задачу тестирования можно разбить на две основные подзадачи: построение последовательности тестовых воздействий и проверку правильности поведения целевой системы в ответ на поданные тестовые воздействия. В соответствии с этим, разработка тестов по технологии UniTESK разбивается на разработку тестовых сценариев и разработку спецификаций. Данный раздел посвящен определению полноты требований для разработки тестовых сценариев. Определение. Будем говорить, что требования к целевой системе являются сценарно полными, если они описывают, как на основе интерфейса целевой системы можно реализовать все указанные в них варианты использования, а также воссоздать или идентифицировать все указанные в них ситуации. В сценарно неполных требованиях для некоторых описанных в них ситуаций могут отсутствовать описания способов их достижения или идентификации. В контексте технологии тестирования UniTESK это означает невозможность достичь 100% покрытия тестовых ситуаций, определенных в спецификации на основе требований. Если требования сценарно неполны, значения некоторых атрибутов состояния спецификации оказываются неопределенными на любых последовательностях тестовых воздействий. Определение. Будем говорить, что требования к целевой системе являются детерминированными, если они описывают, как на основе интерфейса целевой системы можно контролировать или наблюдать ее поведение при выполнении всех возможных сценариев взаимодействия с ней. В противном случае будем говорить, что требования являются недетерминированными. Если требования недетерминированны, то возможны сценарии взаимодействия с целевой системой, при выполнении которых нельзя однозначно определить состояние целевой системы. В контексте технологии тестирования UniTESK это означает, что функция вычисления постсостояния спецификации в некоторых ситуациях оказывается недетерминированной. Проиллюстрируем понятие сценарной полноты и детерминизма требований на следующем простом примере. Пример. Целевая система является стеком.
    Интерфейс целевой системы состоит из следующих функций:
  • Stack* create(void);
  • void push(Stack *stack, Object *obj);
  • Object* pop(Stack *stack). Пусть требования к целевой системе формулируются следующим образом: Функция create создает пустой стек и возвращает указатель на него. В случае, если стек не полностью заполнен, вызов функции push добавляет в него элемент, противном случае, вызов функции push не меняет состояние стека. Вызов функции pop для непустого стека возвращает последний добавленный в него элемент, для пустого стека - NULL. Эти требования не являются сценарно полными, так как не описывают как достичь или идентифицировать указанную в них ситуацию "стек полностью заполнен". Также эти требования не являются детерминированны, так как не позволяют однозначно определить в какое состояние перейдет целевая система после вызова функции push. Если добавить в требования описание ситуации "стек полностью заполнен", указав, например, максимальное возможное число элементов в стеке, требования становятся сценарно полными и детерминированными.

    и широко распространяются методы, оперирующие

    В последнее время в науке и технике интенсивно развиваются и широко распространяются методы, оперирующие с неполной или нечеткой информацией []. Примерами теорий, в рамках которых разрабатываются подобные методы, являются математическая статистика, теория нечетких множеств, теория возможностей и многие другие. Методы анализа и обработки неполной информации хорошо зарекомендовали себя в искусственном интеллекте, теории баз данных, системном анализе и исследовании операций. Сегодня их активно используют для моделирования сложных физических, экономических и социальных явлений []. Таким образом, работа с неопределенной и нечеткой информацией постепенно становится уделом точных наук. Программной инженерии, претендующей на точность и методичность инженерной дисциплины, также приходится иметь дело с неполной информацией. В проектах по разработке программного обеспечения неопределенность возникает на самых ранних фазах. Размытый и туманный характер носят требования к программной системе, которые приходится многократно уточнять, дополнять и согласовывать. После доработок требования используются на следующей фазе, на которой, как правило, имеют дело с более низким уровнем абстракции, поэтому требования вновь кажутся неполными, их снова приходится дорабатывать. Таким образом, на каждой фазе проекта исходная информация оказывается не полностью определенной. В работе рассматривается лишь один из этапов разработки программного обеспечения - тестирование. Обычно на этапе тестирования располагают более-менее завершенным продуктом и достаточно проработанными требованиями. Казалось бы, что на этом этапе в требованиях не должно быть никаких неопределенностей, но, как показывает практика, это не так. Использование требований для целей тестирования в очередной раз выявляет их недостатки. Требования после многочисленных доработок вновь оказываются неполными. С другой стороны, в процессе тестирования возможна ситуация, когда состояние целевой системы не полностью определено. Например, такая ситуация может возникнуть, когда состояние целевой системы скрыто, а начальное состояние в требованиях не определено.
    Неполнота информации о состоянии целевой системы может приводить к невозможности адекватной оценки правильности поведения целевой системы. Перед тем, как оценивать поведение, необходимо получить представление о состоянии целевой системы путем подачи тестовых воздействий и анализа реакций на них. Статья выполнена в контексте тестирования на основе моделей (MBT, model based testing), более точно, в контексте технологии тестирования UniTESK [-], хотя рассматриваемые в ней вопросы актуальны не только для этой технологии. Модель представляет собой некоторое достаточно абстрактное отображение структуры и поведения целевой системы, созданное на базе требований []. В процессе тестирования состояние модели можно интерпретировать, как располагаемую информация о состоянии целевой системы, сформулированную в терминах требований. В работе предлагается подход к разработке функциональных спецификаций и генерации функциональных тестов, позволяющий работать с неполными требованиями к целевой системе и неполной информацией о ее состоянии. Подход основан на использовании неопределенных значений для моделирования состояния целевой системы, а также трехзначной логики Клини (Kleene) [] для работы с требованиями и описания свойств системы. Результаты работы получены в ходе выполнения проекта по разработке открытого тестового набора OLVER для операционной системы Linux []. Статья построена следующим образом. Во втором, следующем за введением, разделе делается краткий обзор технологии тестирования UniTESK. В третьем разделе рассматриваются вопросы, связанные с неполнотой функциональных требований и неполнотой информации о состоянии целевой системы в процессе тестирования. В четвертом разделе описывается подход к спецификации систем на основе неопределенных значений, уточняемых типов и трехзначной логики Клини. В пятом разделе рассматриваются вопросы построения тестовых последовательностей по неопределенным обобщенным моделям. В шестом разделе предлагается простое расширение технологии тестирования UniTESK на примере инструмента разработки тестов CTesK [].В заключении делается краткое резюме работы.

    В работе были рассмотрены вопросы

    В работе были рассмотрены вопросы функционального тестирования программных систем в условиях неполной информации. На базе технологии тестирования UniTESK предложен подход к разработке функциональных спецификаций и генерации функциональных тестов, основанный на использовании неопределенных значений для моделирования состояния целевой системы, а также трехзначной логики Клини для работы с неполными требованиями и описания свойств системы. Идеи предложенного подхода нашли свое применение в проекте по разработке открытого тестого набора OLVER для операционной системы Linux.

    Тестирование софта - статьи

    Абстрактное состояние

    Абстрактное состояние – это множество структур данных, которые моделируют внутреннее состояние целевой системы. Все действия, которые выполняет целевая система, моделируются в терминах абстрактного состояния. Документация на IPv6 не содержит требований к внутреннему устройству реализации IPv6. Абстрактное состояние было спроектировано таким образом, чтобы предоставить удобные концептуальные структуры данных для спецификаций стимулов и реакций. Абстрактное состояние для MSR IPv6 включает следующие элементы:
  • Множество исходящих пакетов IPv6. Это множество моделирует множество пакетов, которые реализация передает канальному уровню
  • Набор очередей входящих датаграм UDP. Данные очереди используются для моделирования получения данных клиентами UDP.
  • Пул фрагментов. Пул фрагментов используется для двух целей. (1) мы моделируем процедуру сборки пакетов в целевой системе. (2) мы проверяем, что из фрагментов, которые выпускает целевая система, получатель сможет воссоздать оригинальный пакет
  • Набор таблиц Neighbor Discovery. Эти таблицы используются при моделировании алгоритмов Neighbor Discovery.

    Функции IPv выбранные для тестирования

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


    ICMPv6 – это протокол, который используется для передачи служебных сообщений на сетевом уровне. В частности, форматом пакетов ICMPv6 пользуются протоколы Neighbor Discovery (см. далее). Протокол ICMPv6 специфицирован в RFC 2463 . Кроме того, ICMPv6 используется некоторыми протоколами верхнего уровня (например, UDP), для передачи сообщений об ошибках. ICMPv6 Echo ICMPv6 Echo – это простой диагностический протокол, который используется для определения пропускной способности сети и состояния удаленного узла IPv6. В частности, данный протокол использует утилита ping. Протокол ICMPv6 Echo является неотъемлемой частью ICMPv6 и определен также в RFC 2463 . Протокол использует формат пакетов ICMPv6. Всего в протоколе два вида сообщений – Echo Request и Echo Reply. При получении Echo Request узел IPv6 должен выслать ответ, Echo Reply, тому узлу, от которого пришел запрос. Таким образом, узел – источник запроса – может установить, что адресат запроса достижим. Если же узел-источник не получит ответа, то либо узел-адресат не функционирует, либо сеть перегружена и не в состоянии доставить пакет адресату. При тестировании мы проверяли, что реализация корректно строит пакеты-запросы Echo Request, и выдает корректные ответы (Echo Replies) на корректные запросы Echo Request.

    Тестовый набор разрабатывался средствами реализации

    Тестовый набор разрабатывался средствами реализации UniTesK для языка С – CTesK-lite. CTesK-lite поддерживает разработку спецификаций на спецификационном расширении языка С – SEC (Specification Extension of C language). В CTesK-lite реализована архитектура тестового набора UniTesK – тестовые сценарии, оракулы, медиаторы. Реализована поддержка тестирования систем с отложенными реакциями. SEC – это ASCII C, к которому были добавлены ряд конструкций, характерных для академических языков формальных спецификаций. В частности, в SEC реализованы пред- и пост- условия, инварианты типов данных и глобальных переменных, описатели доступа (access descriptors). Из спецификаций на языке SEC генерируется код на языке С, который затем компилируется обыкновенным компилятором С (в нашем случае MS VC 6.0). Для разработки и сборки тестового набора мы использовали MS Visual Studio 6.0. Проверка синтаксиса и семантики для SEC не реализована, но синтаксические ошибки в спецификациях можно идентифицировать по ошибкам, которые компилятор С находит в сгенерированном коде. Далее в данном разделе приведены краткие описания компонентов тестового набора.

    Медиаторы для непроцедурных стимулов

    Как упоминалось выше, в интерфейс целевой системы входят непроцедурные стимулы – пакеты IPv6, которые поступают в целевую систему из сети. Медиатор для непроцедурных стимулов представляет собой распределенное приложение, реализованное в рамках архитектуры клиент-сервер. Клиент – тестовая система – передает серверу запрос, в котором содержится IPv6 пакет адрес узла назначения. Сервер, который работает на некотором узле в том же сегменте сети, что и целевой узел, пересылает указанный пакет по указанному IPv6 адресу. Для того, чтобы обмен запросами между клиентом и сервером не оказывал воздействия на целевую систему, запрос серверу передается по сети IPv4.

    Медиаторы для процедурных стимулов

    Тестовая система работает на том же компьютере, что и целевая система. Благодаря этому медиаторы для процедурных стимулов устроены просто. Медиатор для отправки датаграммы UDP вызывает процедуру sendto для отправки указанных данных по указанному адресу через указанный сокет. После того, как sendto возвращает управление, медиатор анализирует возвращенное значение и, при необходимости, код ошибки и формирует собственное возвращаемое значение (OK/FAIL). Медиатор для функции Echo Request обращается к соответствующей точке доступа транспортного драйвера MSR IPv6. После завершения вызова медиатор анализирует возвращенное значение и, при необходимости, код ошибки, сохраняет данные ответа (Echo Reply) и формирует собственное возвращаемое значение (OK/FAIL)

    Медиаторы

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


    MSR IPv6 – это свободно распространяемая открытая реализация IPv6 для Windows NT/Windows 2000. Как заявляют разработчики, назначение MSR IPv6
  • Бесплатная общедоступная реализация IPv6
  • Пример реализации протокола сетевого уровня для Windows NT
  • Исследования протоколов IPv6 В MSR IPv6 реализованы основные функции оконечного узла, описанные в предыдущем разделе, а также ряд базовых функций маршрутизаторов IPv6. Кроме того, MSR IPv6 содержит собственную реализацию транспортных протоколов TCP и UDP и интерфейс сокетов для доступа отправки и получения пакетов в сети IPv6. MSR IPv6 может устанавливаться параллельно стеку IPv4 без каких бы то ни было последствий для работоспособности последнего. Более подробную информацию о MSR IPv6 можно получить на сайте Microsoft Research и в работе .

    Neighbor Discovery

    Neighbor Discovery (ND) объединяет группу протоколов, которые решают следующие задачи:
  • Определение адресов канального уровня для узлов, которые располагаются на одном сегменте локальной сети (соседей)
  • Определение маршрутизаторов, которые подсоединены к сегменту локальной сети
  • Определение того, через какой маршрутизатор отправлять пакеты, адресованные узлам за пределами сегмента локальной сети Для сбора информации о соседних узлах узел IPv6 отправляет и получает служебные пакеты. Всего в ND на данный момент насчитывается пять видов служебных пакетов. Все пакеты используют формат пакетов ICMPv6. Протокол ND задан в RFC 2461 Для уменьшения нагрузки на сеть собранная информация сохраняется узлом, поэтому на протоколы ND возлагается задача своевременно обновлять сведения соседних узлах и удалять устаревшие записи. При тестировании мы проверяли, что реализация правильно обновляет информацию о соседях, правильно выбирает маршрутизаторы для отправки пакетов за пределы локальной сети, правильно сообщает информацию о себе соседям.

    Оценка покрытия кода целевой системы

    Для точного определения покрытия кода целевой системы необходимо пропустить тесты на отладочной версии ядра Windows 2000 с включенным профилировщиком ядра. Это сделано не было, поэтому оценка покрытия кода получена косвенным методом. Мы составили список всех процедур, которые есть в целевой системе, и для каждой процедуры оценили, будет ли она вызываться при прогоне тестов. Оказалось, что 20% процедур в целевой системе не будут вызываться при прогоне тестов; как правило, эти процедуры реализуют функции IPv6, исключенные из рассмотрения при разработке тестового набора. 60% процедур реализуют выбранные нами функции IPv6; эти процедуры должны вызываться при пропуске тестов. Про остальные 20% мы затрудняемся сказать что-либо определенное; как правило, это вспомогательные процедуры. Итого получается оценка покрытия исходных текстов 60-80%.


    Оракулы и спецификации

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

    Рассмотрим особенности, которые отличают тестирование

    Рассмотрим особенности, которые отличают тестирование MSR IPv6 от тестирования обычных программных интерфейсов. Отложенные реакции. Целевая система демонстрирует реакции, которые происходят спустя некоторое время после воздействия извне, причем это реакции нескольких видов. Непроцедурные стимулы. Целевая система допускает как процедурные, так и непроцедурные воздействия. Процедурные воздействия – вызов процедур, которые входят в программный интерфейс. Непроцедурные воздействия – входящие пакеты IPv6, которые целевая система получает из сети. Доступ к процедурным стимулам.. Реализация MSR IPv6 расположена по большей части в ядре Windows 2000, поэтому программный интерфейс MSR IPv6 напрямую не доступен. Для обращения к процедурам, которые экспортирует модуль, расположенный в ядре, необходимо пользоваться специальными средствами операционной системы. Недетерминизм реакций. Отложенные реакции, вообще говоря, недетерминированы. При одном и том же внешнем воздействии целевая система может демонстрировать различные отложенные реакции. Это связано с тем, что реакции системы определяются составляющими её внутреннего состояния, которые невозможно определить извне, а также с тем, что ряд алгоритмов в IPv6 используют случайные числа. Рассмотрим теперь, какие средства предлагает UniTesK для решения выявленных проблем. Для тестирования систем с отложенными реакциями в UniTesK разработаны методы разработки спецификаций и тестовых сценариев, основанные на формализме конечных автоматов с отложенными реакциями. Механизм медиаторов позволяет отделить модель стимулов от того, как в действительности происходит воздействие на целевую систему. С точки зрения модели не играет никакой роли, как осуществляется воздействие – вызовом процедуры, обращение к модулю ядра, отправкой пакета в целевой компьютер извне. Конкретное воздействие определяется соответствующим медиатором. В подходе UniTesK используются имплицитные спецификации. Такие спецификации описывают, какие реакции целевой системы и какие изменения в состоянии целевой системы являются допустимыми. Спецификации не вычисляют реакции и конечное состояние целевой системы, лишь проверяют условия допустимости. Это позволяет описывать недетерминизм поведения целевой системы.


    Отправка и получение пакетов.

    Протокол обеспечивает обмен данными между протоколами более высокого уровня. Также протокол производит обмен служебными сообщениями сетевого уровня между узлами. Формат пакетов IPv6 и правила обработки пакетов определены в RFC 2460 . При отправке пакета протокола верхнего уровня IPv6 формирует свой собственный пакет, включает в него заданный пакет верхнего уровня, и передает полученный пакет IPv6 в сеть. При тестировании мы проверяем, что реализация формирует корректные пакеты IPv6, и данные протокола верхнего уровня передаются в сеть без искажений. При получении пакета из сети реализация IPv6 разбирает пакет. В том случае, если в пакет содержит ошибки, то реализация пакет отбрасывает или отправляет источнику пакета сообщение об ошибке. Если же пакет корректен, то реализация определяет протокол верхнего уровня, которому адресованы данные, содержащиеся в пакете, и передает их получателю. При тестировании мы проверяем поведение реализации как на корректных пакетах, так и на некорректных пакетах. Фрагментация и сборка пакетов. Большинство протоколов канального уровня накладывают ограничения на размер передаваемых данных. Так, в сети Ethernet максимальный размер пакета сетевого уровня, который передается в одном кадре Ethernet, равен 1500 байт. Для передачи больших массивов данных в IPv6 предусмотрена функция фрагментации пакетов. В том случае, если размер пакета протокола верхнего уровня превосходит максимальный допустимый размер, то реализация IPv6 пересылает такой пакет по частям. Реализация нарезает данные на фрагменты и отправляет каждый фрагмент в отдельном пакете. В точке назначения фрагменты накапливаются и по получению всех фрагментов из них восстанавливается первоначальный пакет. Фрагментация и сборка пакетов происходит прозрачно для протоколов верхнего уровня. При тестировании мы проверяли, что реализация протокола корректно строит фрагменты, корректно восстанавливает данные из последовательности фрагментов, а также надежно обрабатывает ошибки во фрагментах и сериях фрагментов. Именно в функции сборки фрагментов мы обнаружили наиболее серьезный дефект в MSR IPv6, который приводит к перезагрузке операционной системы.

    Применение UniTesK к тестированию систем с отложенными реакциями

    Как сказано выше, целевая система может демонстрировать реакции спустя некоторое время после того, как на неё было оказано воздействие. Мы называем такое поведение отложенными реакциями. Как правило, неудобно моделировать отложенные реакции посредством обычных конечных автоматов. Для облегчения моделирования систем с отложенными реакциями в UniTesK введено представление об автоматах с отложенными реакциями . Автоматы с отложенными реакциями отличаются от обычных конечных автоматов тем, что переходы в между состояниями представляют собой цепочку реакций. Как правило, целевая система демонстрирует реакции нескольких различных видов. Например, у реализации сетевого протокола есть как минимум два вида реакций – пакеты, которые отправляется в сеть, и пакеты, которые передаются на верхний уровень. Для регистрации отложенных реакций целевой системы тестовая система содержит так называемые кетчеры. Кетчеры, как видно из названия, “хватают” (catch) реакции целевой системы и передают их в тестовую систему. В тестовой системе должны быть кетчеры для каждого вида реакций. Реакции могут регистрироваться с запозданием, причем для реакций различных типов величина задержки может быть различной. Тестовой системе необходимо определить допустимый порядок реакций различных видов. Процедура определения допустимой последовательности реакций называется сериализацией. В процессе сериализации тестовая система строит различные цепочки реакций и проверяет их допустимость. Каждая реакция рассматривается как переход между промежуточными состояниями, конечное состояние последней реакции рассматривается как последнее состоянии в цепочке реакций и принадлежит множеству состояний конечного автомата. Если при сериализации не удалось найти ни одной допустимой последовательности реакций, то тестовая система выносит вердикт о рассогласовании модели и целевой системы – набор зарегистрированных реакций не соответствуют спецификации. Сериализация реакций происходит после того, как все реакции целевой системы получены.
    Это накладывает ограничения на спецификации и технику тестирования. Во-первых, при разработке спецификаций используется подход “черного ящика”, при котором внутреннее состояние целевой системы недоступно и его приходится моделировать в спецификациях. Во-вторых, на целевую систему накладывается жесткое ограничение на стационарность начальных и конечных состояний. Рассмотрим последнее требование более подробно. Сериализация проводится после того, как собраны все реакции на воздействие. Это означает, что существует такой интервал времени T0, что в течение T0 с момента оказания воздействия целевая система продемонстрирует все реакции на воздействие. Состояние, в которое перейдет целевая система, обладает тем свойством, что нем система не демонстрирует спонтанных (то есть без воздействия извне) реакций. Такие состояний называются стационарными. Реализованный в UniTesK метод тестирования систем с отложенными реакциями требует, чтобы в любом состоянии конечное состояние для любого допустимого в данном состоянии воздействия было стационарным, причем существует верхняя грань времени ожидания реакций по всем состояниям и воздействиям. Кроме того, алгоритм обхода графа состояний автомата тестового сценария требует, чтобы воздействие на целевую систему оказывалось только из стационарного состояния. Несмотря на жесткость указанных ограничений, UniTesK применим для тестирования широкого класса систем с отложенными реакциями. В частности, как показал анализ документации, протокол IPv6 удовлетворяет требованию на стационарность конечных состояний, поэтому UniTesK возможно использовать для построения тестовых наборов для IPv6.


    Процесс разработки тестового набора

    Процесс разработки тестового набора для MSR IPv6 можно разделить на несколько фаз:
  • Определение интерфейса
  • Разработка спецификаций для отправки и получения пакетов без Neighbor Discovery
  • Разработка и прогон тестовых сценариев для отправки и получения пакетов без Neighbor Discovery
  • Пополнение спецификаций функцией Neighbor Discovery.
  • Разработка и прогон тестовых сценариев для Neighbor Discovery
  • Прогон всего полученного тестового набора На фазе определения интерфейса был определен состав функций IPv6 для тестирования, и определены стимулы и реакции, относящиеся к выбранным функциям. Разработка спецификаций проводилась в два этапа. На первом этапе были разработаны спецификации, в которых не моделировалось поведение Neighbor Discovery. Для полученных спецификаций были разработаны и отлажены тестовые сценарии. Протоколы Neighbor Discovery предназначены для сбора и обновления информации о конфигурации сегмента локальной сети, к которой подключен узел IPv6. В спецификациях и тестовых сценариях, которые мы разработали на первом этапе, конфигурация сети предполагалась заданной и неизменной во времени. Это предположение позволило разработать и отладить довольно содержательные тестовые сценария для функций отправки и получения пакетов и ICMPv6. На втором этапе мы добавили спецификации протоколов Neighbor Discovery. Для отладки полученных спецификаций мы воспользовались предположением, что сценарии, разработанные для спецификаций без ND, должны корректно работать и для спецификаций с ND. Избранная нами стратегия себя оправдала. При прогоне тестовых сценариев, разработанных на первом этапе, было выявлено и исправлено много недочетов в спецификациях Neighbor Discovery. После того, как спецификации Neighbor Discovery были отлажены, были разработаны тестовые сценарии специально для Neighbor Discovery.

    Разбиение MSR IPvна подсистемы

    MSR IPv6 отличает хорошая структуризация исходных текстов. Код, который реализует некоторую функцию IPv6, как правило сгруппирован в отдельные файлы, и может рассматриваться как отдельная подсистема MSR IPv6. На рисунке 1 представлено разбиение MSR IPv6 на подсистемы. Разбиение MSR IPvна подсистемы В средней части рисунка представлены подсистемы, которые реализуют базовые функции IPv6 – отправка и получение пакетов, ND, ICMPv6. Прямоугольник, помеченный как “Routing”, реализует часть функций Neighbor Discovery, которые относятся к работе с маршрутизаторами. В верхней части рисунка представлены транспортные протоколы, реализации которых есть в MSR IPv6 – TCP и UDP. В нижней части рисунка расположены подсистемы, которые обеспечивают взаимодействие IPv6 с протоколами нижнего уровня (LAN) и псевдоустройством закольцованной связи (loopback).

    Результаты

    Был разработан тестовый набор для реализации протокола IPv6. Тестовый набор был пропущен на Windows 2000, в качестве целевой системы выступала реализация IPv6 от Microsoft Research (MSR IPv6 version 1.4). Проект по разработке тестового набора для MSR IPv6 показал применимость UniTesK для тестирования сложного API с непроцедурными стимулами и отложенными реакциями. Также показана применимость UniTesK для тестирования внутренних подсистем ядра Windows 2000. В ходе тестирования были обнаружены отклонения от стандартов IPv6 и ошибки программирования. Отклонения от стандартов заключаются в том, что в ряде случаев поведение MSR IPv6 отличается от требований, изложенных в стандартах на IPv6. При тестировании мы выявили также дефекты, которые можно охарактеризовать как ошибки, допущенные при программировании.

    Сбор реакций целевой системы

    Сбор реакций целевой системы осуществляют специальные программы, которые называются кетчерами (от английского глагола to catch – ловить). Как было сказано выше, MSR IPv6 демонстрирует два вида отложенных реакций – исходящие IPv6 пакеты и UDP датаграммы, которые получают в сокетах. Входящие и исходящие пакеты IPv6 Кетчер для входящих/исходящих пакетов IPv6 перехватывает кадры Ethernet, в которых передаются пакеты IPv6. Кетчер реализован как модуль расширения Microsoft Network Monitor’а. Кетчер не разбирает кадры Ethernet, а передает их в тестовую систему. Заметим, что кетчер не умеет отличать исходящие кадры от входящих. В тестовой системе перехваченные кадры преобразуются. Тестовая система извлекает из кадра вложенный в него пакет IPv6 и определяет, является пакет входящим или исходящим. При этом формируется специальная структура данных, которая используется в спецификациях для представления пакетов IPv6. Входящие датаграммы UDP При старте тестовая система открывает несколько сокетов и запускает кетчер, которая слушает открытые сокеты. Когда в один из сокетов прибывает датаграмма, кетчер получает датаграмму и определяет исходный адрес датаграммы.

    Спецификации реакций

    MSR IPv6 демонстрирует два вида отложенных реакций - исходящие пакеты IPv6 и входящие пакеты UDP, то есть данные, которые получают клиенты протокола UDP. Кроме того, по техническим причинам мы моделировали входящие пакеты IPv6 также как реакции (см. обсуждение выше). Спецификация для исходящих пакетов IPv6 проверяет следующее:
  • Все пакеты, которые целевая система отправляет в сеть, удовлетворяют требованиям на формат пакетов, изложенным в соответствующих RFC.
  • Все фрагменты, которые целевая система отправляет в сеть, можно собрать в пакет IPv6
  • Корректность данных для некоторых видов пакетов (например, UDP и ICMPv6) Спецификация для входящих пакетов IPv6 разбирает входящие пакеты IPv6. Если в пакете нет ошибок, то спецификация определяет получателя данного пакета и моделирует получение данных пакета получателем. Если в пакете обнаружена ошибка, то спецификация определяет, какое сообщение об ошибке следует послать. Спецификация для входящих пакетов IPv6 также моделирует сборку пакетов из фрагментов. В спецификации восстанавливается исходный пакет и моделируется доставка восстановленного пакета получателю. Спецификация для входящих датаграм UDP проверяет, что датаграмма получена в правильном сокете и данные получены без искажений.

    Спецификации

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

    Спецификация фрагментации

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

    Спецификация Neighbor Discovery

    Подсистема Neighbor Discovery не содержит входных точек, доступных извне модуля MSR IPv6. ND обрабатывает входящие пакеты и сохраняет информацию о соседних узлах во внутренних таблицах. При отправке IPv6 пакета в сеть соответствующая подсистема IPv6 запрашивает у ND информацию об узле назначения пакета. В результате такого запроса ND может выслать один или более служебных пакетов. Спецификация для входящих пакетов распознает пакеты Neighbor Discovery и определяет, какая информация должна сохраняться в таблицах Neighbor Discovery. Спецификация для исходящих пакетов IPv6 проверяет следующее:
  • Реализация отправляет пакеты известным узлам
  • Реализация производит поиск соседа перед отправкой пакетов незнакомым соседним узлам
  • Реализация не отсылает пакеты, если Neighbor Discovery не в состоянии найти узел адресат
  • Реализация правильно определяет, какие узлы находятся на том же сегменте локальной сети, а какие находятся вне сегмента локальной сети
  • Реализация находит маршрутизаторы в локальной сети и корректно обновляет информацию о маршрутизаторах Заметим, что спецификация не старается предсказать, на какой узел локальной сети будет выслан пакет. Вместо этого спецификация проверяет, что узел, выбранный реализацией, соответствует информации, собранной Neighbor Discovery на момент отправки пакета.

    Спецификация непроцедурных стимулов

    Алгоритм обхода графа состояний автомата тестового сценария, реализованный в CTesK-lite, требует, чтобы воздействия на целевую систему оказывались только в стационарных состояниях. Протокол IPv6 не удовлетворяет указанному ограничению, так как некоторые внешние воздействия (входящие пакеты) могут оказываться в нестационарных состояниях. Например, в протоколе ICMPv6 Echo узел испускает пакет ICMPv6 Echo Request и переходит в нестационарное состояние, в котором ожидает входящего пакета ICMPv6 Echo Reply. Таким образом, внешнее воздействие – пакет ICMPv6 Echo Reply – необходимо оказать в нестационарном состоянии. Для того, чтобы обойти ограничение на стационарность начального состояния, непроцедурные стимулы были специфицированы как отложенные реакции на некоторые процедурные стимулы. Мы ввели стимул “Отправить пакет IPv6 в целевую систему”. Спецификация для данного стимула тривиальна. Медиатор для данного стимула отправляет пакет IPv6 с удаленного узла. Собственно входящий пакет рассматривается как отложенная реакция на данный стимул.

    Спецификация процедурных стимулов

    Мы специфицировали два процедурных стимула:
  • Отправить пакет UDP
  • Отправить ICMPv6 Echo Request Спецификация “Отправить пакет UDP” моделирует отправку данных через UDP сокет. У данного стимула есть следующие параметры: адрес узла назначения, адрес узла отправителя (так как узел IPv6 может иметь более одного адреса), номер порта получателя и данные, которые необходимо отправить. Спецификация утверждает, что в сеть будет отправлен пакет IPv6 с заданным исходным адресом и заданным адресом назначения, который содержит пакет UDP на указанный порт и с указанными данными. Данная спецификация нацелена на проверку функции отправки пакетов в сеть. Так как точка доступа к упомянутой функции расположена глубоко внутри модуля IPv6, то для тестирования мы воспользовались функцией-посредником. В качестве посредника был выбран протокол UDP, так как UDP (в отличие от TCP) практически не обладает собственной внутренней функциональностью, и запрос на отправку пакета UDP почти точно отображается на запрос на отправку пакета IPv6. Спецификация “Отправить ICMPv6 Echo Request” моделирует протокол ICMPv6 Echo, описанный в .У данного стимула есть следующие параметры: адрес узла назначения, адрес узла отправителя (так как узел IPv6 может иметь более одного адреса) и данные, которые необходимо отправить в пакете ICMPv6 Echo Request. Спецификация утверждает, что в сеть будет отправлен пакет IPv6 с заданным исходным адресом и заданным адресом назначения, который содержит пакет ICMPv6 Echo Request с указанными данными, и что все пакеты Echo Reply, которые придут в ответ на запрос, будут доставлены в процесс, инициировавший запрос. Спецификация “Отправить ICMPv6 Echo Request” также имеет отношение к функции отправки пакетов. Но так как у данного стимула есть сложная внутренняя функциональность, не связанная с отправкой пакетов, то тестирование отправки пакетов производилось преимущественно посредством отправки пакетов UDP.

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

    Тестовый сценарий в UniTesK определяет последовательность воздействий, которые оказываются на целевую систему. В UniTesK в качестве теоретической основы для построения тестового сценария выбрана модель конечных автоматов. Тестовый сценарий обладает собственным состоянием, которое, как правило, вычисляется на основе состояния целевой системы. В каждом состоянии задаются воздействия, которые в данном состоянии можно оказать на целевую систему. Тестовый сценарий в процессе работы обходит все состояния и в каждом состоянии оказывает все перечисленные воздействия. Тестовые сценарии На рисунке 2 изображен пример тестового сценария, в котором насчитывается три состояния (S1, S2 и S3) и семь воздействий (I1_1, I1_2, I2_1, I2_2, I3_1, I3_2, I3_3). При тестировании тестовый сценарий побывает в каждом состоянии и пройдет по каждой дуге. К сожалению, для большинства целевых систем невозможно получить описание тестового сценария из спецификаций полностью автоматически, без помощи человека. Можно указать следующие причины:
  • Число состояний целевой системы, как правило, очень велико.
  • Число воздействий, которые можно оказать на целевую систему в каждом состояний, как правило, очень велико. При всем при том, число групп разных состояний, как правило, вполне обозримо. Но критерий различения состояний автоматически не определить, здесь нужна помощь человека – разработчика тестов. Число различных воздействий на целевую систему также много меньше общего числа воздействий. Из формальных спецификаций можно автоматически получить критерий различения воздействий, но автоматически построить воздействия можно только в простейших случаях. В UniTesK реализован компромиссный подход к разработке тестовых сценариев. Разработчик тестового сценария пишет процедуру различения состояний целевой системы, тем самым определяя классы эквивалентности состояний целевой системы. Каждый класс эквивалентности определяет одно состояние автомата тестового сценария. Разработчик тестового сценария также задает процедуру, которая строит всевозможные тестовые воздействия, то есть переходы автомата тестового сценария. Эти воздействия фильтруются в соответствии с одним из сгенерированных критериев покрытия. Фильтры отсеивают избыточные воздействия, то есть воздействия, которые не улучшают уже достигнутое покрытие. Фильтрация воздействий существенно упрощает написание процедур перебора воздействий. По предоставленным описаниям динамически строится граф состояний автомата тестового сценария. При обходе графа автоматически отслеживается покрытие требований, описанных в спецификациях для генерации отчета о покрытии.
    В ходе проекта были разработаны 15 тестовых сценариев. Приблизительно тестовые сценарии можно разделить на 4 группы:
  • Тесты на отправку пакетов. Тесты из данной группы проверяют, как целевая система передает пакеты верхнего уровня в сеть. Также производится проверка того, как реализация проводит нарезку больших пакетов на фрагменты.
  • Тесты на получение пакетов. Тесты из данной группы проверяют, как целевая система обрабатывает различные входящие пакеты. Тестируются функция сборки фрагментов и доставка пакетов протоколам верхнего уровня. Значительное место занимает проверка того, как система обрабатывает поданные на вход некорректные пакеты.
  • Тесты для функции ICMPv6 Echo. Тесты из данной группы проверяют корректность реализации простого диагностического протокола Echo, встроенного в ICMPv6.
  • Тесты для Neighbor Discovery. Тесты из данной группы проверяют, что реализация Neighbor Discovery соответствует требованиям, изложенным в RFC 2461 . Заметим, что представленное разделение нестрогое, и отдельные тестовые сценарии можно отнести сразу к нескольким группам. Тестовые сценарии для MSR IPv6 осуществляют два вида тестирования:
  • тестовые сценарии проверяют корректность работы целевой системы на корректных входах.
  • тестовые сценарии проверяют устойчивость целевой системы по отношению к ошибкам во входных пакетах IPv6.

    Транспорт стимулов и реакций

    В данном разделе описывается, как тестовая система оказывает воздействие на целевую систему (MSR IPv6) и собирает реакции целевой системы.

    В статье представлен опыт разработки

    В статье представлен опыт разработки тестового набора для реализации протокола IPv6 на Windows 2000. Тестовый набор предназначался для проверки соответствия реализации протокола IPv6 спецификациям IPv6. Проект по разработке тестового набора проходил при поддержке исследовательского гранта Microsoft Research. Разработка тестового набора проводилась с использованием методологии тестирования UniTesK, которая разработана и развивается в Институте системного программирования РАН. В качестве объекта тестирования была выбрана MSR IPv6 – реализация IPv6, разработанная в Microsoft Research. Перед тестовым набором стояла задача проверить, насколько реализация протокола соответствует стандарту протокола. Кроме того, перед разработчиками тестового набора были поставлены следующие цели:
  • Продемонстрировать применимость формальных методов для специфицирования и тестирования сложного API, такого как реализация IPv6
  • Исследовать применимость UniTesK к специфицированию и тестированию внутренних подсистем Windows NT или приложений для Windows NT
  • Исследовать применимость UniTesK к тестированию индустриального программного обеспечения Статья построена следующим образом. содержит краткое введение в IPv6 и описание функций IPv6, которые мы тестировали. В указаны общие сведения о MSR IPv6 и обсуждаются особенности MSR IPv6 в контексте тестирования. В дано общее описание метода разработки тестовых наборов UniTesK. посвящен применению UniTesK для тестирования систем с отложенными реакциями. В дано описание устройства тестового набора для MSR IPv6, в перечисляются результаты проекта по разработке тестового набора. – Заключение.

    Введение в IPv

    IPv6 – сетевой протокол нового поколения. Призван заменить существующий протокол сетевого уровня IPv4. IPv6 содержит ряд усовершенствований по сравнению с IPv4 и разрешает проблемы, которые ограничивают дальнейшее развитие сетей на IPv4. По эталонной модели OSI протокол IPv6 относится к протоколам сетевого уровня. На этом уровне происходит маршрутизация пакетов на основе преобразования сетевых адресов в адреса канального уровня. Сетевой уровень обеспечивает прозрачную передачу данных между транспортным уровнем и канальным уровнем.

    Введение в UniTesK

    Данный раздел содержит очень поверхностное введение в UniTesK. Более подробное описание можно найти в С 1994 года в ИСП РАН активно разрабатываются методы и инструменты тестирования программного обеспечения на основе формальных методов. В 1994-1999 годах по контрактам с Nortel Networks в ИСП РАН был разработан и активно использовался метод тестирования KVEST. KVEST отличался от обычных методов тестирования индустриального программного обеспечения тем, что в KVEST использовались формальные спецификации в форме пред- и постусловий для построения тестовых оракулов, а также модель конечных автоматов для построения тестовых воздействий. Применение KVEST показало, что формальные методы можно с большим успехом применять при тестировании индустриального программного обеспечения. Опыт применения KVEST показал, что использование академических языков формальных спецификаций и специальных языков описания тестов препятствует встраиванию инструментов, основанных на этих языках, в процесс разработки ПО. Чем ближе язык спецификаций к языку, на котором ведется разработка, тем проще разработчикам писать спецификации и тесты . UniTesK разрабатывался на основе опыта, полученного при разработке и применении KVEST. Рассмотрим основные особенности UniTesK:
  • Разделение построения тестовых воздействий и проверки правильности поведения целевой системы. Тестовые воздействия строятся в тестовых сценариях, а проверка правильности поведения целевой системы в тестовых оракулах.
  • Автоматизированное построение тестовых воздействий
  • Представление функциональных требований к целевой системе в виде формальных спецификаций
  • Для записи формальных спецификаций используется язык, “близкий” к языку, на котором разработана целевая система
  • Автоматическая генерация тестовых оракулов из спецификаций
  • Оракулы и реализация связаны посредством тонкой прослойки медиаторов.
  • Язык описания тестовых воздействий “близок” к языку, на котором разработана целевая система
  • Автоматически генерируются критерии качества покрытия требований
  • Автоматически производится оценка качества покрытия требований при прогоне тестов

    Выявленные дефекты

    При тестировании мы обнаружили, что в реализации алгоритма сборки фрагментов в MSR IPv6 присутствует ошибка. При получении последовательности фрагментов определенного вида в модуле MSR IPv6 возникает исключительная ситуация, которая приводит к краху ядра операционной системы и перезагрузке Windows 2000. В MSR IPv6 некорректно спроектирован интерфейс доступа к функции ICMPv6 Echo Request. Клиентский процесс, инициировавший запрос, блокируется до прибытия первого ответа (Echo Reply) или до истечения таймаута. Если на запрос поступили ответы, то первый ответ возвращается клиенту, а все остальные отбрасываются. Такое поведение не соответствует требованию передавать клиенту все поступившие ответы . В одном частном случае реализация алгоритма сборки фрагментов в MSR IPv6 нарушает одно из требований, изложенное в RFC 2460 . RFC требует, чтобы служебная информация, которая передается во фрагментах, не попадала в восстановленный пакет. Тестирование показало, что для фрагментов определенного вида MSR IPv6 оставляет служебную информацию в восстановленном пакете. Более подробное описание выявленных дефектов можно найти в .

    Проект по разработке тестового набора

    Проект по разработке тестового набора для MSR IPv6 продемонстрировал применимость UniTesK для тестирования сложного API на платформе Win32. UniTesK был использован для тестирования системы, расположенной в ядре Windows 2000. Интерфейс целевой системы содержит непроцедурные стимулы и отложенные реакции. Были выявлены ошибки в системе, которая уже несколько лет находилась в эксплуатации и проходила испытания другими тестовыми наборами.


    Тестирование софта - статьи

    Аннотация.

    Контрактные спецификации в форме пред- и постусловий широко используются в программной инженерии для формального описания интерфейсов программных компонентов. Такие спецификации, с одной стороны, удобны для разработчиков, поскольку хорошо привязываются к архитектуре системы, с другой стороны, на их основе можно автоматически генерировать тестовые оракулы, проверяющие соответствие поведения целевой системы требованиям, описанным в спецификациях. В работе предлагается использовать контрактные спецификации для автоматизации функционального тестирования моделей аппаратного обеспечения, разработанных на таких языках, как VHDL, Verilog, SystemC, SystemVerilog и др. В статье подробно описаны особенности спецификации аппаратного обеспечения, приводится сравнение предлагаемого подхода с существующими методами спецификации, применяемыми в тестировании аппаратуры. В качестве базового подхода используется технология тестирования UniTESK, разработанная в Институте системного программирования РАН.

    Архитектура тестовой системы UniTESK

    Архитектура тестовой системы UniTESK [] была разработана на основе многолетнего опыта тестирования промышленного программного обеспечения из разных предметных областей и разной степени сложности. Учет этого опыта позволил создать гибкую архитектуру, основанную на следующем разделении задачи тестирования на подзадачи:
  • построение тестовой последовательности, нацеленной на достижение нужного покрытия;
  • создание единичного тестового воздействия в рамках тестовой последовательности;
  • установление связи между тестовой системой и реализацией целевой системы;
  • проверка правильности поведения целевой системы в ответ на единичное тестовое воздействие. Для решения каждой из этих подзадач предусмотрены специальные компоненты тестовой системы (Рис. 3): для построения тестовой последовательности и создания единичных тестовых воздействий - обходчик и итератор тестовых воздействий; для проверки правильности поведения целевой системы - тестовый оракул; для установления связи между тестовой системой и реализацией целевой системы - медиатор. Рассмотрим подробнее каждый из указанных компонентов. Архитектура тестовой системы UniTESK Рис. 3.Архитектура тестовой системы UniTESK. Обходчик является библиотечным компонентом тестовой системы UniTESK и предназначен вместе с итератором тестовых воздействий для построения тестовой последовательности. В основе обходчика лежит алгоритм обхода графа состояний обобщенной конечно-автоматной модели целевой системы (конечного автомата, моделирующего целевую систему на некотором уровне абстракции). Обходчики, реализованные в библиотеках инструментов UniTESK, требуют, чтобы обобщенная конечно-автоматная модель целевой системы, была детерминированной и имела сильно-связный граф состояний. Итератор тестовых воздействий работает под управлением обходчика и предназначен для перебора в каждом достижимом состоянии конечного автомата допустимых тестовых воздействий. Итератор тестовых воздействий автоматически генерируется из тестового сценария, представляющего собой неявное описание обобщенной конечно-автоматной модели целевой системы. Тестовый оракул оценивает правильность поведения целевой системы в ответ на единичное тестовое воздействие. Он автоматически генерируется на основе формальных спецификаций, описывающих требования к целевой системе в виде пред- и постусловий интерфейсных операций и инвариантов типов данных. Медиатор связывает абстрактные формальные спецификации, описывающие требования к целевой системе, с конкретной реализацией целевой системы. Медиатор преобразует единичное тестовое воздействие из спецификационного представления в реализационное, а полученную в ответ реакцию - из реализационного представления в спецификационное. Также медиатор синхронизирует состояние спецификации с состоянием целевой системы. Трасса теста отражает события, происходящие в процессе тестирования. На основе трассы можно автоматически генерировать различные отчеты, помогающие анализировать результаты тестирования.

    Инструмент разработки тестов CTESK

    Инструмент CTESK [], который используется в описываемой работе, является реализацией концепции UniTESK для языка программирования C. Для разработки компонентов тестовой системы в нем используется язык SeC (specification extension of C), являющийся расширением ANSI C. Инструмент CTESK включает в себя транслятор из языка SeC в C, библиотеку поддержки тестовой системы, библиотеку спецификационных типов и генераторы отчетов. Компоненты тестовой системы UniTESK реализуются в инструменте CTESK с помощью специальных функций языка SeC, к которым относятся:
  • спецификационные функции - содержат спецификацию непосредственной реакции целевой системы в ответ на единичное тестовое воздействие, а также определение структуры тестового покрытия;
  • функции отложенных реакций - содержат спецификацию отложенных реакций целевой системы;
  • медиаторные функции - связывают спецификационные функции с тестовыми воздействиями на целевую систему, а также реакции целевой системы с функциями отложенных реакций;
  • функция вычисления обобщенного состояния - вычисляет состояние обобщенной конечно-автоматной модели целевой системы;
  • сценарные функции - описывают набор тестовых воздействий для каждого достижимого обобщенного состояния. В работах [, ] подробно описано, как базовая архитектура тестовой системы UniTESK может быть расширена для функционального тестирования моделей аппаратного обеспечения, разработанных на языках Verilog и SystemC. Там же приводятся технические детали, связанные с использованием инструмента CTESK для функционального тестирования таких моделей.

    Использование CTESK для спецификации аппаратуры

    Инструмент разработки тестов CTESK предоставляет достаточно универсальные средства для спецификации систем с асинхронным интерфейсом []. Эти средства были адаптированы для спецификации модулей аппаратного обеспечения. Рассмотрим подробнее процесс разработки спецификаций. Для каждой операции, реализуемой модулем, пишется спецификационная функция, в которой определяется предусловие операции и структура тестового покрытия. Постусловие спецификационной функции обычно возвращает true, поскольку все проверки, как правило, определяются в постусловиях микроопераций: // спецификация операции specification void operation_spec(...) { // предусловие операции pre { ... } // определение структуры тестового покрытия coverage C { ... } // постусловие операции обычно возвращает true post { return true; } } Для каждой микрооперации, входящей в состав специфицируемой операции, пишется функция отложенной реакции, в которой определяется ее постусловие: // спецификация микрооперации reaction Operation* micro_return(void) { // постусловие микрооперации\ post { ... } } Далее определяется функция временнóй композиции микроопераций, которая, во-первых, добавляет стимул (операцию вместе с набором аргументов) в очередь стимулов с указанием времени, необходимого для обработки стимула (time), во-вторых, для каждой микрооперации добавляет соответствующую реакцию в очередь реакций с указанием номера такта (относительно текущего времени), в конце которого следует осуществить проверку реакции (ticki): // временная композиция микроопераций void operation_time_comp(...) { Operation *descriptor = create_operation(...); // добавление стимула в очередь стимулов register_stimulus(create_stimulus(time, descriptor))); // добавление реакций в очередь реакций register_reaction(micro1_return, tick1, descriptor); ... register_reaction(micron_return, tickn, descriptor); } Очередь стимулов содержит стимулы, обрабатываемые модулем в текущее время. Для каждого стимула в очереди хранится время, которое он уже обрабатывается.
    Очередь реакций содержит еще не завершенные микрооперации. Для каждой микрооперации хранится время, через которое микрооперация завершится и можно будет осуществить проверку реакции. Изменение времени осуществляется функцией сдвига времени. После изменения времени вызываются функции обработки очередей стимулов и реакций. Функция обработки очереди стимулов удаляет из очереди полностью обработанные стимулы. Функция обработки очереди реакций регистрирует отложенные реакции для всех завершившихся микроопераций, которые после этого удаляются из очереди. Для иллюстрации синтаксиса языка SeC приведем очень простой пример. Рассмотрим устройство, называемое 8-ми битным счетчиком (Рис. 4). Использование CTESK для спецификации аппаратуры Рис. 4.Схема входов и выходов 8-ми битного счетчика. Интерфейс счетчика состоит из двух двоичных входов clk и rst и одного 8-ми битного выходного регистра cnt. Если уровень сигнала rst низкий, фронт сигнала тактового импульса clk увеличивает счетчик cnt по модулю 256; иначе счетчику присваивается значение 0. Ниже приводится спецификационная функция, описывающая операцию увеличения счетчика, то есть поведение счетчика в ответ на фронт clk при низком уровне сигнала rst. Поскольку операция является простой, спецификация выполнена без привлечения функций отложенных реакций. // спецификация операции увеличения счетчика specification void increment_spec(counter_8bit *counter) updates cnt = counter->cnt, rst = counter->rst { // предусловие операции pre { return rst == false; } // определение структуры тестового покрытия coverage C { return { SingleBranch, "Single branch" }; } // постусловие операции post { return cnt == (@cnt + 1) % 0xff; } }

    Mодели аппаратного обеспечения

    Перед тем как описывать предлагаемый подход, рассмотрим особенности моделей аппаратного обеспечения на таких языках, как VHDL [], Verilog [], SystemC [], SystemVerilog [] и др. Знание этих особенностей позволяет адекватно адаптировать контрактные спецификации в форме пред- и постусловий для функционального тестирования моделей аппаратного обеспечения.

    Опыт практического применения подхода

    Предлагаемый подход был применен на практике при тестировании буфера трансляции адресов (TLB, translation lookaside buffer) микропроцессора c MIPS64-совместимой архитектурой [, ]. Буфер трансляции адресов, входящий в состав большинства современных микропроцессоров, предназначен для кэширования таблицы страниц - таблицы операционной системы, хранящей соответствие между номерами виртуальных и физических страниц памяти. Использование такого буфера позволяет значительно увеличить скорость трансляции адресов. Буфер представляет собой ассоциативную память с фиксированным числом записей. Помимо интерфейса для трансляции адресов, он предоставляет интерфейс для чтения и изменения содержимого этой памяти. Трансляция виртуального адреса осуществляется следующим образом. Если буфер содержит запись с нужным номером виртуальной страницы, в выходном регистре модуля формируется соответствующий физический адрес; в противном случае, на одном из выходов модуля устанавливается сигнал, говорящий об отсутствии в буфере требуемой записи.

    Особенности моделей аппаратного обеспечения

    Модели аппаратного обеспечения представляют собой системы из нескольких взаимодействующих модулей. Как и в языках программирования, модули используются для декомпозиции сложной системы на множество независимых или слабо связанных подсистем. У каждого модуля имеется интерфейс - набор входов и выходов, через которые осуществляется соединение модуля с окружением, и реализация, определяющая способ обработки модулем входных сигналов: вычисление значений выходных сигналов и изменение внутреннего состояния. Обработка модулем входных сигналов инициируется событиями со стороны окружения. Под событиями в моделях аппаратного обеспечения понимаются любые изменения уровней сигналов. Поскольку обычно рассматриваются двоичные сигналы, выделяются два основных вида событий: фронт сигнала (posedge, positive edge) - изменение уровня сигнала с низкого на высокий - и срез сигнала (negedge, negative edge) - изменение уровня сигнала с высокого на низкий . Как правило, каждый модуль состоит из нескольких статически созданных параллельных процессов , каждый из которых реализует следующий цикл: сначала осуществляется ожидание одного или нескольких событий из заданного набора событий, затем производится их обработка, после чего цикл повторяется. Набор событий, ожидаемых процессом для обработки, называется списком чувствительности (sensitive list) процесса. Будем называть процесс пассивным, если он находится в состоянии ожидания событий, и активным в противном случае. Важной особенностью моделей аппаратного обеспечения является наличие в них понятия времени. Время моделируется целочисленной величиной; можно задавать физический смысл единицы времени. Для описания причинно-следственных отношений между событиями, происходящими в одну единицу модельного времени используется понятие дельта-задержки (delta delay). События, между которыми есть дельта-задержка, выполняются последовательно одно за другим, но в одну и ту же единицу модельного времени. Для выполнения моделей аппаратного обеспечения с целью анализа их поведения обычно используется симуляция по событиям (event-driven simulation).
    В отличие от симуляции по интервалам времени (time-driven simulation), в которой значения сигналов и внутренние состояния модулей вычисляются через регулярные интервалы времени, в этом способе модель рассматривается только в те моменты времени, когда происходят некоторые события. Работа событийного симулятора (event-driven simulator) состоит в следующем. В начале симуляции модельное время устанавливается в ноль. Далее в цикле, пока есть активные процессы , выбирается один из них и выполняется до тех пор, пока он не станет пассивным. После того как выполнены все активные процессы, симулятор проверяет, есть ли события, запланированные на текущий момент времени через дельта-задержку или на будущие моменты времени. Если такие события есть, симулятор изменяет модельное время на время ближайшего события, реализует события, запланированные на этот момент времени, перевычисляет множество активных процессов, после чего цикл повторяется. Если таких событий нет, симуляция заканчивается.

    Проверка требований

    После того как требования к модулю формализованы, проверка поведения модуля на соответствие им может осуществляться в процессе тестирования автоматически. Предположим, что в некоторый момент времени t тестируемый модуль выполняет m операций f1, …, fm, которые были поданы на выполнение раньше на τ1, …, τm соответственно (τi≥ 1, i=1, …, m). Пусть C1, …, Cm - контракты операций f1, …, fm соответственно, и в моменты подачи операций f1, …, fm были выполнены предусловия preC1, …, preCm; тогда для проверки правильности поведения модуля в момент времени t необходимо проверить выполнимость предиката PostC1(τ1) Проверка требованийПроверка требований PostCm(τm). Понятно, что для проверки соответствия поведения модуля требованиям также важно уметь строить хорошие тестовые последовательности, но рассмотрение этого вопроса выходит за рамки данной работы.

    Разработка спецификаций модуля

    Основные требования к буферу трансляции адресов были получены в письменной форме от разработчиков модуля. В процессе формализации требования уточнялись в результате общения с разработчиками и чтения технической документации. Следует отметить, что все сформулированные разработчиками требования были легко представлены в форме пред- и постусловий. Проект продемонстрировал удобство и сравнительно небольшую трудоемкость разработки контрактных спецификаций для моделей аппаратного обеспечения. Спецификации были разработаны одним человеком за ≈2 недели, а их объем составил ≈2 500 строк кода на SeC. Отметим также, что в результате проекта было найдено несколько ошибок в реализации модуля.

    Спецификация и проверка требований

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

    Спецификация требований

    Предлагаемый подход к представлению требований основан на использовании контрактных спецификаций в форме пред- и постусловий. В отличие от классического Design-by-Contract, когда контракты определяются на уровне операций, мы предлагаем определять контракты для отдельных микроопераций, а контракт для операции в целом получать путем временнóй композиции контрактов отдельных микроопераций (Рис. 2.). Спецификация требований Рис. 2.Построение спецификации отдельной операции. Здесь под микрооперациями мы понимаем некоторым образом выделенные аспекты функциональности операций, реализуемые за один такт работы модуля, а под временнóй композицией контрактов - спецификацию, в которой для каждой микрооперации указан номер такта, в конце которого должен выполняться соответствующий контракт. В общих чертах процесс спецификации требований к отдельной операции состоит в следующем. Определяется предусловие, ограничивающее ситуации, в которых операцию можно подавать на выполнение. На основе анализа документации производится функциональная декомпозиция операции на набор микроопераций. Для каждой микрооперации определяется постусловие, описывающее требования к ней. После этого производится временнáя композиция спецификаций - постусловие каждой микрооперации помечается номером такта, в конце которого оно должно быть выполнено. Таким образом, контракт операции f, для которой выделено n микроопераций, формализуется структурой C=(pre, {(posti, τi)i=1,n}) . Для контракта С=(pre, {(posti, τi)i=1,n}) введем следующие обозначения. Через preC будем обозначать предусловие операции (pre), через postC,i - постусловие i-ой микрооперации (posti), через τC,i - номер такта, в конце которого должно выполняться постусловие i-ой микрооперации (τi), через PostC(τ) - конъюнкцию постусловий микроопераций, помеченных тактом τ, то есть Спецификация требований{ postC,i | τC,i=τ }.

    Сравнение с существующими подходами

    В данном разделе приводится сравнение предлагаемого подхода с существующими методами спецификации аппаратуры, поддерживаемыми современными языками верификации аппаратуры (HVL, hardware verification languages). Языки верификации аппаратуры, к которым относятся PSL, OpenVera, SystemVerilog и др. [], включают в себя конструкции языков описания аппаратуры, языков программирования, а также специальные средства, ориентированные на разработку спецификаций и тестов. К последним относятся средства спецификации поведения, определения структуры тестового покрытия и генерации тестовых данных. Мы сравниваем только способы спецификации. Средства спецификации поведения, используемые в современных языках верификации аппаратуры, базируются на темпоральной логике линейного времени (LTL, linear temporal logic) и/или темпоральной логике ветвящегося времени (CTL, computation tree logic) []. Языки, по крайней мере, по части спецификации, имеют следующие корни: ForSpec (Intel) [] (для языков, использующих логику LTL ) и Sugar (IBM) [] (для языков, использующих логику CTL). Ниже приведена диаграмма, показывающая влияние некоторых языков верификации аппаратуры друг на друга. Логика CTL используется преимущественно для формальной верификации систем. Для целей симуляции и тестирования больший интерес представляет логика линейного времени LTL. Поскольку все языки верификации аппаратуры, в которых используется LTL, имеют схожие средства спецификации, для сравнения с предлагаемым подходом мы будем использовать только один из них - OpenVera []. Данный язык поддерживается многими инструментами; кроме того, он является открытым. Сравнение с существующими подходами Рис. 5. Влияние языков верификации аппаратуры друг на друга. Язык OpenVera был разработан в 1995 году компанией Systems Science. Первоначальное название языка - Vera. В 1998 году System Science была поглощена компанией Synopsys. В 2001 году Synopsys сделала язык открытым и переименовала его в OpenVera. Для спецификации поведения OpenVera предоставляет специальный язык формулирования темпоральных утверждений (temporal assertions), который называется OVA (OpenVera assertions) [, ]. OVA оперирует с ограниченными по времени последовательностями событий, в которых можно обращаться к прошлому и будущему.
    Из простых последовательностей можно строить более сложные с помощью логических связок, таких как AND и OR, или используя регулярные выражения. В языке имеются средства объединения темпоральных утверждений в параметризованные библиотеки спецификаций. Проиллюстрируем синтаксис OVA на простом примере. // тактовый сигнал clock negedge(clk) { // граничные значения счетчика bool cnt_00: (cnt == 8'h00); bool cnt_ff: (cnt == 8'hff); // событие переполнения счетчика event e_overflow: cnt_ff #1 cnt_00; } // утверждение, запрещающее переполнение счетчика assert a_overflow: forbid(e_overflow); В примере определяется событие переполнения счетчика e_overflow, а утверждение a_overflow запрещает возникновение такого события. В подходе OpenVera, как и в других подходах на основе темпоральных логик, упор делается на временнýю декомпозицию операций. Для каждой операции сначала выделяется ее временнáя структура - допустимые последовательности событий и задержки между ними; затем определяются предикаты, описывающие отдельные события; после этого предикаты, относящиеся к одному моменту времени, некоторым образом группируются (Рис. 6). Сравнение с существующими подходами Рис. 6. Построения спецификации в подходах на основе темпоральных логик. В подходе, предлагаемом нами, основной акцент ставится на функциональную декомпозицию операций. Первым делом выделяется функциональная структура операции - набор микроопераций; каждая микрооперация специфицируется; после этого производится временнáя композиция спецификаций (). Мы полагаем, что функциональная структура операции более устойчива по сравнению с временнóй. Тем самым, подходы, основанные на функциональной декомпозиции операций, позволяют разрабатывать спецификации, более устойчивые к изменениям реализации по сравнению с подходами на основе временнóй декомпозиции. К достоинствам предлагаемого подхода также можно отнести наглядность и простоту. Пред- и постусловия обычно понятнее формул темпоральной логики и не требуют от разработчика тестов каких-нибудь специальных знаний.

    Структура и функциональность модуля

    Рассмотрим устройство тестируемого модуля. Память TLB состоит из 64 ячеек, которые составляют объединенный TLB (JTLB, joint TLB). Кроме того, для повышения производительности модуль содержит дополнительные буферы: TLB данных (DTLB, data TLB) и TLB инструкций (ITLB, instruction TLB). DTLB используется при трансляции адресов данных, ITLB - при трансляции адресов инструкций. Оба буфера содержат по 4 ячейки, содержимое буферов является подмножеством JTLB, обновление происходит по алгоритму LRU (last recently used). Каждая ячейка TLB условно делится на две секции: секция-ключ и секция-значение. Секция-ключ включает в себя спецификатор сегмента памяти (R), номер виртуальной страницы, деленный на два (VPN2), идентификатор процесса (ASID), бит глобальной трансляции адресов (G) и маску страницы (MASK). Секция-значение состоит из двух подсекций, каждая из которых содержит номер физической страницы (PFNi), бит разрешения чтения (Vi), бит разрешения записи (Di) и политику кэширования страницы (Ci). Какая именно подсекция будет использована при трансляции адреса, определяется младшим битом номера виртуальной страницы. Интерфейс тестируемого модуля TLB состоит из 30 входов (16 входов общего назначения, 3 входа DTLB, 4 входа ITLB, 7 входов JTLB) и 31 выходов (6 выходов общего назначения, 9 выходов DTLB, 8 выходов ITLB, 8 выходов JTLB) . Функциональность модуля включает операции записи, чтения и проверки наличия ячейки в памяти, а также операции трансляции адресов данных и инструкций. RTL-модель модуля разработана на языке Verilog и составляет ≈ 8 000 строк кода. Структура и функциональность модуля Рис. 7.Структура ячейки буфера трансляции адресов.

    Технология тестирования UniTESK

    В качестве базового подхода в работе используется технология тестирования UniTESK [], разработанная в Институте системного программирования РАН []. Характерными чертами технологии являются использование контрактных спецификаций в форме пред- и постусловий интерфейсных операций и инвариантов типов данных для спецификации требований, а также применение обобщенных конечно-автоматных моделей для построения тестовых последовательностей.

    Типичная организация модулей аппаратного обеспечения

    В дальнейшем будем считать, что спецификация и тестирование моделей аппаратного обеспечения осуществляется на уровне отдельных модулей. В типичном случае работа модуля аппаратного обеспечения управляется сигналом тактового импульса, который для краткости будем называть тактовым сигналом или просто часами. Фронты (или срезы) тактового сигнала разбивают непрерывное время на дискретный набор интервалов, называемых тактами. Поведение модуля на текущем такте определяется значениями входных сигналов и внутренним состоянием модуля. Как правило, часть входов модуля определяет операцию, которую модулю следует выполнить; такие входы будем называть управляющими (control). Другая часть входов определяет аргументы операции; такие входы будем называть информационными (informative). Среди операций, реализуемых модулем, обычно присутствует специальная операция NOP (no operation), означающая бездействие модуля. Модули аппаратного обеспечения могут быть организованы разными способами. В соответствии с длительностью операций, выполняемых модулем, эти операции бывают однотактными и многотактными. По способу организации выполнения операций модули делятся на модули с поочередным выполнением операций, модули с конвейерным выполнением операций и модули с параллельным выполнением операций. Рассмотрим, как осуществляется выполнение модулем однотактной операции. До начала очередного такта окружение устанавливает на соответствующих входах модуля код операции и аргументы. Выполнение операции начинается модулем с началом такта. За этот такт модуль производит необходимые вычисления, изменяет внутреннее состояние и устанавливает значения выходных сигналов, которые окружение может использовать, начиная со следующего такта (Рис. 1). Типичная организация модулей аппаратного обеспечения Рис. 1.Временнáя диаграмма сигналов для однотактной операции. В отличие от однотактной операции, результат многотактной операции вычисляется постепенно, такт за тактом. Пусть операция f выполняется модулем за n тактов, тогда на каждом такте τТипичная организация модулей аппаратного обеспечения{1, ..., n} модуль выполняет некоторую микрооперацию fτ, а окружение после окончания каждого такта получает некоторый частичный результат.
    Представление многотактовой операции f в виде последовательности микроопераций (f1, …, fn) будем называть временнó й декомпозицией f. Теперь несколько слов о способах организации выполнения операций. В модулях с поочередным выполнением операций, как видно из названия, очередную операцию можно подавать на выполнение только после того, как полностью завершена предыдущая. В модулях с конвейерным выполнением операций операции можно подавать последовательно друг за другом, не дожидаясь завершения предыдущей операции. В модулях с параллельным выполнением операций несколько операций можно подавать одновременно. Модули с поочередным и конвейерным выполнением операций объединим общим термином - модули с последовательным выполнением операций, поскольку и в том, и в другом случае операции подаются на выполнение последовательно одна за другой. В дальнейшем будем считать, что модули организованы таким образом, что одновременно выполняемые операции не вступают в конфликты (hazards), то есть не влияют друг на друга. Если взаимное влияние все-таки возможно, требования должны описывать, как подавать операции на выполнение, чтобы избежать возникновения конфликтов.

    Требования к модулям аппаратного обеспечения

    В общем случае операции являются многотактными, то есть выполняются модулем за несколько тактов. Требования на такие операции бывают двух основных типов: требования на операцию в целом, которые не накладывают ограничений на то, на каком именно такте выполняется та или иная микрооперация, и требования на временнýю композицию операции, в которых фиксируется, на каких тактах выполняются конкретные микрооперации. Требования на операцию в целом допускают определенную свободу в реализации модуля. Не важно, на каком такте выполняется некоторая микрооперация, важно, чтобы после завершения всей операции результат этой микрооперации был доступен окружению. В процессе тестирования требования на операцию в целом проверяются после завершения операции. Требования на временнýю композицию операции являются более жесткими. В них указаны такты, на которых выполняются микрооперации. Обычно при тестировании имеет смысл проверять не то, что микрооперация была выполнена на определенном такте τ0, а то, что в конце этого такта соответствующим выходам модуля были присвоены требуемые значения, неважно на каком именно такте τ Требования к модулям аппаратного обеспечения{1, …, τ0}. При такой трактовке требования на операцию в целом являются частным случаем требований на временнýю композицию; поэтому в дальнейшем мы не будем различать эти типы требований - просто будем считать, что каждому требованию соответствует номер такта, в конце которого его следует проверять.

    Современный мир не мыслим без

    Современный мир не мыслим без огромного разнообразия электронных устройств. Мобильные телефоны, цифровые фотокамеры и переносные компьютеры давно стали неотъемлемыми атрибутами жизни человека. Специальные устройства управляют работой бытовой техники, контролируют бортовые системы самолетов и космических спутников, управляют медицинскими системами жизнеобеспечения. В основе практически всех этих систем лежит полупроводниковая аппаратура - кристаллы интегральных схем, состоящие из миллионов связанных друг с другом микроскопических транзисторов, которые, пропуская через себя электрические токи, реализуют требуемые функции. Чтобы убедиться, что аппаратура работает правильно, то есть реализует именно те функции, которые от нее ожидают пользователи, на практике используют функциональное тестирование. Требования, предъявляемые к качеству тестирования аппаратного обеспечения, очень высоки. Это связано не только с тем, что аппаратура лежит в основе всех информационных и управляющих вычислительных систем, в том числе достаточно критичных к сбоям и ошибкам. Большое влияние на формирование высоких требований оказывают также экономические факторы. В отличие от программного обеспечения, в котором исправление ошибки стоит сравнительно дешево, ошибка в аппаратном обеспечении, обнаруженная несвоевременно, может потребовать перевыпуск и замену продукции, а это сопряжено с очень высокими затратами. Так, известная ошибка в реализации инструкции FDIV микропроцессора Pentium [], заключающаяся в неправильном делении некоторых чисел с плавающей точкой, обошлась компании Intel в 475 миллионов долларов [, ]. С другой стороны, требования к срокам тестирования также очень высоки. Важно не затягивать процесс и выпустить продукт на рынок своевременно, пока он не потерял актуальность, и на него существует спрос. Как разработать качественный продукт своевременно, используя ограниченные ресурсы? В настоящее время для проектирования аппаратного обеспечения используются языки моделирования высокого уровня, которые позволяют значительно ускорить процесс разработки за счет автоматической трансляции описания аппаратуры на уровне регистровых передач (RTL, register transfer level) в описание аппаратуры на уровне логических вентилей (gate level).
    Такие языки называются языками описания аппаратуры (HDL, hardware description languages), а модели, построенные на их основе - HDL-моделями или RTL-моделями . Языки описания аппаратуры позволяют значительно повысить продуктивность разработки аппаратного обеспечения, но они не страхуют от всех ошибок, поэтому функциональное тестирование по-прежнему остается актуальной и востребованной задачей. При современной сложности аппаратного обеспечения невозможно разработать приемлемый набор тестов вручную за разумное время. Необходимы технологии автоматизированной разработки тестов. В настоящее время разработка таких технологий и поддерживающих их инструментов выделилась в отдельную ветвь автоматизации проектирования электроники (EDA, electronic design automation) - автоматизацию тестирования (testbench automation). Основной задачей тестирования является проверка соответствия поведения системы предъявляемым к ней требованиям. Для возможности автоматизации такой проверки требования к системе должны быть представлены в форме, допускающей автоматическую обработку. Такую форму представления требований называют формальными спецификациями или просто спецификациями. В работе рассматривается определенный вид спецификаций - контрактные спецификации (contract specifications). Контрактные спецификации и процесс проектирования на их основе (DbC, Design-by-Contract) были введены Бертраном Майером (Bertrand Meyer) в 1986 году в контексте разработки программного обеспечения [, ]. Центральная метафора подхода заимствована из бизнеса. Компоненты системы взаимодействуют друг с другом на основе взаимных обязательств (obligations) и выгод (benefits). Если компонент предоставляет окружению некоторую функциональность, он может наложить предусловие (precondition) на ее использование, которое определяет обязательство для клиентских компонентов и выгоду для него. Компонент также может гарантировать выполнение некоторого действия с помощью постусловия (postcondition), которое определяет обязательство для него и выгоду для клиентских компонентов. Почему в своих исследованиях мы выбрали именно контрактные спецификации? Контрактные спецификации, с одной стороны, достаточно удобны для разработчиков, поскольку хорошо привязываются к архитектуре системы, с другой стороны, в силу своего представления стимулируют усилия по созданию независимых от реализации критериев корректности целевой системы [].Основное же их преимущество состоит в том, что они позволяют автоматически строить тестовые оракулы, проверяющие соответствие поведения целевой системы требованиям, описанным в спецификациях. Несколько слов о том, как организована статья. Во втором, следующем за введением, разделе даются общие сведения о моделях аппаратного обеспечения и типичной организации аппаратуры. В третьем разделе описывается предлагаемый подход к спецификации и проверке требований к аппаратному обеспечению. Четвертый раздел содержит краткий обзор технологии тестирования UniTESK и инструмента разработки тестов CTESK. В нем также описан способ использования инструмента для спецификации аппаратуры. В пятом разделе приводится сравнение предлагаемого подхода с существующими методами спецификации аппаратного обеспечения. Шестой раздел описывает опыт практического применения подхода. Наконец, в последнем, седьмом разделе делается заключение и очерчиваются направления дальнейших исследований.

    Изначально контрактные спецификации были предложены

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

    Тестирование софта - статьи

    Области применимости подхода

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

    Подход к решению задачи проверки сохранения семантики программы во время работы оптимизатора

    При тестировании важной задачей является анализ правильности работы тестируемой системы. Для случая оптимизатора такой анализ состоит из двух частей:
  • проверка, что семантика программы не изменилась после работы оптимизатора;
  • проверка, что были произведены все оптимизирующие трансформации. В настоящей работе мы не касаемся проверки того, что все трансформации были сделаны. Мы здесь рассматриваем лишь необходимую часть оракула, а именно проверку сохранения семантики программы после оптимизации. Задача проверки сохранения семантики произвольной программы при обработке ее оптимизатором равносильна задаче проверки эквивалентности двух программ. Эта задача в общем случае неразрешима. Тем не менее, для некоторых видов программ такая задача может быть решена. Например, для программ, функциональная семантика которых полностью представляется их трассой. Напомним, что мы считаем неразличимыми для оптимизатора те программы, которым соответствуют одинаковые модельные структуры. Таким образом, можно в качестве представителей классов эквивалентности выбрать программы, функциональная семантика которых полностью представляется трассой. Для таких программ задача проверки сохранения семантики во время работы оптимизатора сводится к сравнению трассы оптимизированной программы и некоторой эталонной трассы. В качестве такого эталона мы предлагаем использовать трассу, выдаваемую неоптимизированной версией той же программы. Пример: Инструкции для трассировки в тесте для анализатора Weak-Zero SIV Subscripts. Ниже приведен пример тестового воздействия для анализатора Weak-Zero SIV Subscripts на языке программирования C: 01: void f( int i, int* a, int* b, int* c ) 02: { 03: for( i = -10; i Строки 03-06 содержат код, построенный по модельной структуре. Строки 07-09 содержат инструкции для трассировки. Подход к решению задачи проверки сохранения семантики программы во время работы оптимизатора Для решения задачи проверки сохранения оптимизатором семантики необходимо иметь множество тестов и оракул. Для построения набора тестов будем генерировать программы P, обладающие следующими свойствами:
  • множество программ P является представительным для алгоритма тестируемого оптимизатора, т.е. это множество соответствует выбранному критерию тестового покрытия;
  • каждая P компилируется, т.е. синтаксически и семантически корректна;
  • каждая P корректно завершается за конечное время;
  • каждая P содержит некоторые вычисления в тех местах, которые должны подвергаться оптимизации;
  • функциональная семантика каждой P состоит в выводе информации, зависящей от всех имеющихся в программе вычислений. Работа оракула заключается в следующем:
  • каждый тест компилируется дважды - с оптимизацией и без оптимизации;
  • обе откомпилированные версии запускаются на исполнение;
  • полученные трассы сравниваются;
  • семантика признается сохранившейся в том и только том случае, если трассы эквивалентны. Далее в статье мы подробно рассмотрим процессы генерации и запуска тестов.

    Построение абстрактной модели

    Модель строится на основе абстрактного описания алгоритма оптимизации. Алгоритм оптимизации формулируется с использованием терминов, обозначающих сущности некоторого подходящего абстрактного представления программы, такого как граф потока управления, граф потока данных, таблица символов и пр. Оптимизатор для осуществления своих трансформаций ищет сочетания сущностей абстрактного представления программы, которые удовлетворяют некоторым шаблонам (например, наличие в программе циклов, наличие в теле цикла конструкций с определенными свойствами, наличие в процедуре общих подвыражений, наличие между инструкциями зависимости данных некоторого вида и пр.). При этом могут учитываться сущности лишь части терминов. Для построения модели мы будем рассматривать только те термины, которые именуют сущности, задействованные хотя бы в одном шаблоне. Итак, в результате анализа алгоритма выделяются термины и шаблоны, используемые в алгоритме. Далее на основании этой информации описывается множество модельных строительных блоков:
  • каждому термину соответствует свой вид модельного строительного блока;
  • строительные блоки могут связываться между собой чтобы иметь возможность образовывать структуры, соответствующие шаблонам. Пример: Weak-Zero SIV Subscripts analyzer. Рассмотрим анализатор, собирающий информацию о некотором виде зависимости данных для последующего использования этой информации в различных оптимизаторах. А именно, рассмотрим Weak-Zero SIV Subscripts analyzer (см., например, []). Термин subscript используется для обозначения пары выражений, использующихся в паре обращений в теле цикла к одному (возможно, многомерному) массиву и стоящих на одной и той же позиции в ндексах. Subscript называется SIV (single index variable), если на соответствующей индексируемой позиции используется ровно одна индексная переменная. SIV subscript, зависящий от индукционной переменной i, называется слабо-нулевым (weak-zero), если он имеет вид , где a, c1, c2 - константы и a≠0. Зависимость между двумя обращениями к массиву существует тогда и только тогда, когда обращение к общему элементу попадает в границы цикла.
    Это случается только тогда, когда значение Построение абстрактной модели является целым и L ≤ i0 ≤ U, где L и U соответственно нижняя и верхняя граница цикла. Этот алгоритм использует следующие термины: SIV subscript, определяемый тремя коэффициентами a, c1 и c2; цикл, определяемый своей нижней границей L и верхней границей U. Алгоритм осуществляет поиск следующего шаблона: Построение абстрактной модели Таким образом, модель состоит из следующих видов строительных блоков:
  • SIV subscript, содержащий три атрибута, которые соответствуют значениям a, c1 и c2;
  • Цикл, содержащий два атрибута, которые соответствуют значениям L и U, а также множество SIV subscript. Построение абстрактной модели Для частного случая оптимизаций, работающих с таким абстрактным представлением, которое близко синтаксической структуре программы, можно использовать способ построения модели, основанный на идее редукции грамматик (см. []). Пример: Control Flow Graph optimizer. Рассмотрим оптимизатор, который осуществляет трансформации для упрощения графа потока управления процедуры. Термин линейный участок (basic block) обозначает последовательность инструкций, которая начинается с метки, может заканчиваться условным или безусловным переходом, и может содержать последовательность не-переходных инструкций. Линейный участок называется пустым, если в нем не содержится не-переходных инструкций. Оптимизатор осуществляет следующие трансформации:
  • если некоторый переход J1 ведет на метку L1 некоторого пустого линейного участка, который завершается безусловным переходом J2 на метку L2, то J1 трансформируется в прямую ссылку на метку L2;
  • если обе ветви условного перехода J ведут на одну и ту же метку L, то J трансформируется в безусловный переход на метку L;
  • если метка L некоторого линейного участка B не используется ни в каком переходе, то B удалается. Алгоритм этой оптимизации использует такие термины: линейный участок, условный переход, безусловный переход. Алгоритм осуществляет поиск следующих шаблонов:
  • переход J1 ведет на метку L1 некоторого пустого линейного участка, который завершается безусловным переходом J2 на метку L2;
  • обе ветви условного перехода J ведут на одну и ту же метку L;
  • метка L не используется ни в каком переходе; Этот алгоритм использует граф потока управления в качестве абстрактного представления обрабатываемой программы.


    Такое представление тесно связано с синтаксической структурой программы. Редукция грамматики языка позволяет получить модель, которая состоит из следующих видов строительных блоков:
  • Процедура, содержащая последовательность линейных участков;
  • Линейный участок, содержащий метку, переход, а также атрибут ``пустой'';
  • Метка, содержащая атрибут ``не используется'';
  • Безусловный переход, содержащий ссылку на метку;
  • Условный переход, содержащий ссылки на метки. Построение абстрактной модели Будем называть модельной структурой граф, вершины которого - строительные блоки, а ребра - связи между строительными блоками. Проекция предложений исходного языка в модельные структуры индуцирует разбиение множества предложений исходного языка на классы эквивалентности. Один класс эквивалентности состоит из предложений, которые имеют одинаковое модельное представление, т.е. которые неразличимы для алгоритма оптимизации. Это свойство позволяет нам выдвинуть гипотезу, согласно которой на эквивалентных предложениях оптимизатор работает одинаково. Следовательно, в желаемом тестовом наборе достаточно иметь не более одного представителя из каждого класса эквивалентности. Поскольку множество модельных структур, т.е. множество классов эквивалентности, в общем случае бесконечно, то для создания тестового набора мы должны выбрать некоторое его конечное подмножество. Основанием для этого выбора должны служить те шаблоны, которые были выделены при анализе алгоритма оптимизации. Таким образом, критерий тестового покрытия формулируется в терминах абстрактной модели. Пример: Критерий тестового покрытия для анализатора Weak-Zero SIV Subscripts. Напомним, что анализатор Weak-Zero SIV Subscripts осуществляет поиск следующего шаблона: L ≤ i0 ≤ U и i0 целое, где i0 определяется из соотношения
    Построение абстрактной модели(1)
    Сформулируем соответствующий критерий тестового покрытия в терминах модели, т.е. в терминах L, U, a, c1 и c2. Фиксируем L, U и c1 - целые числа. Пусть a принимает значения 1 и 2. Мы хотим, чтобы i0 принимало целые значения из некоторого множества, а также какие-нибудь нецелые значения.Пусть в упомянутое множество входят целые числа, удовлетворяющие одному из следующих требований:
  • i0 i0=L;
  • i0 расположено близко к L внутри интервала, задаваемого границами цикла, например, i0=L+1;
  • i0 расположено в середине интервала, задаваемого границами цикла, например, i0=Построение абстрактной модели;
  • i0 расположено близко к U внутри интервала, задаваемого границами цикла, например, i0=U-1;
  • i0=U;
  • i0>U, например, i0=U+1. Для нахождения значения c2 достаточно решить уравнение (1) относительно значений a и i0. Построение абстрактной модели

    Практическое применение подхода

    С помощью предложенного подхода были построены тесты и протестированы ряд оптимизаторов в нескольких компиляторах для современных архитектур: GCC, Open64, Intel C/Fortran compiler. Были разработаны генераторы для следующих оптимизаторов:
  • Control Flow Graph optimization;
  • Common Subexpression Elimination;
  • Induction Variable optimization;
  • Loop Fusion optimization;
  • Loop Data Dependence analysis. Соответствующие множества тестов генерировались для языков программирования C и Fortran.

    Применение модельного подхода для автоматического тестирования оптимизирующих компиляторов

    , , ,
    В статье предлагается концепция автоматизированного построения тестовых наборов и тестовых оракулов для тестирования оптимизаторов. Используется подход, основанный на генерации тестов из моделей. Основные идеи модельного подхода заключаются в следующем: 1) модельный язык неявно разбивает множество программ целевого языка на классы эквивалентности; 2) критерий тестового покрытия формулируется в терминах модельного языка; 3) в соответствии с выбранным критерием генерируется набор тестов. В работе описывается схема построения тестового оракула, который проверяет сохранение семантики программы после ее оптимизации.


  • Создание генератора тестов

    Будем называть тестовым воздействием единичные входные данные для тестируемого оптимизатора, т.е. программу на целевом языке. Термин ко-тестовое воздействие обозначает единичные входные данные для результата компиляции тестового воздействия, т.е. значения параметров для запуска соответствующей откомпилированной программы. Итератор ко-тестовых воздействий - это компонент, обеспечивающий перебор необходимых ко-тестовых воздействий и запуск откомпилированного тестового воздействия. Таким образом, один тест включает в себя тестовое воздействие и итератор ко-тестовых воздействий. Для получения множества тестов для целевого оптимизатора необходимо разработать соответствующий генератор. Этот генератор должен создавать представительное множество тестовых воздействий вместе с соответствующими итераторами ко-тестовых воздействий. Создание генератора представительного множества тестовых воздействий начинается с анализа алгоритма тестируемого оптимизатора, построения абстрактной модели и формулировки критерия тестового покрытия, как это было описано выше. После этого происходит разработка собственно генератора. Генератор тестов состоит из двух компонентов. Первый, называемый итератор, отвечает за последовательную генерацию модельных структур. Второй компонент, называемый меппер, отвечает за отображение каждой модельной структуры в целевой язык. Итератор должен создавать множество модельных структур в соответствии с выбранным критерием тестового покрытия. Для данной модельной структуры S меппер должен строить соответствующий тест со следующими свойствами:
  • тестовое воздействие, построенное по модельной структуре S, имеет модельное представление, совпадающее с S;
  • построенный тест (т.е. тестовое воздействие и итератор ко-тестовых воздействий) является синтаксически и семантически корректным с точки зрения целевого языка;
  • тестовое воздействие содержит вычисления в тех местах, которые должны подвергаться оптимизации;
  • тестовое воздействие содержит инструкции для трассирования окончательных результатов вычислений;
  • итератор ко-тестовых воздействий содержит инструкции для формирования всех необходимых параметров, а также инструкции для активации откомпилированного тестового воздействия (т.е. вызов соответствующей процедуры или нескольких процедур). Если модель предполагает начилие некоторых вычислений, то для формирования трассы меппер вставляет в текст тестового воздействия печать окончательных значений всех переменных, вовлеченных в вычисления. В противном случае меппер дополнительно вставляет в текст тестового воздействия некоторые вычисления, а для формирования трассы вставляет печать окончательных результатов этих вычислений. По окончании разработки итератора и меппера они собираются в генератор. После этого происходит генерация искомого множества тестов.

    это основной инструмент при создании

    Компиляторы[,,] - это основной инструмент при создании программного обеспечения, поэтому их надежность особенно важна. Наряду с другими методами верификации и валидации для компиляторов, тестирование попрежнему остается важным элементом в семье этих методов. Необходимость автоматизации тестирования компиляторов также представляется очевидной, поскольку реальные объемы добротных наборов тестов и сложность анализа результатов весьма велики. Подход UniTesK [] является методологий построения надежного и качественного ПО на основе использования моделей этого ПО. UniTesK использует модельный подход в следующих целях:
  • для построения критериев правильности реализации ПО,
  • для построения критериев полноты и эффективности проверки качества ПО,
  • для построения входных тестовых данных и процедур анализа результатов целевого ПО. В узком смысле UniTesK предлагает рассматривать модели как инструмент для построения тестов целевого ПО. При этом процесс разработки теста и собственно тестирования распадается на следующие фазы:
  • Построение абстрактной модели или спецификации поведения целевой системы.
  • Извлечение тестового оракула (т.е. процедуры анализа результата целевой системы) из спецификации.
  • Разбиение пространства входных данных целевой системы на домены,
  • Проектирование критерия тестового покрытия в терминах абстрактной модели.
  • Интеграция сгенерированных и вручную написанных компонентов тестовой системы.
  • Пропуск тестов, включающий
  • анализ результатов целевой системы при помощи оракулов;
  • измерение тестового покрытия в терминах модели/спецификации или в терминах реализации. Основные достоинства этого подхода состоят в следующем:
  • Спецификации и модели обычно строятся на основе функциональных требований к системе, и часто структура спецификаций следует структуре требований, что позволяет явным образом связать требования к системе с результатами тестирования и метриками тестового покрытия.
  • Спецификации и модели, а значит и тесты, могут разрабатываться до завершения реализации целевой системы, что сокращает общее время разработки ПО.
  • Спецификации и модели обычно компактнее и проще реализации, что упрощает ре-юз и сопровождение как самих моделей, так и тестов, которые строятся на их основе.
  • Достижение исчерпывающего покрытия в соответствии с критериями, определенными на спецификациях и моделях, как правило, обеспечивает уровень покрытия реализации, сравнимый с уровнем, достигаемым при традиционном тестировании, но за счет существенно меньших трудозатрат. Подход UniTesK прошел апробацию в проектах, свзяаннных с тестированием как действующего, так и вновь создаваемого ПО [,,].
    Целевое ПО в этих проектах принадлежало к различным классам систем, предоставляющих процедурный интерфейс:
  • ядра операционных систем,
  • телекоммуникационные протоколы,
  • серверы,
  • run-time поддержка компиляторов и отладчиков. Следуя общей схеме процесса UniTesK, описанной выше, удалось полностью автоматизировать работы фаз 2, 3, 5, 6. Фаза 1 выполняется вручную, фаза 4 - полуавтоматически. Перенос опыта и инструментов UniTesK на тестирование компиляторов вскрыл ряд проблем. В этой статье мы опишем применение этого подхода к тестированию оптимизирующих модулей в компиляторах. Главная проблема здесь заключается в том, что нет эффективного способа создавать для оптимизатора такие спецификации, из которых можно было бы извлекать эффективные оракулы. Поэтому в предлагаемом подходе мы используем лишь следующие фазы процесса UniTesK:
  • Построение абстрактной модели входных данных целевой системы.
  • Проектирование критерия тестового покрытия в терминах абстрактной модели.
  • Интеграция сгенерированных и вручную написанных компонентов тестовой системы.
  • Пропуск тестов, включающий
  • анализ результатов целевой системы при помощи оракулов;
  • измерение тестового покрытия в терминах модели/спецификации или в терминах реализации. В рамках предлагаемого подхода оракул проверяет только сохранение семантики программы во время оптимизации. Для этого в качестве тестовых воздействий на оптимизатор берутся такие программы, семантика которых полностью представляется их трассой. Такое свойство тестов позволяет свести задачу проверки сохранения семантики к сравнению трассы с некоторой эталонной трассой. Итак, суть подхода такова:
  • Построить для тестируемого оптимизатора представительное множество тестовых воздействий следующим образом:
  • построить абстрактную модель входных данных оптимизатора;
  • в терминах абстрактной модели сформулировать критерий покрытия этих входных данных;
  • перебрать соответствующие тестовые воздействия;
  • Протестировать оптимизатор следующим образом:
  • пропустить тесты через компилятор при активированном тестируемом оптимизаторе;
  • вынести вердикт относительно сохранения семантики тестов после оптимизации. В следующих разделах статьи описываются детали процесса тестирования оптимизаторов в соответствии с предлагаемым подходом.В конце приводятся экспериментальные данные по применению методологии, обсуждаются область применимости и ограничения этого подхода и приводится обзор близких работ. Предварительный вариант настоящей статьи был доложен на международном семинаре ``Понимание программ''[].

    Для автоматического получения представительных множеств

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

    Запуск тестов

    Для проверки сохранения семантики программы оптимизатором каждый тест требуется откомпилировать с оптимизацией, выполнить результат компиляции и сравнить полученную трассу с эталоном. Напомним, что мы предлагаем в качестве эталона использовать трессу неоптимизированной версии соответствующего теста. Итак, процесс запуска тестов и анализа результатов состоит из следующих шагов:
  • Тестирование оптимизатора:
  • компиляция тестов с включенной целевой оптимизацией.
  • Ко-тестирование:
  • запуск результатов компиляции на выполнение;
  • сохранение получившейся трассы (тестовая трасса).
  • Получеие эталона:
  • компиляция теста с отключенной целевой оптимизацией;
  • запуск результатов компиляции;
  • сохранение получившейся трассы (эталонная трасса).
  • Запуск оракула:
  • cравнение тестовой трассы с эталоном;
  • вынесение вердикта о сохранении семантики теста во время оптимизации. Тестируемый оптимизатор признается сохраняющим семантику программ в соответствии с выбранным критерием тестового покрытия тогда и только тогда, когда оракул вынес положительные вердикты для всех тестов.

    Тестирование софта - статьи

    Аннотация.

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

    Экспериментальное исследование метода сокращения набора тестов

    Целью проводимого эксперимента являлось определение и оценка параметров метода сокращения набора тестов, предложенного в данной работе, на модельных данных. При анализе методов сокращения набора тестов важны следующие параметры:
  • Степень сокращения набора. Это один из наиболее существенных параметров работы метода сокращения, так как этот параметр показывает, насколько сокращаются первоначальный набор тестов и связанные с ним затраты. Чем больше степень сокращения набора тестов, тем сильнее уменьшаются затраты на его выполнение, проверку и сопровождение, тем самым, сокращая затраты на проведение регрессионного тестирования в целом [].
  • Уровень обнаружения ошибок. Основной проблемой при сокращении набора тестов является возможное уменьшение его уровня обнаружения ошибок вследствие удаления тестов, обнаруживающих ошибки []. Слишком большие потери в уровне обнаружения ошибок могут привести к нецелесообразности сокращения набора тестов. Поэтому важно, чтобы в результате работы метода его уровень обнаружения ошибок по возможности не уменьшался, а если и уменьшался, то незначительно. Целью эксперимента являлось решение следующих задач:
  • определение величины сокращения исходного набора тестов;
  • определение уровня обнаружения ошибок сокращенным набором тестов;
  • сравнение уровня обнаружения ошибок сокращенным набором тестов с уровнем первоначального набора и уровнем, обеспечиваемым методом случайного сокращения набора тестов;

    Метод сокращения набора тестов

    Идея предлагаемого метода сокращения набора тестов достаточно проста. Можно предположить, что при тестировании функциональности и структуры программы встречаются тесты, которые инициируют одни и те же последовательности системных вызовов. Метод основан на этом предположении и состоит в "отсеивании" тех тестов, которые не генерируют новые последовательности системных вызовов, то есть не инициируют нового поведения программы. В предлагаемом методе сокращения набора тестов используется модель поведения программы, построенная в предыдущем разделе. Общий алгоритм работы метода выглядит следующим образом (см. Рис.2): Метод сокращения набора тестов Рис.2Алгоритм работы метода. Здесь T - первоначальный набор тестов;
    T' - сокращенный набор тестов, T' Метод сокращения набора тестов T;
    t - очередной тест из первоначального набора, t Метод сокращения набора тестов T;
    Mt - модель поведения программы на тесте t;
    MT' - множество моделей поведения программы на тестах из T'.
  • На первом этапе выбирается очередной тест из сокращаемого набора тестов и на нем выполняется тестируемая программа.
  • Во время выполнения программы строится модель Mt ее поведения на выбранном тесте t.
  • На третьем этапе проверяется, входит ли модель Mt в множество моделей MT' или нет.
  • Если модель Mt не входит в множество моделей MT', то она добавляется в множество MT', и тест t добавляется в сокращенный набор тестов T'.
  • Если набор тестов T не исчерпан, то осуществляется переход к шагу 1, в противном случае сокращенный набор тестов T' считается построенным, и работа метода завершается. Для того чтобы реализовать метод, необходимо предложить операцию сравнения моделей поведения программы. Такая операция должна показывать, когда модели поведения программы совпадают, а когда нет. Будем считать, что модели поведения программы равны, когда их множества последовательностей совпадают, и различны в противном случае. Тогда операция сравнения моделей будет сводиться к операции сравнения множеств последовательностей системных вызовов. Надо заметить, что использование в предложенном методе множества последовательностей системных вызовов вместо трасс системных вызовов позволяет решить проблему циклов в тестируемой программе.
    Данная проблема изначально возникла при оценке адекватности наборов тестов, основанной на анализе покрытия путей выполнения программы, и состоит в том, что если код программы содержит циклы, то количество различных путей выполнения программы может быть очень большим, что приводит к падению эффективности метода оценки и значительному увеличению затрат на его работу []. Аналогичная проблема возникает и в рассматриваемом методе, если в качестве модели поведения программы рассмотривать трассы системных вызовов. В этом случае, если системные вызовы встречаются внутри циклов программы, различное количество возможных повторений цикла может привести к очень большому количеству разных трасс программы. Такая ситуация может исказить результаты сокращения тестового набора, так как поведения программы, различающиеся только количеством выполненных циклов, будут приняты как различные, хотя фактически они не тестируют новую функциональность или структуру программы. Использование множества последовательностей системных вызовов решает эту проблему, так как, даже при произвольном количестве повторений цикла, множество уникальных поведений программы быстро стабилизируется, и модель перестает изменяться.

    Модель поведения программы на тесте

    В предлагаемом методе последовательности системных вызовов используются для построения модели поведения программы на тесте. Для начала введем несколько определений. Трассой системных вызовов программы P для теста t будем называть последовательность системных вызовов, совершенных программой P при выполнении на входных данных тестового случая t. При этом будем подразумевать, что системные вызовы встречаются в трассе в порядке их вызова программой P . Последовательностью системных вызовов длины K называется любая непрерывная подпоследовательность длины K, встречающаяся в трассе системных вызовов. Множеством последовательностей системных вызовов длины K, соответствующим поведению программы P на тесте t, называется множество всех возможных последовательностей длины K, встречающихся в трассе системных вызовов программы P для теста t. Для получения множества последовательностей системных вызовов фиксированной длины используется техника "скользящего окна" [] с размером K (размер окна соответствует длине выделяемых последовательностей). Согласно этому подходу, выделение последовательностей происходит следующим образом: в качестве первой последовательности из трассы выбираются K идущих подряд системных вызовов, начиная с первого вызова в трассе; в качестве второй последовательности выбираются K идущих подряд системных вызовов, начиная со второго вызова; и так далее, пока не будет пройдена вся трасса. В качестве модели поведения программы P на тесте t принимается определенное выше множество последовательностей. Рассмотрим пример построения модели поведения программы. Допустим, у нас есть следующая последовательность, состоящая из системных вызовов операционной системы UNIX: open write write open write close write close. Тогда результатом выделения последовательностей с помощью скользящего окна размера 4 будет следующее множество последовательностей: open write write open write write open write write open write close open write close write write close write close Полученное таким образом множество последовательностей будет являться моделью поведения программы.

    Об одном методе сокращения набора тестов

    Д.Ю. Кичигин, Труды Института системного программирования РАН

    Описание метода

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

    Проведение эксперимента и результаты

    Так как стратегия генерации тестовых наборов оказывает косвенное влияние на исследуемый метод (вследствие того, что индивидуальные тесты выбираются из набора в порядке их следования, и, соответственно, в сокращенный набор тестов будет помещен первый тест, расширяющий множество моделей), то для достижения чистоты эксперимента тесты исходного набора были перенумерованы случайным образом. После этого тестируемая программа была выполнена на каждом тесте из пула, и для каждого теста была построена модель выполнения программы. Затем из пула сгенерированных тестов методом случайной выборки было сформировано 35 исходных наборов тестов разного размера, начиная с 50 и заканчивая 1700 с шагом 50. В качестве последнего, 35-го набора был принят набор, содержащий все 1715 тестов из пула. Далее, из исходных наборов с помощью исследуемого метода были построены сокращенные наборы, для каждого из которых были вычислены параметры сокращения: размер сокращенных наборов и коэффициент сокращения. После этого были вычислены уровни обнаружения ошибок сокращенными наборами, и затем результаты были сравнены с уровнями исходных наборов и уровнями, получаемыми при использовании метода случайного сокращения. Для этого из исходных наборов методом случайной выборки были построены наборы такого же размера, как и сокращенные, и были вычислены их уровни обнаружения ошибок.

    Результаты эксперимента

    Результаты работы метода проиллюстрированы на Рис. 3 - 6: Размер сокращенного набора находился в пределах от 12 до 24 тестов в зависимости от размера исходного набора. Величина сокращения находилась в пределах от 76% до 98.6% в процентном соотношении. По результатам видно, что размер сокращенного набора рос вместе с ростом размера исходного набора и в итоге стабилизировался. Это является закономерной картиной, если учесть, что множество различных путей выполнения программы конечно, и при увеличении числа тестов увеличивается вероятность выполнения всех путей программы. На модельных данных уровень обнаружения ошибок для сокращенных наборов составил от 95.24% до 100% от уровня исходных наборов, т.е. величина падения уровня обнаружения ошибок для исследуемого метода составила менее 4.8%. Средняя величина падения уровня обнаружения ошибок для исследуемого метода составила 1.5%. При этом исследуемый метод проиграл методу случайного сокращения лишь в одном случае; в большинстве остальных случаев исследуемый метод показал выигрыш на 4.3% - 26%. В среднем сокращенные наборы, построенные с помощью исследуемого метода, при проведении эксперимента обнаруживали на 9.8% ошибок больше, чем соответствующие наборы, полученные с помощью метода случайного сокращения.

    Системные вызовы как характеристика поведения программы

    Системными вызовами (system call) называются функции, входящие в состав программного интерфейса операционной системы. Изначально последовательности системных вызовов были предложены в качестве характеристики поведения программы при решении задачи обнаружения вторжений [,,,] в области компьютерной безопасности. Последовательности системных вызовов также успешно использовались при поиске потенциальных ошибок в программном обеспечении []. При этом было отмечено, что последовательности системных вызовов, произведенных программой во время своего выполнения, являются достаточно информативной характеристикой выполнения программы и позволяют различать, когда выполнение программы идет по разным путям []. В данной работе это свойство последовательностей системных вызовов используется для анализа поведения программы на наборе тестов. Мониторинг системных вызовов, вызываемых программой во время своего выполнения, осуществляется командой ktrace, входящей в пакет операционной системы. Результатом работы этой программы является трасса системных вызовов, выполненных программой во время своей работы. На Рис.1 приведен пример трассы для фрагмента программы. Системные вызовы как характеристика поведения программы Рис.1Представление поведения программы с помощью системных вызовов.

    Существующие решения

    Существующие методы сокращения набора тестов [,,,,] объединяет общий подход, который состоит в построении набора тестов меньшего размера, но при этом эквивалентного первоначальному набору в терминах выбранного покрытия кода программы. В результате полученный сокращенный набор тестов замещает собой первоначальный и используется для проведения регрессионного тестирования. Существующие методы сокращения набора тестов в основном различаются видом используемого покрытия. В работе [] для сокращения набора тестов используется покрытие "всех использований" ("all-uses" coverage). Это покрытие является частным случаем покрытия ассоциаций вида определение-использование (definitions-use associations coverage), основанного на модели потока данных в программе []. Анализ этого покрытия основывается на изучении вхождений переменных в программу и рассмотрении путей выполнения программы от места определения переменной (variable definition) до места ее использования (variable use). В [] рассматривается метод сокращения наборов на основе покрытия ребер гипотетического графа потока управления программы. Для анализа покрытия ребер используется исходный код программы []. В работе [] для сокращения набора тестов используется покрытие MC/DC (Modified Condition/Decision Coverage). Покрытие MC/DC определяется в терминах языка программирования, используемого при написании программы, и является развитием покрытия условий (conditions coverage) []. В [] предлагается метод сокращения набора тестов, основанный на комбинации покрытия блоков программы и ассоциированных с выполнением теста ресурсозатрат. Блоком называется последовательность выражений программы, не содержащая ветвлений; таким образом, покрытие блоков эквивалентно покрытию выражений. Общей особенностью перечисленных подходов является то, что для их применения необходим доступ и инструментирование исходного либо объектного кода программы, что подвергает эти методы указанным во введении ограничениям. Немного особняком стоит работа [], где авторы в качестве элементов покрытия используют стек вызовов функций программы (call stack) и указывают, что такой подход может применяться и без доступа к исходному коду программы. Однако в эксперименте, проведенном авторами этой работы, для мониторинга стека вызовов функций требуется знать сигнатуры внутренних функций программы, для получения которых используется доступ к исходному тексту программы. Поэтому, по крайней мере, пока также нельзя говорить, что данная методика применима в условиях указанных выше ограничений.

    Условия проведения эксперимента: тестовые программы и тестовые наборы

    Эксперимент проводился в операционной системе FreeBSD 5.3; для сбора трасс использовалась команда ktrace, входящая в состав операционной системы. Длина K используемых последовательностей составляла 15 системных вызовов. Для проведения эксперимента в качестве объекта тестирования использовались программа nametbl, входящая в пакет программ, впервые предложенный в [] и впоследствии использовавшийся в [,,] для исследования методов оценки адекватности тестовых наборов. Программа nametbl считывает команды из файла и выполняет их с целью тестирования нескольких функций. Тестируемые функции реализуют работу с таблицей символов для некоторого языка программирования. Таблица символов хранит для каждого символа его имя и тип. Команды, считываемые из файла, позволяют добавить в таблицу новый символ, указать тип ранее введенного символа, осуществить поиск символа и распечатать таблицу символов. Общий объем исходного текста программы составляет 356 строк. Основной причиной выбора этой программы для проведения эксперимента послужило то, что ее исходные тексты и руководство по созданию наборов тестов находятся в открытом доступе , и, таким образом, описываемый эксперимент может быть повторен независимо. Для построения тестов использовалось разбиение входных данных программы nametbl на домены, предложенное в работе []. В этой работе было предложено 4 "измерения" входных данных, в трех из которых было выделено 7 доменов, а в четвертом - 5 доменов. В данной работе было использовано это разбиение, в каждом домене было выбрано по одному значению, и в итоге был сгенерирован пул из 1715 разных тестов. Для определения параметров метода были использованы следующие индикаторы:
  • размер сокращенного набора тестов;
  • коэффициент сокращения набора тестов в процентном выражении:
    100 × (1 - Sizereduced/Sizeinitial), где
    Sizeinitial означает размер исходного набора тестов,
    Sizereduced - размер сокращенного набора тестов;
  • количество обнаруженных ошибок;
  • коэффициент обнаружения ошибок в процентном выражении:
    100 × (FaultsDetectedreduced/FaultsDetectedinitial), где
    FaultsDetectedinitial означает количество ошибок, обнаруживаемых исходным набором тестов,
    FaultsDetectedreduced - количество ошибок, обнаруживаемых сокращенным набором тестов. Уровень обнаружения ошибок измерялся исходя из следующего принципа эквивалентности тестовых наборов: два набора тестов считаются эквивалентными при обнаружении некоторой ошибки, если каждый набор содержит по крайней мере один тест, обнаруживающий эту ошибку (в англоязычной литературе такой принцип носит название per-test-suite basis []). Такой подход применялся в работах [, ].

    Темпы развития современного программного обеспечения

    Темпы развития современного программного обеспечения и особенно увеличение количества вносимых в него модификаций приводят к увеличению затрат на проведение регрессионного тестирования. В связи с этим достаточно актуальной является проблема уменьшения стоимости регрессионного тестирования. Сокращение набора тестов [,,] является одним из способов уменьшения затрат на проведение регрессионного тестирования. Целью задачи сокращения набора тестов является создание набора тестов меньшего размера, но при этом, желательно, с таким же уровнем обнаружения ошибок []. Такое сокращение позволяет уменьшить затраты на сопровождение тестового набора и на выполнение на нем тестируемого программного обеспечения [], что, в свою очередь, позволяет сократить затраты на проведение регрессионного тестирования в целом. Традиционный подход к решению задачи сокращения набора тестов заключается в "отсеивании" тестов из первоначального набора таким образом, чтобы сохранялся уровень его адекватности в терминах некоторого выбранного критерия адекватности, и основывается на использовании статического анализа и/или инструментирования исходного текста программного обеспечения (ПО). При таком подходе решение задачи, как правило, состоит из трех основных этапов. На первом этапе происходит инструментирование кода тестируемого ПО и запуск ПО на наборе тестов. На втором этапе собирается информация о достигнутом покрытии кода. Наконец, на третьем этапе происходит сокращение набора тестов на основе собранной информации о покрытии []. Современные тенденции разработки программного обеспечения ставят новые условия для методов сокращения набора тестов и во многом ограничивают область применения существующих методов. Во-первых, растет популярность использования готовых программных компонентов. Такие компоненты, как правило, поставляются без исходного текста, что ограничивает применимость методов, основанных на анализе или инструментировании исходного кода программы []. Во-вторых, программное обеспечение зачастую разрабатывается с использованием разных языков программирования, что ограничивает доступность средств анализа и инструментирования исходного кода, так как многие из них ориентированы на работу с конкретным языком программирования [].
    В третьих, возрастающая сложность современного программного обеспечения, особенно увеличение его модульности, делает неудобным и повышает затраты на использование средств инструментирования кода, так как в этом случае увеличивается количество инструментируемых модулей и, соответственно, возрастает сложность анализа покрытия. Рассмотренные обстоятельства делают актуальной разработку новых методов, способных работать в указанных условиях. В данной работе предлагается новый метод сокращения набора тестов, основанный на анализе поведения программы на наборе тестов и использующий для этого короткие последовательности системных вызовов. Предлагаемый метод не зависит от языка программирования, на котором производилась разработка ПО, не требует доступа к исходному коду и не предполагает инструментирования кода ПО, что существенно расширяет область его применения и облегчает использование. Статья организована следующим образом. В разделе 2 рассмотрены основные существующие методы сокращения набора тестов. Раздел 3 посвящен описанию предлагаемого метода сокращения набора тестов. В разделе 4 приводится описание проведенного эксперимента и его результатов. В конце работы делаются выводы и рассматриваются направления будущих исследований.

    Заключение и направление будущих исследований

    В данной работе был представлен метод решения задачи сокращения набора тестов, возникающей в контексте регрессионного тестирования. Данный метод основан на использовании моделей поведения программы, построенных в терминах системных вызовов, которые производятся программой во время своего выполнения. Работа метода была экспериментально исследована на модельных данных. Были исследованы такие параметры работы метода, как величина сокращения набора тестов и уровень обнаружения ошибок. Результаты работы метода были сравнены с результатами метода случайного сокращения набора тестов, и было показано, что исследуемый метод практически не уступает методу случайного сокращения набора тестов, а в большинстве случаев превосходит его. В будущих исследованиях планируется развивать предложенный метод и, в частности, сосредоточиться на следующих направлениях. Во-первых, необходимо более четко охарактеризовать класс программ, к которым может быть применен предложенный метод. Интуитивно понятно, что результаты работы метода зависят от внутреннего устройства программы - частоты и характера обращений тестируемой программы к системным вызовам операционной системы. Например, на преимущественно вычислительных программах, где обращения к системным вызовам происходят редко, или в программном обеспечении баз данных, где разнообразность поведения программы характеризуется скорее характером передаваемых данных, нежели разными путями в графе выполнения программы, исследуемый метод может и не показать хороших результатов. Во-вторых, имеет смысл учитывать не только имена системных вызовов, совершенных программой во время своего выполнения, но и передаваемые в них параметры и возвращаемые значения. Такая информация косвенно учитывает потоки данных в программе и будет полезна при построении модели поведения программы. Наконец, планируется сравнить результаты работы предлагаемого метода не только с методом случайного сокращения наборов тестов, но и с остальными методами, используемыми в данной области.

    Тестирование софта - статьи

    Аннотация

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

    Целенаправленный поиск

    Учитывая структуру критерия , из задачи 1 можно выделить следующую подзадачу: Задача 2. Для заданной тестовой системы S и заданного элемента тестового покрытия q, построить тест Целенаправленный поиск , удовлетворяющий условию Целенаправленный поиск. Для решения исходной задачи 1, достаточно решить задачу 2 для Целенаправленный поиск попарно различных элементов тестового покрытия Целенаправленный поиск, то есть построить тесты Целенаправленный поиск такие, что Целенаправленный поиск Решением задачи 1 будет множество Целенаправленный поиск . Рассмотрим генетический алгоритм решения задачи 2. В качестве множества кандидатов возьмём множество тестов T. Условие останова: в текущей популяции присутствует тест q такой, что Целенаправленный поиск. Оценочная функция Целенаправленный поиск каждому тесту t ставит в соответствие числовую меру Целенаправленный поиск того, насколько тест t близок к тому, чтобы покрыть элемент тестового покрытия q. При этом оценочная функция Целенаправленный поиск достигает своего максимального значения на тех и только на тех тестах, которые удовлетворяют условию Целенаправленный поиск. Иными словами: Целенаправленный поиск (5) В частности, в качестве оценочной функции можно использовать следующую функцию, удовлетворяющую условию (5): Целенаправленный поиск В такой оценочной функции считается, что все тесты, не покрывающие элемент тестового покрытия q, одинаково далеки от того, чтобы покрыть элемент q. При использовании этой оценочной функции эффективность генетического алгоритма будет не выше, чем при случайном поиске. Примеры более эффективных оценочных функций для некоторых метрик полноты тестового покрытия можно найти в [,,].

    Генетические алгоритмы

    Генетические алгоритмы - это метод решения задач оптимизации. В методе используются идеи, почерпнутые из эволюционной биологии: наследование признаков, мутация, естественный отбор и кроссовер []. Определяется множество кандидатов, среди которых ищется решение задачи. Кандидаты представляются в виде списков, деревьев или иных структур данных. Общая схема генетического алгоритма выглядит следующим образом:
  • создать начальный набор кандидатов;
  • оценить качество каждого кандидата в текущем наборе;
  • выбрать пары наиболее качественных кандидатов для воспроизводства;
  • применить оператор кроссовера;
  • применить оператор мутации;
  • если не выполнено условие останова, перейти к шагу 2. Начальный набор кандидатов, как правило, формируется случайным образом. На множестве кандидатов определяется оценочная функция, задающая качество кандидата, то есть то, насколько он близок к верному решению. При выборе кандидатов для воспроизводства более качественные кандидаты имеют больше шансов. По двум выбранным кандидатам предыдущего поколения оператор кроссовера строит кандидата следующего поколения. Оператор мутации вносит малые случайные изменения кандидатов. Алгоритм завершается, когда выполняется условие останова. Часто используются следующие условия останова:
  • достигается заданное количество поколений;
  • найдено верное решение;
  • за заданное количество итераций максимальное качество кандидатов в популяции не улучшилось;
  • различные комбинации предыдущих условий. Генетические алгоритмы позволяют решать задачи, для которых не применимы традиционные методы оптимизации. Одной из областей применения генетических алгоритмов является автоматическая генерация тестов для программного обеспечения.

    Генетический алгоритм генерации тестов

    Рассмотрим следующую задачу генерации тестов: Задача 1. Для заданной тестовой системы S построить тестовый набор Генетический алгоритм генерации тестов, удовлетворяющий критерию . Для построения генетического алгоритма решения этой задачи необходимо определить:
  • множество кандидатов;
  • структуру представления кандидатов;
  • оценочную функцию;
  • оператор кроссовера;
  • оператор мутации;
  • условие останова.

    Критерии полноты тестового покрытия

    Для тестирования программного обеспечения требуется создать репрезентативный набор тестов, то есть набор, охватывающий все возможные сценарии работы системы. Для оценки репрезентативности тестовых наборов используются различные критерии полноты тестового покрытия. Пусть P - множество программных систем, T - множество тестов, а Σ - множество тестовых наборов, то есть множество всех конечных подмножеств множества T . Тогда задача генерации тестов может быть сформулирована следующим образом: для заданной тестируемой системы S Критерии полноты тестового покрытия P построить тестовый набор Критерии полноты тестового покрытия, удовлетворяющий заданному критерию полноты тестового покрытия Критерии полноты тестового покрытия, то есть такой набор Критерии полноты тестового покрытия, для которого Критерии полноты тестового покрытия. Многие критерии полноты тестового покрытия, имеющие практическое применение, строятся по следующей схеме: для тестируемой системы S критерий F определяет множество элементов тестового покрытия Критерии полноты тестового покрытия. Элементом тестового покрытия можно считать некоторый класс событий, которые могут произойти в ходе работы тестируемой программной системы. По появлению в процессе исполнения программы элементов тестового покрытия и различных их комбинаций можно судить о полноте или качестве проверки, которую выполняет данный тестовый набор. Например, элементами тестового покрытия могут быть исполняемые строки исходного кода (соответствующие событиям их исполнения); рёбра графа потока управления; пути в графе потока управления; логические выражения, встречающиеся в исходном коде и т.п. Кроме того, критерий F определяет логическую функцию Критерии полноты тестового покрытия, которая принимает значение Критерии полноты тестового покрытия, если элемент тестового покрытия q покрывается тестом t. Тестовый набор Критерии полноты тестового покрытия для системы S удовлетворяет критерию полноты тестового покрытия F, если каждый элемент тестового покрытия из множества Критерии полноты тестового покрытия покрывается хотя бы одним тестом из тестового набора Критерии полноты тестового покрытия. Иными словами: Критерии полноты тестового покрытия (1) Приведём несколько примеров часто упоминаемых критериев полноты тестового покрытия:
  • каждый оператор в исходном коде выполняется хотя бы один раз;
  • каждая ветвь графа потока управления выполняется хотя бы один раз;
  • каждый путь графа потока управления исполнение выполняется хотя бы один раз;
  • каждое логическое выражение хотя бы один раз вычисляется со значением "истина" и хотя бы один раз - со значением "ложь";
  • тестовый набор убивает всех мутантов из заданного набора. Заметим, что все критерии, приведённые в качестве примеров, соответствуют ранее изложенной схеме.

    Метрики тестового покрытия

    Со многими критериями полноты тестового покрытия можно связать соответствующую метрику тестового покрытия. Метрика тестового покрытия - это функция вида Метрики тестового покрытия. Значение этой функции Метрики тестового покрытия имеет смысл числовой оценки того, насколько хорошо тестовый набор Метрики тестового покрытия покрывает тестируемую систему S. Сам критерий при этом можно записать в виде Метрики тестового покрытия, где Метрики тестового покрытия - это минимальное пороговое значение метрики M для тестируемой системы S. В частности, для критерия полноты тестового покрытия F, представимого в виде , можно ввести следующую метрику: Метрики тестового покрытия (2) Сам критерий при этом примет вид: Метрики тестового покрытия (3) В некоторых случаях, когда не удаётся построить тестовый набор, удовлетворяющий такому критерию полноты тестового покрытия, можно использовать ослабленный критерий: Метрики тестового покрытия (4) Параметр Метрики тестового покрытия указывает, какая доля элементов тестового покрытия должна быть покрыта тестовым набором. Приведём несколько примеров часто упоминаемых метрик тестового покрытия:
  • количество покрытых (выполненных хотя бы один раз) операторов в исходном коде;
  • количество покрытых ветвей графа потока управления;
  • количество покрытых путей графа потока управления;
  • количество распознанных мутантов (версий тестируемой системы с искусственно привнесёнными ошибками). Все эти метрики могут быть представлены в виде . Подробное описание этих и других, используемых на практике, метрик полноты тестового покрытия можно найти в [].

    Оценочные функции

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

    Покрытие операторов исходного кода

    Тестовый набор удовлетворяет критерию покрытия операторов исходного кода, если при выполнении этого тестового набора каждый оператор исходного текста программы выполняется хотя бы один раз. Элементами тестового покрытия в данном случае являются операторы исходного текста. Для заданного оператора q значение оценочной функции Покрытие операторов исходного кода тем больше, чем ближе тест t к тесту, покрывающему оператор q. Для построения оценочной функции рассмотрим граф потока управления тестируемой системы S. Вершинами графа являются операторы исходного кода, то есть множество Покрытие операторов исходного кода. В графе существует ребро, идущее из вершины q1 в вершину q2 тогда и только тогда, когда оператор q2 может быть выполнен непосредственно после оператора q1. Пусть Покрытие операторов исходного кода - это множество всех элементов q' из Покрытие операторов исходного кода, для которых выполняются следующие условия:
  • существует путь в графе потока управления ведущий из q' в q или q' = q;
  • Покрытие операторов исходного кода. Обозначим через dist(q', q) длину кратчайшего пути в графе потока управления, ведущего из q' в q (dist(q, q)≡0). Тогда оценочную функцию можно определить следующим образом: Покрытие операторов исходного кода (6) Выражение, стоящее справа, определяет, за какое минимальное количество переходов можно добраться до элемента покрытия q от уже покрытых элементов из множества Покрытие операторов исходного кода. Функция Покрытие операторов исходного кода принимает значение 0 на тех и только тех тестах, которые покрывают элемент q. Заметим, что Покрытие операторов исходного кода

    Покрытие путей потока управления

    Тестовый набор удовлетворяет критерию покрытия путей потока управления, если его выполнение хотя бы один раз проходит по каждому возможному пути в графе потока управления ведущему от точки входа до точки завершения работы. Этот критерий сильнее критерия покрытия ветвей потока управления. Каждый путь представляет собой последовательность переходов Покрытие путей потока управления, где Покрытие путей потока управления имеет вид Покрытие путей потока управления, при 1 ≤ i ≤ n . Упорядоченным подмножеством пути Покрытие путей потока управления назовём последовательность Покрытие путей потока управления такую, что Покрытие путей потока управления. Заметим, что в упорядоченном подмножестве пути конечный оператор перехода может не совпадать с начальным оператором следующего за ним перехода. Пусть есть два пути Покрытие путей потока управления и Покрытие путей потока управления , и пусть Покрытие путей потока управления причём Покрытие путей потока управления и Покрытие путей потока управления. Тогда пути Покрытие путей потока управления и Покрытие путей потока управления имеют общее упорядоченное подмножество размера m. Обозначим через length(R) длину пути R, а через Покрытие путей потока управления - максимальный размер общего упорядоченного подмножества путей Покрытие путей потока управления и Покрытие путей потока управления. Определим оценочную функцию для критерия покрытия путей потока управления следующим образом: Покрытие путей потока управления Здесь path(t) - это путь, по которому приходит управление при выполнении теста t. Значение в правой части равно количеству переходов в путях R и path(t), не входящих в максимальное общее упорядоченное подмножество этих путей. Оно равно 0 тогда и только тогда, когда пути R и path(t) совпадают.

    Покрытие ветвей потока управления

    Тестовый набор удовлетворяет критерию покрытия ветвей потока управления, если при выполнении этого тестового набора управление хотя бы один раз проходит по каждому ребру графа потока управления. Заметим, что любой тестовый набор, удовлетворяющий этому критерию, удовлетворяет также и критерию покрытия операторов исходного кода. Обратное утверждение, однако, неверно []. Элементами тестового покрытия являются переходы в графе потока управления. С каждым переходом в графе потока управления можно связать условие, при котором этот переход может быть выполнен. Переход от оператора q к оператору r, с которым связано условие p, обозначим как Покрытие ветвей потока управления. Для выполнения перехода Покрытие ветвей потока управления необходимо и достаточно, чтобы был выполнен оператор q, и чтобы после этого условие p обратилось в истину. Соответственно, для тестов, не покрывающих оператор q, в качестве оценочной подходит функция Покрытие ветвей потока управления, определённая уравнением , так как истинность условия p для оценки таких тестов роли не играет. Для тестов, покрывающих оператор q, функция Покрытие ветвей потока управления обращается в 0. Для таких тестов оценочная функция должна определять, насколько близок тест к тесту, для которого после выполнения оператора q будет истинным условие p. Таким образом, в общем виде оценочную функцию для критерия покрытия ветвей потока управления можно определить следующим образом: Покрытие ветвей потока управления (7) Значение функции Покрытие ветвей потока управления тем больше, чем ближе заданный тест к тесту, в котором условие p выполняется после выполнения оператора q. При этом функция Покрытие ветвей потока управления достигает своего максимума на тех и только тех тестах, в которых после выполнения оператора q выполняется условие p, то есть тех, которые покрывают переход Покрытие ветвей потока управления . Функцию Покрытие ветвей потока управления можно определять по-разному в зависимости от характера условия p. Если условие имеет форму простого (не)равенства Покрытие ветвей потока управления , где " Покрытие ветвей потока управления " обозначает одно из отношений " < ", " > ", " = ", " ≤ " или " ≥ ", то для определения функции Покрытие ветвей потока управления можно использовать значение Покрытие ветвей потока управления, например, следующим образом: Покрытие ветвей потока управления Если условие представляет собой конъюнкцию Покрытие ветвей потока управления, то в качестве значения функции Покрытие ветвей потока управления можно взять количество членов этой конъюнкции, принимающих значение "истина". В общем случае эффективно определить функцию Покрытие ветвей потока управления затруднительно.

    Простейший алгоритм

    Рассмотрим простейший генетический алгоритм для решения задачи 1. В качестве множества кандидатов возьмём множество Σ; в качестве оценочной функции возьмём метрику тестового покрытия Простейший алгоритм для заданной тестируемой системы S. Условием останова будет наличие в текущем поколении решения Простейший алгоритм, удовлетворяющего критерию . Структуру представления кандидатов, а также операторы кроссовера и мутации мы пока уточнять не будем. Заметим, что такой алгоритм допускает ситуацию, в которой критерий не выполняется ни для одного тестового набора из текущего поколения, но, тем не менее, выполняется для некоторого объединения тестовых наборов из текущего и предшествующих поколений. Иными словами, все тесты, необходимые для построения решения, уже найдены, но само решение ещё не построено. В этой ситуации алгоритм не способен эффективно построить искомое решение, целенаправленно объединив подходящие тесты из разных тестовых наборов. Причина проблемы в том, что при построении алгоритма не использовалась имеющаяся информация о структуре критерия . Заметим также, что каждое последующее поколение тестов формируется путём применения операторов кроссовера и мутации к тестам из предыдущего поколения. Если в предыдущем поколении не было ни одного теста, покрывающего некоторый элемент тестового покрытия q, то в последующем поколении такой тест может появиться только как результат кроссовера или мутации тестов, не покрывающих q. Как бы мы не определяли операторы кроссовера и мутации, нет никаких оснований полагать, что получить таким способом тест, покрывающий q, проще, чем при полностью случайной генерации. Из этих замечаний следует, что эффективность данного генетического алгоритма, вообще говоря, не выше, чем у полностью случайного алгоритма генерации тестов.

    и сопровождении программного обеспечения, значительная

    При разработке и сопровождении программного обеспечения, значительная часть усилий тратится на поиск и устранение ошибок. Самым распространённым методом поиска ошибок является тестирование, то есть процесс выполнения программ с целью обнаружения ошибок []. Здесь слово "программа" понимается в широком смысле, как любая запись алгоритма. В частности, программами являются отдельные процедуры, функции, классы и т.д. Процесс тестирования включает выполнение некоторого набора тестов и анализ полученных результатов. Тест - это последовательность обращений к тестируемой программе. Результатом выполнения теста является решение (вердикт) о том, отработала ли программа корректно или некорректно. Основной характеристикой тестового набора, определяющей качество тестирования, является класс возможных ошибок в программе, которые данный тестовый набор способен обнаружить. Для количественной оценки качества тестирования используются различные метрики тестового покрытия []. Для качественного тестирования необходимо построить полный тестовый набор, то есть набор, удовлетворяющий некоторому критерию полноты. Зачастую критерий полноты для тестового набора определяют через пороговое значение метрики тестового покрытия. Построение полного тестового набора для больших систем вручную может быть крайне трудоёмкой задачей. Автоматизация этого процесса позволяет существенно снизить затраты на тестирование. Существуют различные подходы к решению задачи автоматической генерации тестов: [,,]. Один из них основан на применении генетических алгоритмов []. Этот подход во многих случаях даёт хорошие результаты. К сожалению, его эффективность существенно зависит от используемого критерия полноты. Цель данной статьи - проанализировать некоторые широко распространённые критерии полноты тестового набора на их применимость при использовании генетических алгоритмов для генерации тестов.

    Применение генетических алгоритмов для генерации

    Применение генетических алгоритмов для генерации тестов предъявляет дополнительные требования к используемым критериям полноты тестового покрытия. Это вызвано тем, что критерий полноты используется не только для оценки качества сгенерированных тестов, но и непосредственно в процессе генерации для оценки близости полученных тестов к нужным результатам. Таким образом, нужно иметь оценочную функцию, позволяющую измерить эту близость, определить, насколько перспективными являются уже построенные тесты с точки зрения их использования в качестве основы для построения новых тестов. Кроме того, нужно иметь в виду, что тривиальные решения - функции вида "покрыто - 0, не покрыто - 1" - работают очень плохо. Для критериев, связанных с покрытием тех или иных путей в коде программы, удается построить достаточно удобные оценочные функции, основанные на количестве непокрытых дуг в пути, который нужно покрыть. В статье построены такие функции для некоторых широко распространённых критериев полноты тестового покрытия.

    Тестирование софта - статьи

     Конфигурационные параметры

    Другой вид структуризации тестовых наборов — определение и использование некоторых конфигурационных параметров, управляющих ходом тестирования, набором подключаемых компонентов и выполняемыми проверками. Во многих случаях достаточно статически устанавливаемых конфигурационных параметров, значения которых заносятся в конфигурационные файлы или передаются запускающей тестовой набор программе в качестве аргументов. Такие параметры могут определять глубину проводимого тестирования, набор выполняемых тестов (например, используя некоторый помечающий их квалификатор), объем проводимых проверок — некоторые проверки в тестах можно помечать как опциональные и выполнять, только если выставлено соответствующее значение некоторого параметра. Большей гибкости управления тестовым набором можно добиться, использую динамически устанавливаемые конфигурационные параметры, хотя они несколько повышают сложность анализа и сопровождения тестового набора. Приведем два примера их использования.
  • Такой параметр может своим значением определять присутствие или отсутствие в системе определенной функциональности, объявленной в стандарте опциональной. Если включение этой функциональности связано с использованием определенных конфигурационных параметров самой тестируемой системы или может быть выявлено при помощи простой проверки, специальный модуль теста может в начале его работы определить нужное значение соответствующего параметра теста и выставить его. При дальнейшем выполнении тестов значение такого параметра просто используется, как если бы он был статическим. При этом возникает дополнительный модуль тестовой системы, детектор конфигурации, работающий до запуска основных тестов и выявляющий текущую конфигурацию тестируемой системы.
  • Другое использование динамических параметров связано с повышением удобства анализа результатов тестирования сложной системы. В таких системах часто бывают функции, тщательное тестирование которых требует выполнения достаточно сложных сценариев, в которых тяжело разобраться, если возникает какая-либо ошибка.
    Примерами такой функциональности являются межпроцессное взаимодействие в операционных системах и зависящая от многих факторов обработка заголовков телекоммуникационных протоколов. Ошибка, связанная с полной неработоспособностью такой функции, может сделать результаты выполнения сложных тестов для нее совершенно непонятными — обычно бывает ясно, что ошибка есть, но более точная ее локализация требует значительных усилий. Во избежание подобных затрат можно предварять выполнение сложных тестов различных аспектов такой функциональности простыми тестами на работоспособность функции в целом. Сложные тесты должны выполняться только в том случае, если предшествовавшие им простые не нашли ошибок. Реализовать описанную процедуру можно с помощью динамически устанавливаемых по результатам простых тестов конфигурационных параметров. Аналогично, нагрузочные тесты имеет смысл выполнять только в том случае, если проверяемые ими элементы тестируемой системы выполняют свои основные функции правильно.

     Квалификаторы

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

     Модульность

    Самой мощной техникой структуризации тестового набора является выделение в нем модулей, ответственных за решение разнообразных задач, возникающих во время работы теста. Простейший способ выделения таких компонентов — определение групп тестовых вариантов, ответственных за проверку определенных элементов тестируемой системы или же некоторых аспектов требований к ней. Эти группы могут образовать иерархию, в которой группы верхнего уровня далее разбиваются на подгруппы, и т. п. При такой организации тестов выделение основных групп возможно еще на ранней стадии создания тестового набора, что позволяет эффективно распределять усилия по его разработке в большой команде. Однако более полезным с точки зрения обеспечения многократного использования одного и того же кода является выделение модулей внутри самих тестовых вариантов. Наиболее четко могут быть выделены следующие виды компонентов.
  • При тестировании достаточно широко используются компоненты, решающие задачи системного характера, не специфические именно для тестов. Они применяются для организации взаимодействия между другими компонентами теста и обеспечивают гибкое и точное управление ходом тестирования. К таким компонентам можно отнести планировщики хода теста  или диспетчеры, управляющие синхронизацией действий распределенных тестовых агентов, таймеры, используемые для отсчета времени, специализированные компоненты для мониторинга событий определенных видов, а также компоненты, отвечающие за запись информации в трассу теста.
  • Тестовые адаптеры (test adapters). Компоненты-адаптеры необходимы для привязки теста к тестируемым интерфейсам, если эти интерфейсы могут меняться без изменения их функциональности или если один и тот же тест предназначен для тестирования различных систем, реализующих одни и те же функции. Адаптер реализует абстрактный интерфейс, с которым работает тест, на основе одного из реальных интерфейсов, позволяя остальным компонентам теста не зависеть от конкретного синтаксиса реальных интерфейсов. Тестовые адаптеры — один из наиболее широко используемых видов компонентов теста.
    Адаптеры используются и в UniTESK под именем медиаторов , и при разработке тестов на TTCN для их привязки к конкретным тестируемым системам. В UML Testing Profile адаптеры не упоминаются, поскольку он определяет структуру абстрактного тестового набора, не зависящего от синтаксиса обращений к тестируемой системе.
  • Тестовые заглушки (test stubs). Заглушки используются при тестировании отдельных компонентов, модулей или групп модулей, для работы которых необходимы другие компоненты, если эти другие компоненты недоступны (еще не разработаны) или просто не используются, чтобы не усложнять тестирование и анализ его результатов. Заглушка реализует интерфейс одного из отсутствующих компонентов, заменяя его в ходе теста. В качестве результатов заглушки обычно возвращают произвольные значения — постоянные или сгенерированные случайным образом. Однако иногда используются "умные заглушки" (smart stubs), реализующие какую-то часть функций заменяемого модуля или специфические сценарии его работы. Поскольку заглушки часто возникают при модульном тестировании, в книге  различным видам заглушек посвящена отдельная глава. В сообществе, связанном с разработкой на основе тестирования (TDD), заглушки предпочитают называть «фиктивными объектами» (mock objects, mocks) или «тестовыми дубликатами» (test doubles). Более точно, в терминологии  тестовые дубликаты могут относиться к различным видам.
  • Фальшивый объект (fake object). Это простой тестовый дубликат, способный принимать обращения из тестируемой системы и выдавать какие-то результаты в ответ на них, все равно какие, лишь бы происходило корректное взаимодействие.
  • Собственно, заглушка (test stub). В рамках данного сообщества считается, что такие дубликаты должны уметь выдавать значения возвращаемых тестовой системе результатов в соответствии с целями теста, в котором они используются. Эти значения используются как неявные тестовые данные, с помощью которых тест приводит проверяемую систему в нужное состояние или оказывает на нее нужный набор воздействий.
  • Тестовый шпион (test spy). Это разновидность дубликата, которая умеет протоколировать сделанные к ней обращения из тестируемой системы, чтобы проверить их правильность в конце теста.
  • Фиктивный объект (mock object). Отличается от шпиона только тем, что выполняет проверки корректности производимых к нему обращений прямо в ходе работы теста, а не в его конце.
  • Генераторы тестовых данных. Роль их, как видно из названия, состоит в построении некоторого набора данных, обычно одного типа.


    Выгода от их использования появляется при необходимости создавать разнообразные объекты одного типа данных в разных тестах. Генераторы тестовых данных сложной структуры обычно делаются составными. Например, генератор значений комплексных чисел можно построить из двух генераторов действительных чисел — для вещественной и для мнимой частей. Генератор сложных документов удобно строить в виде системы из взаимодействующих генераторов отдельных частей таких документов — заголовков, отдельных полей и разделов, отдельных фраз и слов. В технологи UniTESK такого рода компоненты названы итераторами . В профиле UML для разработки тестов  они названы селекторами данных (data selector). Селекторы могут использовать контейнеры данных (data pool), хранящие определенный набор данных, выбор из которых может производиться селектором по дополнительным правилам.
  • Тестовые оракулы (test oracles) или просто оракулы. Тестовый оракул  — компонент, ответственный за вынесение вердикта о соответствии или несоответствии поведения системы требованиям. Работа оракула часто в большой степени зависит от конкретной тестовой ситуации, от сценария данного теста. Однако оракул для сложного сценария часто получается некоторой композицией проверок корректности его отдельных действий. Проверки корректности работы отдельных операций гораздо проще использовать многократно в разных тестах и удобнее применять для отслеживания более точной информации об ошибке, например, точного нарушенного требования или конкретной операции, выполненной с ошибкой. Поэтому удобно определить оракул типа событий или оракул операции, которые привязываются к событиям соответствующего типа или к вызовам определенной операции и выносят вердикт о том, насколько поведение системы при возникновении событий такого типа или при различных обращениях к этой операции соответствует требованиям. Другая возможная разновидность таких оракулов — ограничения целостности данных. Они относятся к некоторому типу данных и должны проверяться каждый раз, когда данные такого типа передаются в тестируемую систему или принимаются от нее.


    Поскольку данные сами по себе не активны, а лишь используются в вызываемых операциях и возникающих событиях, обычно ограничения целостности данных используются всеми оракулами операций и событий, в которых затрагивается соответствующий тип данных. Выделение таких модульных оракулов оправдано двумя факторами.
  • Требования формулируются, в основном, именно в отношении различных типов данных, типов событий или операций. Поэтому при переносе их в тесты достаточно удобно и с точки зрения возможных будущих модификаций, и для обеспечения прослеживаемости требований объединять требования к одному типу событий, типу данных или операции в один компонент.
  • Одна и та же операция, данные или события одного и того же типа могут в сложном тестовом наборе использоваться во многих тестах. Это одно из основных отличий «сложных» тестовых наборов от «простых» — во втором случае тестов, затрагивающих одну и ту же операцию, не так много, и проблема многократного использования кода не стоит так остро.
  • Кроме описанных выше оракулов, могут использоваться более сложные, композиционные оракулы. Они возникают в тех случаях, когда для вынесения вердикта о корректности поведения системы в некоторой ситуации требуется нетривиальный анализ многих разных его аспектов, который неудобно проводить в рамках одного компонента. Например, при оценке корректности данных достаточно сложной структуры, таких как XML-документы или программы на языках программирования, или даже документы, в которых может быть смешано несколько языков, проводить такую проверку в рамках одного компонента крайне неудобно — он становится крайне сложным, неудобным для модификаций и сопровождения. В таком случае ограничения целостности данных разбиваются на группы ограничений, относящихся только к определенным конструкциям, и правила корректного связывания конструкций, и для каждой такой группы можно иметь отдельный компонент, проверяющий ее ограничения. Общий оракул для документа в целом получается как некоторая композиция этих компонентов. Композиционные оракулы применяются и при тестировании распределенных систем.


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

     Проблемы организации тестовых наборов

    При тестировании всегда используется конечный набор тестов, даже в определении тестирования в SWEBOK  сказано, что оно проводится в конечном наборе ситуаций. Однако не менее важен другой аспект тестирования, также зафиксированный в этом определении, — необходимость сделать выводы о качестве проверяемой системы и потребность в том, чтобы такие выводы были достоверными. Чтобы обеспечить достоверности выводов по результатам тестирования, оно должно проводиться так, чтобы все существенные аспекты поведения тестируемой системы и все факторы, способные повлиять на его корректность, были хоть как-то затронуты. Для сложного программного обеспечения таких аспектов и факторов очень много, что и приводит и к большому количеству необходимых тестов, и к сложности их самих. Чаще всего тестовые наборы организуются в виде комплектов тестовых вариантов. Один тестовый вариант представляет собой последовательность действий, состоящую из следующих частей.
  • Сначала выполняются некоторые действия, нацеленные на создание определенной ситуации, приведение тестируемой системы в определенное состояние. Это преамбула тестового варианта.
  • Затем выполняется основной набор действий, правильность которых в заданной ситуации нужно проверить. Часто этот набор содержит ровно одно действие. Обычно ситуация и действия, которые в ней нужно выполнить, задаются целью тестирования (test purpose), для достижения которой и создается данный тестовый вариант. Результаты этих действий проверяются на предмет их соответствия требованиям к тестируемой системе. Правильность остальных действий в тестовом варианте тоже часто проверяется, но проверка корректности основного набора действий является главной целью его создания.
  • В конце выполняются некоторые операции, нацеленные на освобождение ресурсов, захваченных предшествовавшими действиями и, возможно, возвращение тестируемой системы в некоторое исходное состояние. Представление тестовой системы как набора тестовых вариантов сложилось достаточно давно, еще 30-40 лет назад.
    Оно довольно удобно для разработки тестов человеком — можно поставить определенную задачу (в данном случае представленную целью тестирования), а затем оформить ее решение в виде отдельной процедуры, которую можно использовать независимо от разработчика. Кроме того, такая организация тестов имеет следующие достоинства.
  • Каждый тестовый вариант сам по себе достаточно компактен и легко отделяется от остального набора тестов. Поэтому, если необходимо построить тестовый набор, нацеленный на проверку только определенных функций или определенной части интерфейса тестируемой системы, соответствующие тестовые варианты можно выделить и использовать отдельно от остальных. По той же причине достаточно просто выбросить часть тестовых вариантов из набора, если потребуется уменьшить его размер или ускорить выполнение тестов.
  • Тестовые варианты облегчают разработчикам анализ возникающих ошибок. Хотя основной целью тестирования является только обнаружение ошибок, а не их локализация, результаты тестирования только в виде вердиктов «ошибок нет» или «ошибки есть» никому не нужны на практике. В случае обнаружения ошибки разработчики системы надеются получить достаточно информации, чтобы легко восстановить и проанализировать возникшую ситуацию. Для этого тестовый вариант хорошо подходит — он представляет собой единый сценарий событий, достаточно компактен и формирует ровно одну основную ситуацию, так что для выполняющего отладку разработчика область анализа ограничена. Но у организации тестовой системы как набора тестовых вариантов есть и недостатки, связанные с многократно возросшей сложностью тестируемых систем и необходимостью постоянного обновления и развития наборов тестов.
  • В современных тестовых наборах тестовых вариантов часто очень много, иногда десятки и сотни тысяч. Таким количеством тестов уже нельзя эффективно управлять, если не вводить дополнительных уровней иерархии или каких-то классификаторов.
  • Очень часто в больших наборах тестов одни и те же их элементы используются многократно.


    Например, проверка реакции системы на одни и те же действия обычно одинакова, генерация тестовых данных для разных операций может выполняться одними и теми же процедурами. Все это приводит к потребности обеспечения многократного использования одних и тех же решений, которые стоит оформлять в виде отдельных компонентов. Таким образом выделяются тестовые оракулы — компоненты, чья задача состоит в проверке корректности поведения тестируемой системы в ответ на воздействия определенного типа, возникающие в различных тестах. Генераторы тестовых данных тоже часто становятся отдельными компонентами, которые можно использовать в разных тестах. Возможные виды компонентов тестовых систем обсуждаются в профиле универсального языка моделирования UML для разработки тестов (UML 2.0 Testing Profile [6-10]), в предложениях общей архитектуры инструментов тестирования, сформулированных в проекте AGEDIS [11-13], а также в работах, посвященных технологии UniTESK [14-16]. Техники выделения модулей в модульных тестах (unit tests), как общего характера, так и связанные с подходом к разработке на основе тестирования (test driven development, TDD), обсуждаются в книгах .
  • Развитие тестируемой системы или потребность в независимом развитии тестового набора (например, для повышения полноты тестирования, добавления проверки ранее игнорируемых свойств и т.п.) вынуждают вносить изменения в тесты. С точки зрения удобства внесения изменений неструктурированный набор тестовых вариантов представляет собой одно из самых худших решений. Без аккуратного анализа всех входящих в него тестов невозможно понять, какие требования к тестируемой системе и какие ее модули проверяются, а какие нет, какие для этого используются техники и пр. Крайне тяжело вносить изменения, при которых иногда требуется согласованно модифицировать десятки и сотни отдельных тестовых вариантов в связи с изменением лишь одного-двух требований к проверяемой системе.
  • Простые, неструктурированные наборы тестовых вариантов не всегда удобны при автоматической генерации тестов из каких-либо моделей.


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


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

     Техники организации тестовых наборов

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

     Заключение

    В статье рассмотрен ряд проблем, делающих необходимой более тонкую структуризацию сложных тестовых наборов, чем традиционное разбиение на тестовые варианты. Также рассказывается об основных техниках внесения дополнительной структуры в тестовый набор: выделении модулей, использовании квалификаторов и определении набора конфигурационных параметров. Наиболее легко с широко применяемыми сейчас подходами к разработке тестов сочетается использование набора квалификаторов для классификации тестов и указания связей между тестами и требованиями или элементами тестируемой системы. Однако большинство инструментов управления тестами поддерживает использование только предопределенного набора квалификаторов, которые не могут быть расширены. Пока только HP/Mercury TestDirector  имеет возможность добавления пользовательских квалификаторов, которые можно затем использовать для построения специфических отчетов — группировки или отбрасыванию результатов тестов по значениям квалификатора. Более сложно с имеющимися подходами к управлению тестами интегрируются использование системы конфигурационных параметров, а также выделение разнообразных модулей и их многократное использование в тестах. Конфигурационные параметры можно использовать только в виде дополнительных квалификаторов, что далеко не всегда удобно, особенно для динамически устанавливаемых параметров. Проблема выделения различных модулей в сложных тестах стоит наиболее остро. Только в последние 5 лет появилось несколько подходов  к разработке тестов, уделяющих этому аспекту достаточно внимания. В то же время, общим элементом этих подходов, да и то не всех, можно считать выделение заглушек, адаптеров, генераторов тестовых данных и некоторых общесистемных компонентов тестов. Выделение более разнообразного набора модулей, включающего оракулы отельных операций и событий, с одной стороны, является необходимым для повышения управляемости и гибкости современных сложных тестовых наборов, но с другой стороны, усложняет ряд традиционных видов деятельности — конфигурирование тестового набора и анализ обнаруживаемых им ошибок. Для разрешения возникающих проблем необходимо разработать новые техники решения соответствующих задач в модульных тестовых наборах.

    Тестирование софта - статьи

    Генерация тестовых данных сложной структуры с учетом контекстных ограничений

    А.В. Демаков, С.В. Зеленов, С.А. Зеленова,
    Труды Института системного программирования РАН

    Идея метода

    Множество вершин типа t изоморфно декартову произведению множеств объектов, которые могут быть её полями. Поясним это на примере. Пусть вершина типа t содержит:
  • Список list детей типа p
  • Ребёнка child типа s
  • Необязательного ребёнка opt_child типа r
  • Атрибут attr типа W Пусть Ml - множество списков вершин типа p, Ms - множество вершин типа s, Mr - множество вершин типа r, Mw - множество объектов типа W. Тогда множество вершин типа t изоморфно декартову произведению множеств Ml, Ms, Mw, Mr U ε (здесь ε - символ отсутствия ребёнка). Действительно, любой кортеж из этого декартова произведения - набор возможных значений полей вершины типа t. Это наблюдение обеспечивает общий метод генерации вершин типа t. Итак, для построения (конечного) множества вершин типа t необходимо построить конечные множества значений полей такой вершины и выбрать подмножество из декартова произведения этих множеств. До сих пор мы говорили о ситуации, когда нет никаких связей между атрибутами, а также нет никаких ограничений на значения полей вершины. Если такие связи или ограничения есть, то множества значений полей зависят друг от друга. То есть для того, чтобы определить множество допустимых значений для одного поля, нужно знать значения каких-то других полей. Если в строящемся дереве два атрибута разных вершин зависят друг от друга, то эта зависимость может быть описана через зависимость полей некоторого узла (см. рис. 5). Идея метода Процесс генерации теперь будет выглядеть несколько по-другому. Сначала нужно определить зависимости между значениями полей. Граф зависимостей должен быть ациклическим, иначе будет невозможно установить порядок построения полей вершины. Когда порядок построения полей определен, строим множество значений для независимого поля, выбираем из него какое-то значение и устанавливаем его в качестве соответствующего поля вершины. Затем строим множества значений для полей, зависящих от уже установленного поля, выбираем из них значения, устанавливаем выбранные значения в качестве соответствующих полей вершины и т.д.
    Таким образом, множества значений полей строятся в соответствии с уже построенным контекстом, поэтому результирующая вершина будет удовлетворять всем наложенным ограничениям. Для построения детей вершины можно использовать такой же метод генерации вершин соответствующего типа (см. рис 6; стрелками указано направление движения данных от одного генератора к другому). Идея метода Откуда могут возникать ограничения и связи между элементами дерева? Есть четыре основных источника.
  • Первый источник - это семантические требования на данные, такие как требование существования определения используемой переменной в языках программирования или требование уникальности значений какого-то атрибута в XML-документе.
  • Второй источник - это удовлетворение требований определённого критерия покрытия. В большинстве практических случаев множество всевозможных деревьев с вершинами определённых типов - это бесконечное множество, и мы, конечно же, не можем построить его целиком. Но это и не нужно, так как обычно такое множество можно разбить на конечное число классов таким образом, что в одном классе будут находиться элементы, "эквивалентные" с точки зрения целевой задачи. Такое разбиение называется критерием покрытия множества деревьев. Для одного и того же множества деревьев можно формулировать разные критерии покрытия; они будут зависеть от целевой задачи.
  • Третий источник - это ограниченность ресурсов. Часто даже в пределах одной задачи сформулировать для неё точный критерий покрытия невозможно или же слишком трудоёмко. Поэтому приходится брать более сильный критерий покрытия, разбивающий множество деревьев на более мелкие классы. При этом число классов может весьма сильно возрасти. О том, как можно бороться с этой проблемой, будет сказано немного позже.
  • Наконец, четвертый источник - это наличие рекурсии во множестве типов вершин T. Наличие рекурсии влечет необходимость её ограничения, то есть запрещения перебора деревьев с глубиной рекурсии, большей некоторого числа. При отсутствии ограничений на рекурсию множество допустимых деревьев бесконечно, а это значит, что генератор может войти в бесконечный цикл. Итак, в самом общем виде идея состоит в том, чтобы создать систему генераторов вершин.Генераторы образуют иерархическую структуру, соответствующую структуре типов вершин. При этом работа генератора поля вершины может зависеть от результата работы генераторов других полей этой же вершины в соответствии с требованиями, накладываемыми на строящееся дерево, то есть работа генераторов зависит от контекста строящейся вершины.

    Использование абстрактных моделей

    В описании схемы генерации почти ничего не говорилось об использовании аспектов состояний вершин. Между тем, помимо снижения вычислительных затрат, этот механизм даёт весьма важные преимущества при решении задач снижения количества генерируемых данных, достижения критериев покрытия и целенаправленной генерации. Для начала введём понятие абстрактной модели. Абстрактная модель объекта - это фактор-объект, получаемый с помощью абстрагирования от некоторых деталей. В качестве примера можно привести граф наследования классов в языках Java или C++. Граф наследования не содержит никакой информации о методах или полях классов, он является абстрактной моделью системы классов, для которой эта информация несущественна. Абстрактная модель может иметь разные представления, так, например, граф наследования можно представить в виде списка рёбер, или в виде последовательности чисел, или графически в виде дерева. То есть модель эквивалентна некоторой части объекта, но может не совпадать с ней (рис. 10). Использование абстрактных моделей По абстрактной модели можно построить много различных объектов, реализующих эту модель. Для графа наследования классов это можно сделать, меняя состав методов и полей. Опишем теперь, как можно использовать абстрактные модели для генерации атрибутированных деревьев. Пусть мы хотим построить группу деревьев, отвечающих некоторой абстрактной модели. Для этого мы немного расширим понятие состояния вершины. Добавим к нему новый аспект, который будет содержать описание абстрактной модели этой вершины. Автоматически в корневой вершине возникает элементарное ограничение на соответствие дерева имеющейся абстрактной модели. При переходе к генерации полей мы должны будем добавлять к их контексту абстрактные модели, моделирующие эти поля (они могут быть получены из абстрактной модели корневой вершины), а также ограничение соответствия значения поля его абстрактной модели (). Таким же образом можно моделировать не только дерево целиком, но и отдельные его части. С помощью техники абстрактных моделей можно эффективно уменьшать количество деревьев, увеличивать целенаправленность генерации. Действительно, при использовании "переборного" способа генерации для построения дерева, отвечающего абстрактной модели, требуется перебор многочисленных деревьев, не соответствующих этой модели, а при применении описанного способа требуемое дерево получается с самого начала. Кроме того, использование абстрактных моделей - это удобный способ ограничения рекурсии.

    Итераторы

    В определении элементарного ограничения указано, что для него нужно задать способ вычисления множества допустимых значений. Мы будем использовать для этого технику итераторов. В самом общем виде итератор - это объект, имеющий несколько последовательных состояний. Итератор может перевести себя в начальное состояние; находясь в каком-то состоянии, он может перевести себя в следующее состояние, а также ответить на вопрос, имеется ли у него следующее состояние. Итератором значений называется итератор, связанный с некоторым упорядоченным множеством. Итератор значений можно рассматривать как "указатель" на элемент множества. Для него имеются три операции: встать в начало множества, передвинуться на следующий элемент множества и выдать текущий элемент множества (вместо "выдачи" текущего элемента итератор значений может проделать с ним любую другую операцию). Помимо итераторов значений, существуют комбинаторы. Комбинатор - это итератор, который управляет другими итераторами, то есть указывает им, в каком порядке передвигаться по состояниям. Состоянием комбинатора обычно является кортеж состояний подотчётных ему итераторов. Пример. Пусть имеются итераторы значений i1, i2, связанные с множествами {a,b,c},{d,e} соответственно. И пусть мы хотим построить итератор значений декартова произведения этих множеств. Итераторы Используем для этого такой комбинатор (рис.8): его состояния - это тройка состояний итераторов i1, i2. Начальное состояние - тройка начальных состояний итераторов i1, i2. Таким образом, чтобы перейти к начальному состоянию, наш комбинатор переводит итераторы i1, i2 в начальное состояние. Чтобы перейти к следующему состоянию, комбинатор смотрит, в каком положении находится итератор i2. Если у этого итератора есть следующее состояние, то итератор в него переводится - получается новая пара состояний итераторов i1, i2, то есть новое состояние для комбинатора. Если у итератора i2 нет следующего состояния, то комбинатор смотрит, есть ли следующее состояние у итератора i1; если есть, то итератор i1 переводится в следующее состояние, а итератор i2 - в начальное, снова получается новая пара состояний итераторов i1, i2, а значит и новое состояние комбинатора.
    Если же следующего состояния нет ни у итератора i1, ни у итератора i2, то это означает, что и комбинатор не имеет следующего состояния. Другой важный пример - это комбинатор системы зависимых итераторов. Пусть нам нужно проитерировать пары букв и цифр, при этом для каждой буквы есть своё множество цифр, с которыми она может быть в паре. Пусть для буквы a - это множество {0, 1}, для буквы b - множество {2, 3, 5}, для буквы c - множество {4}. Заведём итераторы для множеств цифр и букв: итератор i1 для множества {a, b, c}, итераторы i2, i3, i4 - для множеств {0, 1}, {2, 3, 5}, {4} соответственно. Состоянием комбинатора теперь будет пара состояний двух других итераторов: итератора i1 и итератора, соответствующего состоянию итератора i1. Работа комбинатора проиллюстрирована рис. 9. При переходе итератора букв в следующее состояние в зависимости от этого состояния выбирается итератор цифр. При переходе к другой букве итератор цифр будет другим. Итераторы Можно определить комбинатор системы зависимых итераторов для итерации кортежей длины n. Точно так же, как было в примере с парами буква-цифра, итератор первого компонента кортежа последовательно проходит все свои состояния. В зависимости от его состояния выбирается итератор второго компонента кортежа, который также проходит все свои состояния (в это время состояние итератора первой компоненты кортежа менять нельзя). В зависимости от состояний итераторов первого и второго компонентов выбирается итератор третьего компонента кортежа и т.д. Итераторы, от которых зависит выбор других итераторов, будем называть осями зависимости. Так, итератор первого компонента кортежа - это первая ось зависимости, итератор второго компонента - это вторая ось зависимости и т.д. В приведенном выше примере одна ось зависимости - итератор букв.

    Представление тестовых данных

    Многие языки формального описания данных сложной структуры, по сути, описывают некоторое атрибутированное дерево. Среди таких языков - BNF грамматики формальных языков, XMLSchema для описания структуры XML-документов, ASN.1 для описания формата данных телекоммуникационных протоколов и многие другие. Действительно, сама структура записи информации в компьютере предполагает такую форму: всякий объект записывается в памяти как некая последовательность бит, в которой можно выделить подпоследовательности, в них - другие подпоследовательности и т.д. При этом выделенные подпоследовательности могут быть связаны между собой, например, равны друг другу. Основываясь на этом наблюдении, можно свести генерацию данных сложной структуры к генерации атрибутированных деревьев. При этом необходимо учитывать и горизонтальные связи, возникающие в сложных структурах. В этой статье мы опишем своеобразный framework для создания генераторов атрибутированных деревьев с учетом горизонтальных связей, или, иными словами, с учетом контекста элементов дерева.

    Предварительные сведения и понятия

    В начале введём понятие атрибутированного дерева. Как известно, дерево - это граф, в котором нет циклов. Мы будем рассматривать ориентированные деревья, то есть деревья, у которых имеется выделенная вершина - корень, и все ребра ориентированы от более близких к корню вершин к более дальним. Предварительные сведения и понятия Пусть A - некоторая вершина ориентированного дерева. Если она не является корнем, то существует единственная вершина B, из которой в A идет ребро. Вершину B мы будем называть родителем вершины A, а вершину A, соответственно, ребёнком вершины B. В отличие от родителя, детей у вершины может быть много. Деревья, которые мы рассматриваем, являются атрибутированными, то есть с каждой вершиной могут быть связаны её атрибуты - объекты произвольного типа. Мы будем рассматривать гетерогенные деревья, то есть деревья, у которых каждая вершина имеет определённый тип. Тип регламентирует, какие дети и атрибуты и какого типа могут быть у вершины данного типа. При этом все дети и атрибуты именуются. Кроме того, в определении типа может содержаться указание на то, что детей (или атрибутов) данного типа может быть несколько (то есть они образуют список), а также на то, что какой-то ребёнок или атрибут является необязательным, то есть может отсутствовать у вершины данного типа. Предварительные сведения и понятия Пример. Рассмотрим следующее определение типа вершины: вершины типа A должны содержать одного ребёнка типа B с именем child, необязательный атрибут name типа "строка", список list детей типа С. Примеры вершин типа A приведены на . Заметим, что на рис. нет атрибута name - это возможно, так как в определении типа этот атрибут заявлен необязательным, в то же время список list присутствует всегда, хотя может и не содержать элементов. Итак, в определении типа вершины должны быть описаны:
  • Атрибуты вершины: их имена и типы, а также то, являются ли они обязательными.
  • Списки атрибутов: их имена и типы элементов этих списков.
  • Дети вершины: их имена и типы, а также то, являются ли они обязательными.
  • Списки детей: их имена и типы элементов этих списков. Детей, атрибуты и списки вершины мы будем называть полями этой вершины. Пусть имеется конечное множество T типов вершин, замкнутое относительно использования типов (то есть все типы вершин, на которые есть ссылки в определениях типов из этого множества, определены). Далее мы будем рассматривать множество D деревьев, типы вершин которых принадлежат множеству T.

    Схема генерации

    В данном разделе описан один из возможных подходов, использующих определённые ранее понятия. Тот генератор, который мы опишем, является итератором, то есть он строит деревья последовательно, по одному. Как отмечалось раньше, для каждого типа вершин имеется свой генератор. Все эти генераторы появляются по требованию и зависят от уже построенной части дерева, то есть контекста. Итак, опишем генератор вершин типа t. Контекст для строящейся вершины типа t - это внешний параметр генератора.
  • Генератор создаёт вершину-заготовку, в которой поля не установлены.
  • По контексту вершины генератор вычисляет граф зависимостей между полями вершины. Для этого он использует информацию из элементарных ограничений.
  • Из графа зависимостей вычисляется порядок построения полей: f1 → f2 → … → fn.
  • Далее строится комбинатор зависимых генераторов значений полей, у которого первая ось зависимости - генератор независимого поля f1, вторая ось зависимости - генератор поля f2, зависимого от поля f1, и т.д. При этом генераторы полей при переходе в новое состояние проставляют значение соответствующего поля вершины.
  • Генератор значений конкретного поля fi создаётся так:
  • Если поле - ребёнок c типа s, то генератор строит для него контекст, основываясь на контексте вершины-родителя и значений полей, от которых зависит поле fi. Для полученного контекста строится генератор вершин типа s, который используется в качестве генератора значений поля.
  • Если поле - атрибут, то генератор значений для него строится так:
  • Для всех элементарных ограничений, наложенных на данный атрибут, вычисляются множества допустимых значений M1, M2, …, Mk. Как было сказано ранее, они задаются итераторами.
  • Строится итератор, перебирающий значения из объединения множеств M1, M2, …, Mk, которые удовлетворяют всем элементарным ограничениям, наложенным на данный атрибут. Этот итератор и будет генератором значений данного атрибута.
  • Если поле - список, то для него строится комбинатор зависимых итераторов, у которого первая ось зависимости - итератор длины списка, вторая - итератор первого элемента списка, третья - итератор второго элемента списка, и т.д. Этот комбинатор используется в качестве генератора значений списка. Генераторы значений элементов списка строятся так же, как и генераторы значений полей. Заметим, что при генерации должны своевременно обновляться все аспекты состояний вершин, которые могут использоваться в элементарных ограничениях для вычисления множества допустимых значений.

    Список литературы

    [1]
    [2]
    [3]
    [4]
    [5]
    [6]
    [7]Paul Purdom. A sentence generator for testing parsers. // Behavior and Information Technology. 12(3):366-375, 1972.
    [8]B.A. Wichmann, B.Jones. Testing ALGOL 60 compilers. // Software Practice and experience. 6 (1976) 261-270.
    [9]A. Celentano, S. Crespi Reghezzi, P. Della Vigna, C. Ghezzi, G. Granata, and F. Savoretti. Compiler Testing using a Sentence Generator. // Software - Practice and Experience. 10:897-918, 1980.
    [10]A.G. Duncan, J.S. Hutchison. Using Attributed Grammars to Test Designs and Implementation. // In Proceedings of the 5th international conference on Software engineering. 170-178, 1981.
    [11]А.К. Петренко и др. Тестирование компиляторов на основе формальной модели языка // Препринт института прикладной математики им. М.В Келдыша, № 45, 1992.
    [12]A. Kalinov, A. Kossatchev, A. Petrenko, M. Posypkin, V. Shishkov. Using ASM Specifications for Compiler Testing // Proceedings of Abstract State Machines - Advances in Theory and Applications 10th International Workshop, ASM 2003.
    [13]С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко. Генерация тестов для компиляторов и других текстовых процессоров // Программирование, Москва. - 2003. - 29. - № 2. - с. 59-69.
    [14]С.В. Зеленов, С.А. Зеленова. Генерация позитивных и негативных тестов парсеров // Программирование, том. 31, №6, 2005, 25-40.
    [15]С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко.
    [16]М.В. Архипова. Генерация тестов для модулей проверки статической семантики в компиляторах. // Труды ИСП РАН, т. 8, 2004, с. 59-76.
    [17]A.K. Petrenko. Specification Based Testing: Towards Practice // LNCS. - 2001. - 2244. - p. 287-300.
    [18]В.В. Кулямин, А.К. Петренко, А.С. Косачев, И. Б. Бурдонов. Подход UniTesK к разработке тестов. // Программирование, 29(6):25-43, 2003.
    [19]V. Kuliamin, A. Petrenko, A. Kossatchev, I. Bourdonov. UniTesK: Model Based Testing in Industrial Practice. // Proceedings of the 1-st European Conference on Model-Driven Software Engineering, Nurnberg, December 2003.


    Способ описания ограничений

    Чтобы как-то учитывать требования при построении дерева, необходимо организовать движение информации по дереву. Иными словами, при построении очередной вершины мы должны знать, в каком контексте находится эта вершина и какие ограничения должны быть наложены на поля вершины в соответствии с этим контекстом. В каждый момент построения дерева мы имеем какую-то конфигурацию вершин и атрибутов. Для конкретной вершины в этом недостроенном дереве мы можем определить её состояние - это то, что окружает вершину в данный момент. Фактически, это вся построенная часть дерева, но с точки зрения данной вершины. Рассмотрим понятие состояния на примере. На рис. 7 показано дерево с тремя вершинами типа Def (определение переменной). Чем отличается состояние, например, второй вершины от состояния первой? Состояния этих вершин различны, так как положения их в дереве различны. Из состояния (положения) первого определения видно, что в нём не могут быть использованы ссылки на переменные, так как до этой инструкции нет определённых переменных, а во втором определении можно использовать ссылку на переменную, поскольку к этому моменту уже есть определённая переменная v1. Способ описания ограничений При построении дерева обычно не требуется полностью знать состояние вершины. Нужны некоторые аспекты построенной части дерева, которые могут влиять на построение полей данной вершины. Эти аспекты будем называть аспектами состояния вершины. В приведённом выше примере для построения новой инструкции определения переменной нам нужен только один аспект состояния - список уже определённых переменных. Заметим, что при построении очередного элемента дерева все аспекты состояний вершин, для которых существенен этот элемент, должны измениться. Итак, множество значений поля вершины может зависеть от:
  • значений других полей этой вершины;
  • некоторых аспектов состояния вершины. Введём понятие элементарного ограничения. Это ограничение накладывается на поле вершины. Чтобы его определить, нужно задать:
  • тип вершины t;
  • поле f вершины типа t, на которое накладывается ограничение;
  • поля вершины f1, f2, …, fk, от которых зависит множество допустимых значений поля f;
  • аспекты состояния вершины S1, S2, … , Sn, от которых зависит множество допустимых значений поля f;
  • способ вычисления множества допустимых значений поля f;
  • функцию проверки значения поля f на соответствие определяемому ограничению;
  • функцию ограничения количества используемых значений поля f. Итак, в каждой вершине строящегося дерева определены:
  • аспекты состояния этой вершины, существенные для её построения;
  • ограничения на поля вершины. Систему аспектов состояния вершины и ограничений на её поля будем называть контекстом данной вершины. Контексты детей вершины зависят от контекста вершины. Контекст ребёнка, который зависит от какого-то поля, должен вычисляться с учётом построенного значения этого поля.

    Тестирование программных систем является важным

    Тестирование программных систем является важным компонентом всех проектов, связанных с разработкой программного обеспечения. По мере роста масштабов и сложности программных систем растет трудоемкость тестирования. Решить задачу повышения качества и сокращения расходов на тестирование можно за счет привлечения эффективных средств автоматизации разработки тестов. Одной из наиболее распространенных задач, возникающей при тестировании сложных программных систем, является генерация тестовых данных сложной структуры. Такие задачи характерны для тестирования систем, использующих интернет-протоколы, XML-технологии, SQL-интерфейсы баз данных; интерпретаторов и трансляторов языков программирования, спецификаций, командных языков, а также многих других видов программных систем. Существенным недостатком имеющихся в настоящее время инструментов для генерации тестовых данных сложной структуры для тестирования, например, приложений над базами данных [-], XML-приложений [-], компиляторов и других обработчиков формальных языков [-] является то, что в них очень узок предоставляемый спектр возможностей по управлению процессом генерации тестовых данных. Поэтому для достижения приемлемого качества тестирования приходится генерировать очень большие множества тестовых данных, что сопряжено с большими накладными расходами по использованию ресурсов, как на этапе генерации тестовых данных, так и на этапе анализа результатов прогонов тестов. В настоящей статье представлена технология автоматической генерации тестовых данных сложной структуры, которая предполагает возможность тонкой настройки процесса генерации тестовых данных и оптимизации этого процесса под особенности функциональности конкретного тестируемого приложения. В основу предлагаемой технологии положен опыт работ ИСП РАН по разработке тестовых данных и созданию автоматических генераторов тестовых данных на основе языковых моделей [-]. Предложенный в ИСП РАН подход к работе с данными сложной структуры является частью разработанной в ИСП РАН технологии UniTesK тестирования программного обеспечения на основе формальных спецификаций и моделей [-]. Этот подход основан на использовании формального описания данных сложной структуры.

    В статье представлена технология автоматической

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

    Тестирование софта - статьи

    Аннотация.

    Статья посвящена анализу дополнительных возможностей автоматизации функционального тестирования Web-приложений на основе технологии UniTesK. В ней рассматриваются существующие подходы к автоматизации функционального тестирования Web-приложений, обсуждаются их достоинства и недостатки. Кроме того, анализируются возможные варианты применения технологии UniTesK для тестирования данного класса приложений, и предлагается способ дополнительной инструментальной поддержки процесса разработки функциональных тестов.

    Дополнительная инструментальная поддержка

    Основной задачей, возлагаемой на инструментальную поддержку, является упрощение работы пользователя по созданию компонентов тестовой системы. Это достигается за счет дополнительной автоматизации шагов технологического процесса UniTesK с учетом специфики Web-приложений. Первый шаг технологического процесса UniTesK – анализ функциональности тестируемой системы – не предполагает инструментальной поддержки, однако для Web-приложений можно предложить способ выделения интерфейсных функций на основе автоматизированного анализа интерфейса Web-приложения. На шаге формализации требований пользователь может описывать требования в виде условий на различные атрибуты элементов интерфейса; эти условия могут строиться с использованием поддержки инструмента. Информации, собранной при автоматизации первого и второго шагов, оказывается достаточно для автоматического связывания интерфейсных функций с Web-приложением. Для шага разработки тестовых сценариев предлагаются дополнительные возможности по описанию его компонентов в терминах интерфейса Web-приложения. Последний шаг не требует дополнительной автоматизации, так как все инструменты семейства UniTesK уже предоставляют развитые средства выполнения тестов и анализа их результатов. При использовании дополнительной инструментальной поддержки процесс разработки тестов для функционального тестирования Web-приложений изменяется и состоит из следующих шагов: (1) создание модели Web-приложения; (2) создание тестового сценария; (3) выполнение тестов и анализ результатов. Первый шаг – создание модели Web-приложения – включает в себя определение интерфейсных функций, описание требований к ним и их связывание с Web-приложением, т.е. объединяет первые три шага технологии UniTesK. Основная задача этого шага – формализация требований к интерфейсным функциям – в отличие от второго шага технологии UniTesK может быть частично автоматизирована, а выделение интерфейсных функций и их связывание с Web-приложением происходит автоматически. Два последних шага соответствуют двум последним шагам технологии UniTesK и отличаются только уровнем автоматизации. Рассмотрим более подробно перечисленные выше шаги. На первом шаге должно быть получено описание модели, состоящее из набора интерфейсных функций и описания требований к ним.
    Интерфейсная функция соответствует воздействию на интерфейс Web-приложения, в результате которого происходит обращение к серверу. Элементы интерфейса, влияющие на параметры этого обращения, включаются в список параметров интерфейсной функции. Результатом воздействия является обновление интерфейса, которое описывается в требованиях к интерфейсной функции. Например, для HTML-формы регистрации можно описать интерфейсную функцию, соответствующую отправке данных формы на сервер, параметрами которой являются значения полей, входящих в форму. В описание требований включается информация о значениях, для которых успешно выполняется регистрация, и описываются ограничения на состояние обновленного интерфейса. Разбиение множества всех возможных состояний интерфейса Web-приложений на классы эквивалентности представляется в модели набором страниц. Это разбиение, которое, с одной стороны, используется при описании требований, с другой стороны, является основой для определения состояния в сценарии. По умолчанию разбиение на страницы осуществляется по адресу (URL) HTML-документа, отображаемого в Web-браузере. Однако пользователь может переопределить разбиение произвольным образом. При традиционном способе построения Web-приложения, когда для каждого URL определяется его собственный интерфейс, разбиение по умолчанию соответствует представлению пользователя о тестировании Web-приложения – пройти все страницы и проверить всю возможную функциональность на каждой из них. Также естественным для пользователя требованием к Web-приложению является требование перехода с одной страницы на другую в результате активизации гиперссылки или нажатия на кнопку HTML-формы. Такие требования легко описываются с помощью понятия страниц. В более сложных случаях, например, для описания требования к результату работы HTML-формы поиска по некоторому критерию, требования формулируются в виде условий на атрибуты элементов интерфейса. Автоматизация построения модели поддерживается в процессе сеанса работы с тестируемым Web-приложением.


    Пользователь осуществляет навигацию по страницам приложения, редактируя список интерфейсных функций и их параметров, который автоматически предлагается инструментом, и добавляет описания требований, формулируя их в виде проверки некоторых условий на атрибуты элементов интерфейса. Для формулировки проверок инструмент предоставляет возможность выделения интерфейсных элементов и задания условий на их атрибуты. В результате этого шага инструмент создает из модели Web-приложения компоненты тестового набора UniTesK, обычно появляющиеся на первых трех шагах технологии – это спецификационные классы, описывающие интерфейсные функции, и медиаторные классы, реализующие связь между интерфейсными функциями и тестируемой системой. В спецификационных классах для каждой интерфейсной функции создаются спецификационные методы, в которых описываются требования к поведению функций, сформулированные при работе инструмента. Для этого используются предусловия и постусловия спецификационных методов. В том случае, если средств, предоставляемых инструментом, недостаточно для описания функциональности Web-приложения, полученные спецификационные классы могут быть доработаны вручную. В медиаторных классах описывается связь между спецификацией и тестируемой системой. Для каждого спецификационного метода задается медиаторный метод, который преобразует вызов этого спецификационного метода в соответствующее воздействие на интерфейс Web-приложения. Это преобразование осуществляется следующим образом. Для каждого параметра спецификационного метода медиатор находит соответствующий ему элемент интерфейса Web-приложения и устанавливает значение его атрибутов в соответствии со значением параметра. Затем медиатор осуществляет воздействие требуемого типа на элемент интерфейса, соответствующий данной интерфейсной функции, и ожидает реакции Web-приложения. Как правило, реакция на воздействие заключается в обращении Web-браузера к Web-серверу и получении от него нового описания интерфейса. Медиатор дожидается завершения обновления состояния интерфейса и синхронизирует состояние модели. На втором шаге нужно получить описание тестов для Web-приложения.


    При создании тестов используется подход, предлагаемый технологией UniTesK. Согласно этому подходу тесты описываются в виде тестовых сценариев, в основе которых лежит алгоритм обхода графа переходов конечного автомата. Для каждого тестового сценария нужно выбрать подмножество интерфейсных функций, для тестирования которых предназначен данный сценарий. Для каждой выбранной функции нужно задать правила, по которым будут перебираться ее параметры. Кроме того, нужно задать правила идентификации состояний тестового сценария. Для автоматизации процесса создания тестового сценария предоставляется возможность определять итерацию для параметров выбранных интерфейсных функций на основе готовых вариантов перебора. Для этого могут использоваться библиотечные итераторы и итераторы, разработанные пользователем. Данные, которые вводились в ходе сеанса работы с инструментом на первом шаге, также могут быть включены в качестве дополнительных значений для заданной итерации. Кроме того, инструмент может предложить перебор параметров, построенный на основе анализа интерфейса Web-приложения. Например, использовать для итерации значения элементов выпадающего списка или же значения, которые берутся из разных интерфейсных элементов, например, расположенных в столбце некоторой таблицы. Для решения другой задачи, связанной с определением состояния тестового сценария, инструмент предоставляет средства для описания этого состояния в терминах интерфейса Web-приложения. В качестве состояния по умолчанию инструмент предлагает использовать страницу HTML-документа. Для более детального разбиения пользователю предоставляется возможность уточнить описание состояния, выбрав элементы интерфейса и указав условия на их атрибуты или на наличие этих элементов в интерфейсе. Кроме того, для описания состояния можно пользоваться описанием состояния сервера. Следует заметить, что информации, собранной на шаге построения модели Web-приложения, уже достаточно для создания тестового сценария. При создании тестового сценария по умолчанию используются все выделенные интерфейсные функции, для итерации параметров которых используются итераторы по умолчанию и значения, введенные пользователем в ходе сеанса построения модели, а в качестве состояния сценария используется страница HTML-документа. В качестве альтернативного способа создания тестов для Web-приложения можно использовать подход Capture & Playback.


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

    Функциональное тестирование Web-приложений на основе технологии UniTesK

    А.А. Сортов, А.В. Хорошилов Труды Института Системного Программирования РАН, 2004 г.

    Моделирование без привязки к уровню взаимодействия

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

    Моделирование на уровне Web-браузера

    Во втором варианте в качестве интерфейса системы рассматривается интерфейс, предоставляемый Web-браузером. В этом варианте интерфейсным функциям соответствуют воздействия на активные элементы интерфейса, в результате которых происходит обращение к Web-приложению. Такими элементами можно считать гиперссылки, кнопки форм и элементы интерфейса, для которых определена обработка на клиентской стороне, приводящая к обращению к серверу. Входные параметры этих функций – это данные, которые может вводить пользователь, например, при заполнении полей ввода или выборе значений из выпадающих списков и т.д. Таким образом, если рассматривать HTML-форму, то нажатию на кнопку, по которой отправляются данные, будет соответствовать интерфейсная функция, параметрами которой являются значения полей этой формы. Стоит оговориться, что в этом варианте в качестве тестируемой системы рассматривается Web-приложение в целом, включая как серверную, так и клиентскую часть. Однако внимание акцентируется на тестировании серверной части, и не ставится задача покрытия функциональности клиентской части. В этом варианте считается, что выходные параметры интерфейсных функций отсутствуют, поскольку результат воздействия описывается как изменение состояния Web-приложения. Состояние Web-приложения в этом варианте разбивается на состояние интерфейса, отображаемого Web-браузером, и состояние сервера. К состоянию интерфейса можно отнести текущую отображаемую страницу и состояние элементов на ней. К состоянию сервера относится, например, состояние базы данных, с которой работает Web-приложение, или данные, описывающие сеанс работы пользователя. Интерфейсные функции доступны не во всех состояниях, так как не во всех состояниях пользовательского интерфейса присутствуют элементы интерфейса, воздействия на которые соответствуют этим интерфейсным функциям. Например, некоторые элементы интерфейса становятся доступны только после авторизации, HTML-формы с полями и кнопками располагаются не на всех страницах Web-приложения.
    Условия доступности описываются в предусловии и определяются состоянием Web-приложения. Требования к функциональности описываются в постусловии в виде ограничений на состояние, в которое переходит Web-приложение в результате воздействия, описываемого интерфейсной функцией. Часто одному URL соответствуют пользовательские интерфейсы, содержащие одни и те же наборы интерфейсных функций. В таких случаях эти наборы удобно объединять в группы функций и специфицировать их как методы одного спецификационного класса. В других случаях одни и те же функции могут присутствовать сразу на нескольких интерфейсах, соответствующих разным URL. В этом случае интерфейсные функции удобно объединять в группы в зависимости от функционального назначения и специфицировать отдельно. Это позволяет получить хорошо структурированные спецификации, в которых дублирование описания функциональности сведено к минимуму. По сравнению с первым, этот вариант позволяет уделить большее внимание описанию именно функциональности Web-приложения, абстрагируясь от деталей обработки HTTP-запросов и ответов, что существенно упрощает моделирование работы пользовательских интерфейсов, обладающих сложным динамическим поведением. При тестировании вызов интерфейсной функции преобразуется в воздействия на соответствующие элементы пользовательского интерфейса Web-приложения. Для этого используются специальные средства (например, API Web-браузера), обеспечивающие доступ к внутреннему состоянию Web-браузера и позволяющие моделировать воздействия на элементы интерфейса. Эти же средства используются для получения информации о результатах воздействия, которые отражаются в состоянии пользовательского интерфейса. Такая информация необходима для проверки корректности поведения тестируемого Web-приложения. В отличие от первого варианта, для определения связи интерфейсных функций с Web-приложением требуются более сложные преобразования, так как обращение к одной интерфейсной функции может быть преобразовано в несколько последовательных воздействий на элементы пользовательского интерфейса.


    При ручном описании этих преобразований потребуется больше усилий; кроме того, тестировщик должен обладать знаниями и навыками работы со специальными средствами доступа к внутреннему состоянию Web-браузера. Тем не менее, в большинстве случаев этот процесс можно автоматизировать при помощи дополнительной функциональности инструментов тестирования, реализующих технологию UniTesK. При создании тестовых сценариев для этого варианта удобно опираться на предлагаемый технологией UniTesK метод построения тестовой последовательности, основанный на обходе графа переходов конечного автомата. Часто в качестве состояния автомата удобно рассматривать страницы с одним и тем же URL, а в качестве переходов между состояниями – вызовы интерфейсных функций. При построении тестового сценария основной задачей пользователя становится описание перебора параметров интерфейсных функций. При этом не нужно описывать все интересные пути обхода страниц Web-приложения; обход достижимых состояний автомата будет автоматически реализован при помощи обходчика. Описание перебора параметров интерфейсных функций (т.е. перебор данных на странице, например, перебор значений полей формы) может быть автоматизировано при помощи некоторого интерактивного режима работы пользователя с инструментом. Этот же интерактивный режим работы, в принципе, может быть использован и для создания сценариев тестирования на основе подхода Capture & Playback, если пользователь все-таки захочет вручную выделить некоторые пути обхода страниц. Итак, во втором варианте пользователю предлагается более «естественный» язык для создания теста – в терминах интерфейсных элементов Web-приложения и воздействий на них. Описания становятся более понятными, что, как следствие, снижает требования к квалификации разработчиков тестов. Недостатком этого варианта по сравнению с первым можно считать отсутствие возможности протестировать работу Web-приложения на некорректных HTTP-запросах.

    Моделирование поведения на уровне HTTP

    В первом варианте модель Web-приложения представляется одной интерфейсной функцией, описывающей HTTP-запрос к Web-приложению. Параметры этой функции – это параметры запроса (например, тип запроса (GET или POST), адрес (URL
    ), параметры заголовка и т.д.) и список данных, которые передаются Web-приложению. Выходные параметры функции формируются на основе HTTP-ответа, пришедшего от Web-приложения. Требования к функциональности формулируются в виде набора ограничений на выходные параметры в зависимости от значений входных параметров и модели состояния Web-приложения. Требования в большинстве случаев сильно различаются в зависимости от URL, поэтому спецификацию интерфейсной функции можно разделить на независимые спецификации нескольких интерфейсных функций, каждая из которых описывает поведение, характерное для конкретного семейства значений параметра, определяющего URL. Однако для больших Web-приложений такое описание требований получается громоздким и плохо структурируемым. Каждая интерфейсная функция соответствует определенному запросу с некоторыми параметрами; в процессе работы тестовой системы вызов интерфейсной функции преобразуется в посылку соответствующего HTTP-запроса серверу. HTTP-запрос строится на основе формальных правил преобразования параметров, поэтому шаг технологии UniTesK, на котором происходит связывание требований с реализацией, полностью автоматизируется. При создании тестовых сценариев для этого варианта наибольшую трудность представляет организация перебора параметров выделенных интерфейсных функций. Для каждого конкретного случая можно найти наиболее подходящий способ перебора параметров, однако это требует от тестировщика определенной квалификации и опыта. Следует отметить, что этот вариант позволяет тестировать Web-приложение на устойчивость к некорректным HTTP-запросам, так как можно имитировать ситуации, которые не должны появиться в процессе нормальной работы с Web-приложением посредством браузера.

    Направления дальнейшего развития

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

    Применение UniTesK для тестирования Web-приложений

    Технология UniTesK применялась для тестирования Web-приложений в нескольких проектах. В ходе разработки тестов выяснилось, что большая часть усилий тратится на создание медиаторов, которые переводят вызов интерфейсных функций в последовательность воздействий на Web-приложение. Анализ опыта показал, что большая часть этой работы может быть автоматизирована, опираясь на стандартизированную архитектуру пользовательского интерфейса Web-приложений. В принципе, эту особенность Web-приложений можно было бы использовать для автоматизации других шагов технологии UniTesK. В этом разделе будут рассмотрены варианты моделирования поведения Web-приложения в контексте возможной автоматизации шагов технологии UniTesK. Моделирование определяется способом выделения интерфейсных функций и способом построения модели состояния Web-приложения. Первый вариант основывается на стандартном протоколе HTTP, который служит для взаимодействия между Web-браузером и Web-приложением. Поведение Web-приложения рассматривается на уровне HTTP, и этот уровень считается единственно возможным для обращения к Web-приложению. Во втором варианте за основу берется формальное описание интерфейса в виде HTML, которое используется Web-браузером для организации взаимодействия с пользователем. В этом варианте взаимодействие с Web-приложением происходит только посредством Web-браузера. И, наконец, в третьем варианте поведение Web-приложения моделируется без привязки к конкретному способу обращения, основываясь лишь на тестируемой функциональности.

    Сравнение с другими подходами

    В предлагаемый подход, по возможности, были включены достоинства распространенных подходов и инструментов, предназначенных для функционального тестирования Web-приложений. Этот подход, как и другие, позволяет строить тесты, оперируя терминами интерфейса и действий с ним; полученные тесты могут быть расширены с использованием итерации данных. Предоставляются средства для автоматического прогона полученных тестов, анализа их результатов и генерации отчетов о покрытии и обнаруженных ошибках. Инструмент, реализующий данный подход, может быть использован для создания тестов в стиле Capture & Playback с сохранением всех достоинств этого подхода к тестированию. В реализации описанного подхода также присутствует и основное достоинство подхода Keyword Driven – хорошая архитектура тестового набора, обеспечивающая устойчивость при изменении интерфейса. В то же время предложенный подход отличается от других рядом преимуществ. В отличие от подхода Capture & Playback он позволяет автоматически генерировать тестовые последовательности, покрывающие различные тестовые ситуации. Таким образом, в этом подходе затраты на создание комплекта тестов того же качества становятся меньше. Другим достоинством данного подхода является возможность создания тестов для не полностью корректной реализации. Если в реализации присутствует ошибка, заключающаяся в том, что при некотором воздействии приложение переходит на некорректную страницу, то инструмент, реализующий Capture & Playback, ошибочно запоминает результат этого перехода как правильный. Добавление этого теста в автоматизированный тестовый набор будет возможно только после исправления ошибки – нужно, чтобы инструмент запомнил корректный результат. В описанном же подходе при описании модели можно явно указать ожидаемую страницу и ее параметры, после чего инструмент будет рассматривать любой другой результат как ошибочный и генерировать соответствующие отчеты об ошибках. Подход Keyword Driven не предоставляет возможности автоматизации разработки тестов и хорошо автоматизирует только выполнение тестов. Описанный подход автоматизирует как выполнение, так и разработку тестов. К перечисленным преимуществам можно добавить возможность автоматизированного анализа интерфейса Web-приложения, благодаря чему пользователь может выбирать интерфейсные функции из списка функций, автоматически найденных на странице. Кроме того, в дополнение к традиционным источникам данных для итерации параметров, таких как файлы различных форматов и базы данных, инструмент позволяет извлекать данные из интерфейса Web-приложения, что удобно использовать для организации динамически изменяемого перебора параметров.

    Сравнение возможной автоматизации вариантов моделирования

    Сравним рассмотренные варианты. Цель сравнения – выбрать вариант для тестирования Web-приложений, который обеспечивал бы наибольшую автоматизацию процесса разработки тестов при реализации инструментальной поддержки. Вместе с тем, инструментальная поддержка должна обеспечивать высокое качество функционального тестирования на уровне UniTesK, обладая при этом простотой использования и освоения. Каждый вариант мы будем рассматривать, исходя из следующих критериев. Во-первых, рассмотрим каждый вариант с позиции уровня автоматизации каждого шага технологического процесса UniTesK (последний шаг – выполнение тестов и анализ результатов – не рассматривается, так как инструменты UniTesK уже автоматизируют этот шаг в достаточной степени). Во-вторых, рассмотрим уровень абстракции получаемой в каждом варианте модели требований, поскольку более абстрактные модели облегчают повторное использование, а также обеспечивают простоту разработки и сопровождения получаемых тестов. В-третьих, рассмотрим предъявляемые каждым вариантом требования к квалификации пользователя, что обобщает простоту использования и обучения работе с инструментом. Результаты сравнения представлены в Таблице 1. Таблица 1. Сравнение рассмотренных вариантов возможного моделирования Web-приложений с использованием технологии UniTesK. Прокомментируем результаты, приведенные в таблице. Результатом анализа функциональности тестируемой системы является список интерфейсных функций с входными и выходными параметрами. При моделировании без привязки к уровню взаимодействия этот выбор осуществляется пользователем полностью вручную, исходя из представлений о функциональности тестируемой системы. В других вариантах моделирования можно предложить формальный способ выделения интерфейсных функций на основе анализа интерфейса Web-приложения (может быть, с использованием интерактивного режима). Формализация требований трудно поддается автоматизации, как и всякий переход от неформального к формальному, но в рамках моделирования на уровне Web-браузера можно предложить автоматизацию формализации некоторой части требований с использованием интерактивного режима. Проблема связывания требований с реализацией решается посредством автоматической генерации медиаторов при моделировании как на уровне Web-браузера, так и на уровне HTTP.
    Для моделирования без привязки к уровню взаимодействия для создания медиаторов можно предложить воспользоваться интерактивным режимом работы с инструментом, при котором для каждой интерфейсной функции определяется последовательность воздействий на элементы пользовательского интерфейса Web-приложения. При разработке тестовых сценариев можно пользоваться стандартными средствами инструментов семейства UniTesK, которые позволяют автоматизировать процесс задания основных элементов тестового сценария. Однако для моделирования на уровне Web-браузера можно предложить дополнительные средства для организации перебора параметров и идентификации различных тестовых ситуаций, например, извлекать данные для перебора из интерфейса Web-приложения или выбирать элементы интерфейса, идентифицирующие состояние. При моделировании на уровне HTTP описание функциональности и тестирование осуществляется в терминах HTTP-запросов, что заставляет пользователя разбираться с большим объемом технической информации, повышая тем самым требования к его квалификации. Моделирование на уровне Web-браузера позволяет описывать функциональность и проводить тестирование в терминах элементов пользовательского интерфейса и воздействий на них, что является естественным языком для разработчика тестов. Описание, выполненное в таких терминах, позволяет отразить требования к функциональности на приемлемом уровне абстракции, не сосредотачиваясь на деталях технологий, лежащих в основе Web-приложения. Третий вариант не ограничивает пользователя в свободе выбора уровня описания функциональности, но вместе с тем не предоставляет дополнительных возможностей по автоматизации, возлагая всю работу на пользователя, что требует от него определенных навыков и опыта. Описанные варианты моделирования были опробованы в ходе тестирования Web-приложений с использованием технологии UniTesK. Все шаги технологического процесса были реализованы с использованием инструментов UniTesK без дополнительной автоматизации, которая упоминалась в обзоре вариантов моделирования.Анализ процесса разработки показал необходимость и подтвердил выводы о возможности дополнительной автоматизации шагов технологии. Кроме того, опыт передачи тестовых наборов, разработанных с помощью технологии UniTesK, в реальное использование показал, что моделирование без привязки к уровню взаимодействия требует от разработчиков и последующих пользователей хорошего знания технологии UniTesK. В то же время, моделирование на уровне Web-браузера более естественно воспринималось пользователями, тестирующими Web-приложения и не владеющими технологией UniTesK. Оценивая рассмотренные варианты с этих позиций, можно сказать, что моделирование на уровне Web-браузера является наиболее подходящим для дальнейшей автоматизации и дополнительной инструментальной поддержки. В следующем разделе мы остановимся более подробно на реализации дополнительной инструментальной поддержки этого подхода.

    Существующие подходы к функциональному тестированию Web-приложений

    Самым распространенным является подход, называемый Capture & Playback (другие названия – Record & Playback, Capture & Replay). Суть этого подхода заключается в том, что сценарии тестирования создаются на основе работы пользователя с тестируемым приложением. Инструмент перехватывает и записывает действия пользователя, результат каждого действия также запоминается и служит эталоном для последующих проверок. При этом в большинстве инструментов, реализующих этот подход, воздействия (например, нажатие кнопки мыши) связываются не с координатами текущего положения мыши, а с объектами HTML-интерфейса (кнопки, поля ввода и т.д.), на которые происходит воздействие, и их атрибутами. При тестировании инструмент автоматически воспроизводит ранее записанные действия и сравнивает их результаты с эталонными, точность сравнения может настраиваться. Можно также добавлять дополнительные проверки – задавать условия на свойства объектов (цвет, расположение, размер и т.д.) или на функциональность приложения (содержимое сообщения и т.д.). Все коммерческие инструменты тестирования, основанные на этом подходе, хранят записанные действия и ожидаемый результат в некотором внутреннем представлении, доступ к которому можно получить, используя или распространенный язык программирования (Java в Solex [2]), или собственный язык инструмента (4Test в SilkTest [3] от Segue, SQABasic в Rational Robot [4] от IBM, TSL в WinRunner [5] от Mercury). Кроме элементов интерфейса, инструменты могут оперировать HTTP-запросами (например, Solex [2]), последовательность которых также может записываться при работе пользователя, а затем модифицироваться и воспроизводиться. Основное достоинство этого подхода – простота освоения. Создавать тесты с помощью инструментов, реализующих данный подход, могут даже пользователи, не имеющие навыков программирования. Вместе с тем, у подхода имеется ряд существенных недостатков. Для разработки тестов не предоставляется никакой автоматизации; фактически, инструмент записывает процесс ручного тестирования.
    Если в процессе записи теста обнаружена ошибка, то в большинстве случаев создать тест для последующего использования невозможно, пока ошибка не будет исправлена (инструмент должен запомнить правильный результат для проверки). При изменении тестируемого приложения набор тестов трудно поддерживать в актуальном состоянии, так как тесты для изменившихся частей приложения приходится записывать заново. Этот подход лучше всего использовать для создания прототипа теста, который впоследствии может служить основой для ручной доработки. Одна из возможных доработок – параметризация теста для проверки тестируемого приложения на различных данных. Этот подход называется тестированием, управляемым данными (Data Driven [6, 7]). Основное ограничение – перебираемые данные не должны изменять поведение тестируемого приложения, поскольку проверки, записанные в тестовом сценарии, не подразумевают какой-либо анализ входных данных, т.е. для каждого варианта поведения нужно создавать свой сценарий тестирования со своим набором данных. Некоторые инструменты, реализующие Capture & Playback, предоставляют возможность по перебору данных (например, e-Tester [8] от Empirix); кроме того, над большинством распространенных инструментов существуют надстройки (Convergys Auto Tester [6] – надстройка над WinRunner). Описанные подходы основываются на построении тестов с использованием тестируемого приложения. В подходе KeywordDriven [7, 9] предпринимается попытка сделать процесс создания тестов независимым от реализации. Суть подхода заключается в том, что действия, выполняемые в ходе тестирования, описываются в виде последовательности ключевых слов из специального словаря («нажать», «ввести», «проверить» и т.д.). Специальный компонент тестовой системы переводит эти слова в воздействия на элементы интерфейса тестируемого приложения. Таким образом, никакого программирования для создания тестов не нужно. Единственное, что нужно менять при изменении интерфейса, – это компонент, который отвечает за перевод слов из «словаря» в последовательность воздействий на приложение.


    Комплект тестов может разрабатываться пользователями, не владеющими навыками программирования, однако для поддержания комплекта в рабочем состоянии программирование все-таки необходимо. В качестве примера инструмента, поддерживающего такой подход к разработке тестов, можно привести Certify [10] от WorkSoft, в котором поддерживается библиотека функций для работы с каждым компонентом интерфейса (окна, гиперссылки, поля ввода и т.д.) и предоставляется язык воздействий на эти элементы (InputText, VerifyValue, VerifyProperty и т.д.). Основные преимущества этого подхода заключаются в том, что он позволяет создавать тесты, не дожидаясь окончания разработки приложения, руководствуясь требованиями и дизайном интерфейса. Созданные тесты можно использовать как для автоматического выполнения, так и для ручного тестирования. Основной недостаток этого подхода – отсутствие автоматизации процесса разработки тестов. В частности, все тестовые последовательности разрабатываются вручную, что приводит к проблемам, как на стадии разработки, так и на стадии сопровождения тестового набора. Эти проблемы особенно остро проявляются при тестировании Web?приложений со сложным интерфейсом.

    Технология UniTesK

    Большинство проблем, присущих рассмотренным подходам разработки тестов, решены в технологии UniTesK, разработанной в Институте системного программирования РАН. Технология хорошо себя зарекомендовала при функциональном тестировании разнообразных систем (ядро операционной системы, стеки протоколов, компиляторы). Опыт применения технологии для тестирования Web-приложений показал, что UniTesK может служить хорошей базой для тестирования такого класса приложений. В этом разделе мы остановимся на основных моментах технологии UniTesK (более детальную информацию о ней можно найти в [11, 12, 13, 14]); в последующих разделах рассмотрим особенности применения технологии для тестирования Web-приложений. Технология UniTesK – это технология разработки функциональных тестов на основе моделей, которые используются для оценки корректности поведения целевой системы3 и автоматической генерации последовательностей воздействий, далее называемых тестовыми последовательностями. Оценка корректности поведения целевой системы осуществляется в рамках следующего представления об устройстве интерфейса целевой системы. Предполагается, что целевая система предоставляет набор интерфейсных функций, и все воздействия на нее осуществляются только через вызовы этих интерфейсных функций. Параметры воздействий, передаваемые целевой системе, описываются входными параметрами интерфейсной функции. Результат воздействия (реакция системы) представляется выходными параметрами, значения которых могут зависеть от истории взаимодействий целевой системы с окружением. Информация об истории моделируется внутренним состоянием целевой системы. Внутреннее состояние влияет на выходные параметры интерфейсных функций и может изменяться в результате их работы. Следует заметить, что в рамках данной статьи для тестирования Web-приложений рассматривается представление, в котором воздействия на целевую систему и получение ее реакции на это воздействие (выходные параметры интерфейсной функции) рассматриваются как атомарное действие.
    Под атомарностью действия понимается, что следующее воздействие можно произвести только после получения реакции на предыдущее. Технология UniTesK также позволяет представлять целевую систему и как систему с отложенными реакциями, т.е. как систему, разрешающую воздействие до получения всех реакций на предыдущее. Корректность поведения целевой системы оценивается с точки зрения его соответствия поведению некоторой «эталонной» модели, называемой спецификацией. В технологии UniTesK эталонная модель описывается неявно в виде требований к поведению каждой интерфейсной функции. При задании эталонной модели можно описывать функции и их параметры в достаточно обобщенном виде, отвлекаясь от несущественных подробностей. Основными компонентами тестовой системы являются итератор тестовых воздействий, оракул и медиатор. Задачей итератора тестовых воздействий, работающего под управлением обходчика, является построение тестовой последовательности, обеспечивающей заранее определенный критерий тестового покрытия. Задачей оракула является оценка корректности поведения целевой системы. Задача медиатора – преобразовывать тестовое воздействие в последовательность реальных воздействий на целевую систему и на основании доступной информации построить новое модельное состояние целевой системы после вызова. В качестве языка описания компонентов тестовой системы используются спецификационные расширения обычных языков программирования, таких как C# и Java. В этих расширениях реализованы три вида специальных классов, предназначенных для описания компонентов тестовой системы. Из спецификационных классов генерируются оракулы, из медиаторных – медиаторы, а из сценарных – итераторы тестовых воздействий. В спецификационных классах описываются спецификационные методы, каждый из которых соответствует некоторой интерфейсной функции и содержит формальное описание требований к поведению целевой системы при взаимодействии с ней через данную интерфейсную функцию. Сценарные классы предназначены для описания тестовых сценариев, содержащих описание единичных воздействий и правил итерации их параметров.


    Медиаторы генерируются на основе медиаторных классов, которые связывают интерфейсные функции с воздействиями на целевую систему. Схема работы тестовой системы, разработанной по технологии UniTesK, представлена на рисунке 2. Основной шаг работы тестовой системы устроен следующим образом. Обходчик выбирает очередное сценарное воздействие. Сценарное воздействие содержит несколько обращений к целевой системе, представляющих собой вызов интерфейсной функции с определенным набором значений входных параметров. Вызов интерфейсной функции передается оракулу, который, в свою очередь, передает его медиатору. Медиатор преобразует вызов интерфейсной функции в последовательность действий над тестируемой системой, получает результат этих действий от тестируемой системы и преобразует его в значения выходных параметров интерфейсной функции. Медиатор также синхронизирует модель состояния тестируемой системы, используемую оракулом для оценки корректности поведения, с ее реальным состоянием. Оракул, зная значения входных и выходных параметров интерфейсной функции, а также состояние целевой системы, оценивает корректность ее поведения в рамках данного взаимодействия. Если вердикт оракула является отрицательным, то это значит, что тестовая система нашла несоответствие между поведением целевой системы и требованиями к ней. По завершении выполнения одного шага управление возвращается обходчику, который выбирает следующее сценарное воздействие или принимает решение о прекращении тестирования. Все события, происходящие в процессе тестирования, находят свое отражение в трассе теста. На основе трассы генерируются различные отчеты, помогающие в анализе результатов тестирования. Процесс разработки тестов с помощью технологии UniTesK можно представить в виде следующей последовательности шагов4: (1) анализ функциональности тестируемой системы; (2) формализация требований к функциональности; (3) связывание формализованных требований с реализацией; (4) разработка сценариев тестирования; (5) исполнение тестов и анализ результатов. Рассмотрим каждый из этих шагов более подробно. В результате анализа функциональности необходимо определить интерфейс тестируемой системы.


    Для этого требуется выделить функции, предоставляемые системой, и для каждой такой функции определить, что выступает в качестве ее входных и выходных параметров. На этапе формализации требований для каждой интерфейсной функции, выявленной на предыдущем шаге, необходимо описать ограничения на значения выходных параметров в зависимости от значений входных параметров и истории предыдущих взаимодействий с тестируемой системой. Для этого в технологии UniTesK используется широко известный подход программных контрактов [15]. В основе этого подхода лежат инварианты данных, а также предусловия и постусловия интерфейсных операций. При связывании требований с реализацией необходимо описать, как каждая интерфейсная функция отображается на реализацию тестируемой системы. В рамках этого отображения требуется установить правила преобразования вызовов интерфейсных функций в последовательность действий над тестируемой системой, а также правила построения модели состояния тестируемой системы. Для систем с прикладным программным интерфейсом, когда взаимодействие через интерфейсную функцию соответствует вызову функции тестируемой системы, установление такого отображения может быть автоматизировано при помощи интерактивных шаблонов, предоставляемых инструментами семейства UniTesK. Тестовые сценарии строятся на основе конечно-автоматной модели целевой системы, которая используется для динамической генерации последовательностей тестовых воздействий. Сценарий определяет, что именно рассматривается как состояние автомата, и какие интерфейсные функции с какими наборами аргументов могут быть вызваны в каждом состоянии. Алгоритмы UniTesK обеспечивают вызов каждой интерфейсной функции с каждым набором ее параметров в каждом достижимом состоянии. Для описания сценария необходимо задать способ идентификации состояний и способ итерации входных воздействий в зависимости от текущего состояния. Инструменты семейства UniTesK предоставляют интерактивные шаблоны, которые позволяют упростить разработку тестовых сценариев. На заключительном этапе технологического процесса происходит выполнение созданных тестов, автоматическая генерация отчетов о результатах тестирования и анализ этих результатов.На основе анализа принимаются решения о создании запросов на исправление дефектов, обнаруженных в тестируемой системе, или о доработке самих тестов с целью повышения уровня покрытия.

    приложениями мы будем называть любые

    В данной статье Web- приложениями мы будем называть любые приложения, предоставляющие Web-интерфейс. В настоящее время такие приложения получают все большее распространение: системы управления предприятиями и драйверы сетевых принтеров, интернет-магазины и коммутаторы связи – это только небольшая часть приложений, обладающих Web интерфейсом. В отличие от обычного графического пользовательского интерфейса Web-интерфейс отображается не самим приложением, а стандартизированным посредником – Web-браузером. Web-браузер берет на себя все взаимодействие с пользователем и обращается к Web-приложению только в случае необходимости. Описание пользовательского интерфейса предоставляется браузеру в стандартном представлении, в роли которого обычно выступает HTML [1]. На рисунке 1 представлен процесс работы типичного Web-приложения. Пользователь взаимодействует с приложением посредством Web-браузера, который при необходимости обращается с запросом к Web-приложению, чтобы выполнить ту или иную операцию. Результатом такого обращения является полное или частичное обновление интерфейса приложения, отображаемого в браузере. При обращении к Web-приложению браузер посылает запрос по одному из протоколов доступа (HTTP, HTTPS или др.). Web-приложение обрабатывает запрос и возвращает браузеру описание обновленного интерфейса. Web-приложения в первую очередь характеризуются тем, что их пользовательский интерфейс имеет стандартизированную архитектуру, в которой: 1) для взаимодействия с пользователем используется Web-браузер; 2) взаимодействие с пользователем четко разделяется на этапы, в течение которых браузер работает с одним описанием интерфейса; 3) эти этапы разделяются однозначно выделяемыми обращениями от браузера к приложению; 4) для описания интерфейса применяется стандартное представление ((HTML); 5) коммуникации между браузером и приложением осуществляются по стандартному протоколу (HTTP). Web-приложения можно рассматривать как клиент/серверные приложения, в которых функциональность реализуется как на серверной, так и на клиентской стороне.

    реализованная на клиентской стороне, как

    Функциональность, реализованная на клиентской стороне, как правило, сводится к проверке вводимых данных и реализации дополнительных возможностей интерфейса, что реализуется путем использования скриптовых возможностей, встроенных в HTML (использование Java-script, VBScript и т.д.). В этой статье мы будем рассматривать функциональное тестирование именно серверной части, оставляя рассмотрение функциональности клиентской части в качестве темы будущих исследований. В этом случае основной интерес представляют взаимодействия браузера с сервером. Эти взаимодействия хорошо формализованы, поскольку осуществляются на основе протокола HTTP. Четкая формализация взаимодействий может служить основой для автоматизации функционального тестирования. С другой стороны, представление интерфейса в виде HTML также четко формализовано. Кроме того, в этом описании интерфейса можно выделить действия, приводящие к взаимодействию с сервером. Эти действия связаны с воздействиями на кнопки, активизацией гиперссылок и реакциями на различные события, закодированные в скриптовой части интерфейса. Таким образом, формальное описание интерфейса Web-приложений также предоставляет широкие возможности для автоматизации функционального тестирования. Но как наилучшим образом использовать потенциал, предоставляемый стандартизированной архитектурой интерфейса Web-приложения? Исследованию этого вопроса и посвящена данная работа. Статья построена следующим образом. В разделе 2 мы рассмотрим существующие подходы к автоматизации функционального тестирования Web-приложений. В разделе 3 будут представлены основные сведения о технологии автоматизации тестирования на основе моделей UniTesK. Затем мы проанализируем различные варианты моделирования Web-приложений в рамках технологии UniTesK и по результатам этого анализа представим расширение базовой технологии UniTesK, специально адаптированное для функционального тестирования Web-приложений. В заключение, мы рассмотрим пути дальнейшего развития предложенного подхода.

    Тестирование софта - статьи

    Автоматизация процесса тестирования при помощи методологии и инструментальных средств IBM Rational

    Новичков Александр, Ематин Виктор, Закис Алексей, Шкляева Наталья, Подоляк Ольга


    Что такое тестирование

    Прежде, чем разбираться с деталями, необходимо определить, что же такое тестирование. Даже этот, казалось бы простой вопрос не так прост. Разные источники определяют тестирование его по-разному. В соответствие с RUP Тестирование — одна из дисциплин RUP. Она ориентирована в первую очередь на оценку качества с помощью следующих методов:
  • поиск и документирование дефектов качества;
  • общие рекомендации относительно качества;
  • проверка выполнения основных предположений и требований на конкретных примерах;
  • проверка, что продукт функционирует так, как было запроектировано;
  • проверка, что требования выполнены соответствующим образом.
  • В соответствие с IEEE Std 829-1983 Тестирование — это процесс анализа ПО, направленный на выявление отличий между его реально существующими и требуемыми свойствами (дефект) и на оценку свойств ПО. По ГОСТ Р ИСО МЭК 12207-99 в жизненном цикле ПО определены среди прочих вспомогательные процессы верификации, аттестации, совместного анализа и аудита. Процесс верификации является процессом определения того, что программные продукты функционируют в полном соответствии с требованиями или условиями, реализованными в предшествующих работах. Данный процесс может включать анализ, проверку и испытание (тестирование). Процесс аттестации является процессом определения полноты соответствия установленных требований, созданной системы или программного продукта их функциональному назначению. Процесс совместного анализа является процессом оценки состояний и, при необходимости, результатов работ (продуктов) по проекту. Процесс аудита является процессом определения соответствия требованиям, планам и условиям договора. В сумме эти процессы и составляют то, что обычно называют тестированием. Как не запутаться в этих определениях, на какое из них лучше опираться в ежедневной деятельности — этому будет посвящен следующий выпуск. (следите за обновлениями)

    Инструментальная поддержка RUP. Инструменты тестирования и смежные инструменты

    Внедрение любой методологии существенно упрощается, если есть поддерживающий ее набор инструментов, позволяющий избежать тяжелого рутинного ручного труда. Методология RUP в этом смысле является одной из наиболее «благополучных», поскольку ее поддерживает набор инструментов IBM Rational. Ниже перечислены инструментальные средства непосредственно предназначенные для автоматизации процесса тестирования: — IBM Rational PurifyPlus (набор, состоящий из Puify, PureCoverage, Quantify);
    — IBM Rational TestManager;
    — IBM Rational Robot;
    — IBM Rational ManualTest. В таблице 1 приведены краткие характеристики инструментов тестирования. Таблица 1 - Продукты тестирования IBM Rational Software Group
    Название продукта Краткая характеристика
    IBM Rational Purify Инструмент для отслеживания трудно обнаружимых ошибок времени исполнения (например, утечка памяти, выходы за границы массивов при чтении и записи).
    IBM Rational PureCoverage Инструмент для отслеживания кода тестируемого приложения (какие именно классы, методы, вызовы функций были протестированы, а какие нет). Это позволяет автоматизировать процесс измерения метрик полноты тестирования (охвата), если для оценки полноты тестирования используется охват кода, а не охват функциональных требований.
    IBM Rational Quantify Инструмент анализа производительности работающего приложения. Позволяет выявить фрагменты кода, в наибольшей мере оказывающие влияние на характеристики производительности системы (“бутылочное горлышко”).
    IBM Rational Robot Инструмент для автоматизации записи и воспроизведения сценариев тестов. Сценарии тестов записываются на специальном языке программирования и могут быть получены либо автоматически (путем записи действий пользователя при работе с системой), либо вручную. Rational Robot является основой для проведения тестирования функций системы, а также широко используется в инструментах тестирования производительности.
    IBM Rational TestManager Средство планирования тестов. Используется для воспроизведения функциональных и нагрузочных тестов, созданных в Rational Robot. Используется для сетевого многоплатформенного тестирования.
    IBM Rational TestFactory Инструмент, позволяющий автоматизировать процесс тестирования графических компонентов. Используется для построения карты компонентов приложения. Способен на основе ручного тестирования формировать скрипты для автоматизированного тестирования с использованием Rational Robot.
    IBM Rational ManualTest Средство планирования тестов, не поддающихся автоматизации
    IBM Rational ClearQuest Средство управления запросами на изменение. В процессе тестирование может использоваться как хранилище дефектов, найденных при тестировании.
    IBM Rational RequisitePro Средство для управления требованиями. Позволяет фиксировать связь тестов и проверяемых с их помощью требований. Упрощает оценку полноты тестирования, а также выявление тестов, которые необходимо доработать и провести при изменении требований к системе.

    Использование перечисленных выше инструментальных средств позволяет существенно повысить эффективность тестирования в любом проекте. Однако, тестирование – это не изолированный процесс. Он тесно связан с другими процессами, входящими в состав RUP. Эта интеграция поддерживается также интеграцией соответствующих инструментов. Основным источником исходных данных для тестирования являются функциональные и нефункциональные требования к разрабатываемой системе. Эти требования могут создаваться в различной форме. Например, это может быть Техническое Задание (по любому из ГОСТов) или артефакты RUP (документ VISION – Концепция, описания сценариев использования и т.д.). Для разработки и сопровождения требований IBM Rational предлагает инструментальное средство IBM Rational RequisitePro. Оно позволяет формировать требования различных типов, отслеживать из изменения и хранить сведения об их важности для системы, сложности выполнения и других атрибутах. Для тестировщиков RequisitePro не только позволяет получить актуальную версию требований к системе, оно также позволяет: — указать явную связь требований и тестов, которые предназначены для проверки их выполнения. Это позволяет существенно упростить формирование плана тестирования для проверки конкретных требований; — отслеживать автоматически тесты, связанные с изменившимися требованиями и требующие соответствующего изменения. Интеграция RequisitePro и TestManager позволяет легко получать информацию о ходе и результатах тестирования конкретных требований. Выше уже упоминалось, что для документирования дефектов эффективно применять средство управления изменениями ClearQuest. Оно позволяет организовывать и контролировать выполнение заданий всеми участниками проекта. Для тестировщиков оно не только позволяет сохранять информацию об обнаруженных дефектах, но также определять приоритет дефектов, назначать их для исправления конкретным исполнителям, отслеживать, когда они готовы к повторному тестированию и т.д. При этом ClearQuest позволяет также формировать автоматически различные отчеты о количестве и статусе обнаруженных дефектов, ушедшего на это времени. Для формирования боле сложных отчетов, которые также могут быть очень полезными для управления процессом тестирования, можно использовать средство IBM Rational SoDA.


    Основным его назначением является автоматическое формирование проектной документации на основе имеющихся моделей и других артефактов. Оно способно «выбирать» информацию из других инструментальных средств в соответствие с весьма сложными правилами и формировать на ее основе документы в форматах MS Word, MS Excel и HTML. И последнее по порядку, но не по своему влиянию на весь процесс разработки ПО инструментальное средство IBM Rational ClearCase. Это средство управления версиями. Оно позволяет создавать для разработчиков безопасные рабочие пространства, где все изменения, которые они сделали, но еще не успели отладить, не мешают остальным участникам проекта. При этом оно позволяет вести параллельную разработку нескольких версий продукта (например, для различных платформ). И оно гарантирует возможность вернуться к любой из промежуточной версий любого файла. И даже к той самой, в которой «вчера все работало», как обычно говорят разработчики. При этом можно вернуться не просо к версии одного файла, но к «слепку» или, как это иногда называют, «базовой линии» всей разрабатываемой системы. То есть к согласованному состоянию всех файлов в проекте. А также к соответствующему состоянию требований, моделей и других вспомогательных артефактов. Следующий рисунок демонстрирует логическую связь между инструментальными средствами IBM Rational. Это очень простая, но в то же время и показательная схема, на которой видно, как происходит взаимодействие между инструментами (в том числе, и не описанными выше, но которые также оказывают свое влияние на процесс тестирования). Инструментальная поддержка RUP. Инструменты тестирования и смежные инструменты
    Рисунок 5. Интеграция средств Rational Инструментальные средства Rational глубоко интегрированы друг с другом. Выходная информация одного инструмента является входной для другого. На рисунке показано, какие инструменты и как используются в проекте. Ко всем перечисленным элементам следует добавить IBM Rational Unified Process, как основу описания процессов, IBM Rational SoDA, как основной инструмент формирования проектной документации для всех инструментов IBM Rational, и, самое главное - IBM Rational Project Console – средство визуализации проектных метрик.

    Итерационная разработка

    В соответствие с RUP жизненный цикл программного продукта (не важно, информационной системы, графического редактора или очередной версии популярной компьютерной игры. Но в целях единообразия мы дальше будем говорить, как правило, о системе) состоит из серии относительно коротких итераций. Итерационная разработка
    Рисунок 1. Итерационный жизненный цикл программного продукта Итерация — это законченный цикл разработки, приводящий к выпуску конечного продукта или некоторой его сокращенной версии, которая расширяется от итерации к итерации, чтобы, в конце концов, стать законченной системой. Каждая итерация включает, как правило, задачи планирования работ, анализа, проектирования, реализации, тестирования и оценки достигнутых результатов. Однако соотношения этих задач может существенно меняться. В соответствие с соотношением различных задач в итерации они группируются в фазы. В первой фазе — Содержание — основное внимание уделяется задачам анализа. В итерациях второй фазы — Разработка — основное внимание уделяется проектированию и опробованию ключевых проектных решений. В третьей фазе — Построение — наиболее велика доля задач разработки и тестирования. А в последней фазе — Передача — решаются в наибольшей мере задачи тестирования и передачи системы Заказчику (хотя форма "передачи" Заказчику существенно различается для заказных систем и коробочных продуктов). Каждая фаза имеет свои специфические цели в жизненном цикле продукта и считается выполненной, когда эти цели достигнуты. Так целями Начала является определение назначения и границ разрабатываемой системы. Целями Разработки являются построение и проверка архитектуры системы, то есть тех типовых решений, на которых будет основываться вся последующая разработка системы. Это и технологические решения (используемые языки и средства разработки, готовые средства), и используемые шаблоны (вплоть до типовых приемов обработки ошибок), и просто код, реализующий ключевые функции системы. Целями Построения является быстрое и экономичное построение законченной системы.
    К концу фазы система должна быть готова к бета тестированию (на площадке заказчика). Построение предполагает активное использование и развитие разработанной архитектуры. Передача нацелена на завершение работ над проектом, включая тестирование, окончательную наладку и передачу системы Заказчику в постоянную эксплуатацию. Все итерации, кроме, может быть, итераций фазы Начало, завершаются созданием функционирующей версии разрабатываемой системы. Итерационная разработка
    Рисунок 2. Процессы, стадии и итерации в RUP Исторически предшественниками RUP являются структурные методологии, основанные на использовании так называемого каскадного подхода или водопада. При использовании каскадного подхода тестирование проводится только на завершающих стадиях разработки. При этом тестированию, как правило, подвергаются завершенные модули, а затем полностью собранная система. Основным недостатком каскадного подхода с точки зрения тестирования считается то, что тестирование начинается на завершающих стадиях проекта, и у разработчиков часто уже не остается времени на устранение обнаруженных дефектов, особенно, если они связаны с ошибками в проектировании архитектуры системы.

    Метрики тестирования и качества

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

    Оглавление
















































    Ориентация на архитектуру.

    Еще одной важной и одновременно принципиальной особенностью RUP является его ориентация в первую очередь на архитектурные задачи. Разработка системы начинается с создания архитектурного базиса или каркаса. Этот каркас представляет собой исполняемую программу (исполняемую архитектуру), содержащую образцы решения всех принципиальных и/или типовых задач разрабатываемой системы. Подобный подход позволяет устранять наиболее тяжелые риски – архитектурные – на ранних стадиях разработки. Завершение разработки исполняемой архитектуры означает, что далее перед участниками проекта осталась, в основном, задача наращивания «мышечной массы» проекта по проверенным шаблонам и образцам. Кроме того, ориентация на архитектуру означает большое внимание таким вопросам как разработка и строгое следование архитектурным шаблонам, за счет чего существенно повышаются качество и скорость реализации системы.

    Основные артефакты, создаваемые в процессе тестирования

    В соответствие с RUP в процессе тестирования создается и используется много различных документов и моделей. Ключевые документы и модели и их назначение перечислены ниже:
  • план тестирования. Основной документ, определяет стратегию тестирования на каждой итерации. В него входят описание целей и задач тестирования в текущей итерации, перечни тестов, которые должны и не должны использоваться, формируемые метрики и критерии начала и завершения тестирования;
  • модель тестирования. Совокупность тестовых артефактов;
  • модель рабочей нагрузки. Представляет собой описание внешней нагрузки системы во всех или в одном из исследуемых состояний. Определяет переменные и значения, используемые в различных тестах для моделирования внешних характеристик (например, функции, выполняемые пользователем);
  • дефекты. Основополагающие артефакты – результат обнаружения ошибок. Являются одним из подтипов запросов на изменение, описывающих найденную ошибку или несоответствие на всех этапах тестирования.
  • Принципиальные достоинства RUP в части тестирования ПО | Итерационная разработка ПО, на которой базируется RUP, позволяет существенно повысить качество разрабатываемых продуктов. Действительно, программное обеспечение при такой разработке проходит несколько циклов тестирования. За счет этого повышается вероятность обнаружения ошибок. Причем в наибольшей степени это касается наиболее критических модулей и функций, которые в соответствие с RUP разрабатываются первыми. Итерационная разработка в сочетании с явным определением стратегии тестирования на каждую итерацию позволяют сконцентрироваться на наиболее критических требованиях к ПО. Так на первых итерациях основное внимание уделяется проверке качества и стабильности выбранных архитектурных решений. Определяется, обеспечивает ли выбранная архитектура требуемую производительность системы и выполнение других нефункциональных требований. На последующих итерациях больше внимания уделяется проверке функциональных требований, а также проведению интеграционных и системных тестов. Использование сценариев использования при выявлении требований, анализе и проектировании ПО позволяет тестировщикам получить с самого начала работы над проектом качественное начальное приближение для разработки тестов.

    Как показывает наша практика построения

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

    Приемосдаточные испытания

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

    Самое главное в RUP это...

    Перечисленные выше особенности RUP позволяют перейти к изложению основных подходов к тестированию в RUP. Начнем с самого главного — с формулировки концепции качества продукта в RUP. RUP не ставит своей целью добиться абсолютного качества разрабатываемого продукта. RUP вообще не призывает к достижению недостижимых целей. В частности, этим объясняются и принятая в RUP концепция фаз. В идеале, хорошо бы сначала все проанализировать, потом спроектировать и только потом взяться за программирование. И быть уверенным, что ничего не придется переделывать, потому, что все качественно проанализировано и спроектировано. К сожалению, это недостижимый идеал, как и абсолютное качество программного обеспечения. Как гласит программистская мудрость, каждая обнаруженная в программе ошибка, в лучшем случае, предпоследняя... Значит ли это, что можно сдавать Заказчику систему "как есть", не задумываясь о том, может ли он ею воспользоваться или ошибки не позволяют этого? Нет, ни в коем случае! RUP предлагает использовать при оценке качества произведенного продукта понятие "достаточно хорошего качества". Использование этого понятия означает, что Разработчик с открытыми глазами оценивает качество продукта, который он представляет Заказчику. И, как минимум, уверен, что поиск и устранение следующей ошибки сейчас обойдутся дороже, чем возможные потери Заказчика при проявлении ошибки и затраты на ее устранение в будущем. Впрочем, для критических систем критерий качественного продукта может быть и более жестким. Важно, что критерий есть, и что он должен быть выполнен. И что при этом качественный продукт — это не обязательно продукт БЕЗ ЕДИНОЙ ошибки. Достижение достаточно хорошего качества невозможно без тщательного анализа статистических характеристик результатов тестирования. И это тоже одна из принципиальных особенностей подхода RUP. Теперь, зная, "что такое хорошо и что такое плохо", прейдем к тому, как же "делать хорошо и не делать плохо". Как следует из особенностей RUP, тестирование должно проводиться практически во всех итерациях.
    Однако цели и задачи группы тестирования на различных итерациях и, тем более, в различных фазах разработки могут существенно различаться. В фазе Начало задача группы тестирования — подготовиться к последующей работе по проекту. Понять назначение системы и ориентировочный объем задач тестирования. Подготовить необходимый инструментарий. Продумать критерии качества и критерии завершения тестирования, которые необходимо применить в данном случае. Есть еще одна работа, о которой часто забывают. В этой фазе тестировщики должны оценить в наиболее общих чертах подход к созданию системы и сформулировать требования, которые необходимо выполнять всем участникам разработки для обеспечения простоты тестирования системы. Например, подготовка специальных имитирующих программ, генераторов тестовых данных или использование инструментальных средств и сред, поддерживаемых имеющимися инструментами тестирования. В фазе Разработка, как правило, не создаются полностью завершенные фрагменты системы. Поэтому нет смысла тратить время на проверку чистоты графического интерфейса, отработку всех исключительных ситуаций и другие проблемы, которые еще не решались. Тем не менее, их имеет смысл фиксировать и сообщать о них проектировщикам и программистам. Возможно, это поможет. Основной задачей тестирования в этой фазе является тестирование архитектуры системы. Проводятся наиболее принципиальные проверки производительности системы, ее устойчивости к нагрузкам, надежности выбранного метода взаимодействия между компонентами системы и т.п. Если, как часто бывает в жизни, отложить эти вопросы "на потом", на тот момент, когда система будет уже практически готова то в результате за неделю до сдачи системы Заказчику может оказаться, что она явно не удовлетворяет требованиям к производительности. Нужно переделывать всю схему обработки информации, а времени и ресурсов на это нет. Фаза Построение — это фаза, в которой тестировщики должны в каждой итерации проверять все более полное выполнение системой требований Заказчика.


    Основной особенностью работы тестировщиков на этой фазе является необходимость многократно (обычно, в каждой итерации) проверять практически все модули разрабатываемой системы. Ведь в них самих были внесены изменения и дополнения, либо они должны взаимодействовать с измененными или доработанными модулями. Таким образом, в условиях итерационной разработки существенно возрастает необходимый объем тестирования. При этом наиболее массовым становится регрессионное тестирование, на которое приходится основной объем выполняемых тестов. Однако регрессионное тестирование во многом сводится к повторению ранее разработанных тестов. Это позволяет эффективно использовать для регрессионного тестирования средства автоматизации, позволяющие проводить тестирование с минимальным участием тестировщиков. В результате из недостатка большой объем регрессионного тестирования превращается в достоинство. Наиболее принципиальные фрагменты системы, разрабатываемые первыми, проходят несколько циклов тестирования и передаются Заказчику более качественными. Программный продукт создается в последовательных итерациях. В каждой итерации коллектив разработчиков выполняет несколько сборок программы. Каждая сборка является потенциальным кандидатом для тестирования. Итерация завершается выпуском внутренней версии программы. Эта версия проходит обязательное тестирование. Для каждой версии могут разрабатываться или уточняться тесты. Поскольку процесс разработки является инкрементным (каждая новая итерация добавляет новые возможности разрабатываемой системы), тесты, используемые на ранних итерациях, как правило, могут быть использованы и на последующих для регрессионного тестирования, то есть для проверки того, что ранее реализованная функциональность системы сохранилась в новой итерации. Таким образом, каждая новая итерация подразумевает повторное тестирование всех компонентов, разработанных в предыдущих итерациях, плюс тестирование новых компонентов. Самое главное в RUP это...
    Рисунок 3.


    Пример тестируемых компонент на различных итерациях Итерационный подход позволяет повысить качество системы за счет многократного регрессионного тестирования ключевых компонентов системы. Так как на каждой итерации нужно повторять ранее проводившиеся тесты, то желательно иметь определенный набор инструментальных средств, которые обеспечивают автоматизированное тестирование ранее разработанных компонентов. Еще одна существенная особенность работы в этой и последующей фазах, как уже упоминалось — необходимость собирать статистику обнаруженных и исправленных дефектов. Именно эта статистика, с одной стороны, позволяет оптимально перераспределить силы разработчиков и тестировщиков между модулями и компонентами системы. А с другой стороны, она служит основой для принятия обоснованных решений о достижении продуктом требуемого качества. Сбор достаточного объема статистики, как правило, требует использования средств автоматизации тестирования. Также уже в фазе Построение желательно наладить активное взаимодействие с Заказчиком и будущими пользователями системы. Без обратной связи с будущими пользователями вряд ли вам удастся разработать не то, что «великую», но хотя бы "приличную" систему. Какой родитель скажет, что у его ребенка столько недостатков, сколько их видит сосед!? В начале фазы такое взаимодействие может происходить в форме демонстрации системы "из своих рук". По мере совершенствования системы следует предоставлять пользователям все большую свободу в обращении с системой. Конечно, это потребует от всех разработчиков, и от тестировщиков в частности, дополнительных усилий по, хотя бы, минимальному обучению пользователей и сбору и анализу их предложений и замечаний, но результат обычно того стоит. Передача. В этой фазе тестирование может приобретать специфические формы. Это могут быть и формальные приемочные испытания, и бета тестирование в той или иной форме. Проводить эти виды тестирования в той или иной мере (полностью или частично) может сам Заказчик или третья фирма по поручения Заказчика. К сожалению, даже при самом высоком качестве разработки редко удается избежать в фазе Передачи внесения изменений в разрабатываемую систему.Хотя в этой фазе желательно ограничиваться минимальными доработками, связанными скорее с «отсечением» недостаточно качественных фрагментов кода, иногда приходится возвращаться и к анализу и проектированию, не говоря уж про разработку. В этом случае приходится возвращаться к автономным и комплексным проверкам доработанных модулей и системы в целом. Более того, придется вернуться к отладке модуля. Отладка модуля, которую наиболее эффективно может провести разработчик, не является тестированием по RUP.

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

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


  • Структурное тестирование

    Концепция структурного тестирования связана с тестированием внутренней структуры исходного кода программного обеспечения и тестированием Web-сайтов. Тесты структуры Web-сайтов разрабатываются и выполняются для проверки всех типов связей/ссылок.

    Тестирование и сценарии использования.

    Выполнение задач жизненного цикла сопровождается разработкой различных артефактов (документов, моделей и других материалов проекта). Как обычно в RUP, разработка артефактов может проводиться в разной форме с разными требованиями к способу выполнения, рецензированию и качеству оформления. Например, вы может посмотреть на описание артефакта и решить, что вам в этом проекте он просто не нужен. Если же он вам необходим, вы можете набросать небольшую схему или несколько предложений на обороте старого документа. Правда, по современным представлениям лучше для этого использовать белую доску и фломастеры. В этом случае проще подключить к работе всех заинтересованных лиц или, по крайней мере, довести результаты до их сведения (прежде, чем их стерли). А можно разработать несколько диаграмм, используя инструменты визуального моделирования. Дополнить их сопроводительным текстом, набранным в мощном текстовом редакторе вроде Word, тщательно отредактированным и отформатированным. А потом все это распечатать и переплести. Но на такое оформление стоит тратить время только тогда, когда вы твердо уверены, что это необходимо. Например, если такое оформление оговорено условиями договора. Ниже представлены основные рабочие артефакты тестировщиков, в той или иной форме связанные со Сценариями использования. Эти документы, если это не оговорено особо, стоит готовить в достаточно аккуратном виде, поскольку, скорее всего, вам придется неоднократно к ним возвращаться самим, а часто еще и передавать их Заказчику или группе сопровождения и технической поддержки системы. Сценарий тестирования (тест кейс). Это один из основных документов, с которыми имеет дело тестировщик. По сути, упрощенное описание теста. То есть входной информации, условий и последовательности выполнения действий и ожидаемого выходного результата. Учитывая, что даже успешно прошедшие тесты в RUP выполняются неоднократно в ходе регрессионного тестирования, наличие таких описаний необходимо. Однако уровень формальных требований к их оформлению может меняться в очень широких пределах.
    Одно дело, если вы собираетесь использовать тесты в ходе приемочных испытаний, проводимых Заказчиком, и другое — в ходе внутреннего тестирования коробочного продукта. Тест скрипт. Обычно говорят о программной реализации теста, хотя скрипт может описывать и ручные действия, необходимые для выполнения конкретного тест кейса. Набор тестов. Как правило, Сценарии тестирования объединяются в пакеты или наборы. Во-первых, это просто способ группирования тестов со сходными задачами, а, во-вторых, в такой набор можно включать зависимые тесты, которые должны выполняться в определенном порядке (поскольку последующие тесты используют данные, сформированные в ходе выполнения предыдущих). Список идей тестов. Использование в RUP для анализа и проектирования Системы Сценариев использования существенно упрощает задачу разработки необходимого набора тестов. Основной объем тестов строится как проверка различных вариантов выполнения каждого сценария использования. Однако тесты не сводятся к Сценариям использования, как и задачи тестирования не сводятся только лишь к проверке функциональных требований к системе. Проверка нефункциональных требований может потребовать использования специальных приемов и подходов. Соответствующие тесты не всегда очевидны. Для таких ситуаций и создается Список идей тестов. В него все желающие могут записать Что и/или Как стоит еще проверить. Этот список является внутренним рабочим документом группы тестирования. Наиболее разумная форма его ведения — электронный документ с минимальными формальными требованиями к оформлению. Модель нагрузки. Сценарии использования, как правило, описывают взаимодействие с системой одного пользователя. Часто этого бывает мало. При тестировании систем необходимо учитывать возможность параллельной работы большого числа пользователей, решающих различные задачи. Модель реальной нагрузки описывает характеристики типового «потока заявок», которые должны использоваться для нагрузочного тестирования, имитирующего работу системы в реальных условиях.Также могут быть созданы стрессовые модели нагрузки для тестирования отказоустойчивости системы. Дефекты. Основополагающие артефакты процесса тестирования – описывают обнаруженные факты несоответствия системы предъявляемым требованиям. Являются одним из подтипов запросов на изменение, описывающих найденную ошибку или несоответствие на всех этапах тестирования. Хотя базу данных дефектов можно вести в текстовом файле или Excel таблице, предпочтительным является использования специализированного инструментального средства, которое позволяет передавать информацию об обнаруженных дефектах от тестировщиков к разарботчикам, а в обратную сторону – сведения об устранении дефектов. А также формировать необходимые отчеты о тенденциях изменения количества обнаруживаемых и устраняемых дефектов.

    Тестирование производительности

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


  • Тестирование удобства использования

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

    Тестируемость

    Далее предполагается рассмотреть понятие Тестируемости. Почему одни продукты можно протестировать существенно быстрее, полнее и надежнее, чем другие? Какие проектные решения упрощают, а какие усложняют качественное тестирование? Как связаны Тестирование и Управление рисками? При выполнении проекта необходимо учитывать, в соответствии с какими стандартами и требованиями будет проводиться тестирование продукта. Какие инструментальные средства будут (если будут) использоваться для поиска и для документирования найденных дефектов. Если помнить о тестировании с самого начала выполнения проекта, тестирование разрабатываемого продукта не доставит неприятных неожиданностей. А значит и качество продукта, скорее всего, будет достаточно высоким.

    Типовой цикл тестирования

    Тестирование обычно проводится циклами, каждый из которых имеет конкретный список задач и целей. Цикл тестирования может совпадать с итерацией или соответствовать ее определенной части. Как правило, цикл тестирования проводится для конкретной сборки системы. RUP предполагает частую сборку разрабатываемой системы. И каждая сборка, как правило, должна быть проверена. В зависимости от задач, которые ставились перед сборкой, проверка может быть более или менее полной. Но, как минимум, она должна включать некоторый набор тестов "на дым", проверяющих, что основная функциональность системы не нарушена, и при ее запуске от компьютера не "валит дым", как из негерметичного трубопровода (для которых исходно такие тесты и применялись). Типовой цикл тестирования приведен на следующем рисунке. Типовой цикл тестирования
    Рисунок 4. Цикл тестирования Ниже приведены краткие описания задач, входящих в цикл тестирования. Определить цели тестирования. Включает выбор тестируемых фрагментов и формулирование задач тестирования (например, определить пригодность архитектуры, проверить реализацию основной функциональности конкретного Сценария использования или проверить выполнение требований Заказчика в полном объеме). Верифицировать метод тестирования. Настройка среды и инструментов тестирования, выполнение отдельных тестов, подтверждение возможности реализовать задачи и цели тестирования. Подтвердить правильность сборки. Прежде, чем приступить к детальному тестированию выбранной сборки, проводятся ее тесты "на дым". Эти тесты должны показать, что сборка не содержит явных ошибок, делающих ее дальнейшее тестирование просто нецелесообразным. Для "проходных" сборок, в которых не реализован достаточный объем новой функциональности, тестирование может на этом и заканчиваться. Тестировать и оценивать. Разрабатываются (уточняются) необходимые тесты, после чего тесты выполняются в ручном или автоматическом режиме, и проводится оценка результатов. Достичь приемлемого уровня достижения целей тестирования. Оценивается, с одной стороны, качество и эффективность тестирования, а, с другой стороны, качество тестируемой системы и ее соответствие требованиям, предъявляемым на данном этапе разработки проекта. Улучшить набор тестов и другие активы для дальнейшего использования. Описать и сохранить тесты, наборы тестовых данных, настройки среды и инструментальных средств, которые можно использовать в последующих тестовых циклах.

    Типы тестирования

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

    Типы тестов

    Тесты существенно различаются по задачам, которые с их помощью решаются, и по используемой технике. Их можно классифицировать в соответствие с традиционными показателями качества, которые проверяются с их помощью. Для проверки функциональности ПО используются собственно функциональные тесты, а также тесты безопасности, объема и другие. Тесты удобства использования (usability) включают тесты на человеческий фактор, эстетику интерфейса и его непротиворечивость, наличие и качество оперативной и контекстной помощи, руководств и учебных материалов. Тестирование надежности ПО производится с помощью интеграционных тестов, тестов структуры и стрессовых тестов и так далее... Различные типы тестов требуют дополнительного рассмотрения.

    Управление сценариями использования.

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

    Выгоды от использования технологии IBM Rational в цифрах и фактах.

    Наверное, можно сколь угодно долго говорить о технологии и об инструментах, поддерживающих ее. Но для того чтобы конечного потребителя окончательно склониться к правильному выбору, приведем реальные цифры и графики эффективности использования предлагаемой технологии и инструментальных средств. В России очень много компаний, которые, так или иначе используют технологии IBM Rational, но это, скорее использование отдельных инструментов, а не процесса. Хотя такое использование и дает эффект, но он не так велик, как было бы при полном переходе на технологию IBM Rational. Для иллюстрации эффективности технологии мы приведем реальные данные по одной из компаний, где полностью внедрили процесс тестирования по RUP в большой проект разработки нескольких последовательных версий большой системы (из области коммуникации). Рисунок 5 демонстрирует то, каким образом снижается число ошибок в версиях программного продукта при применении технологии IBM Rational. В данном случае речь идет о применении регрессионных тестов. Обратите внимание на точку "пика". Это точка обозначает одновременно максимальную нагрузку, максимальное число найденных ошибок и максимальную задействованность тестировщиков, так как трудоемкость на данном шаге достаточно большая, так как сценарным языком тестирования необходимо сымитировать весь спектр функциональных возможностей тестируемого ПО. Как видно из графика, во всех последующих версиях программного продукта количество найденных ошибок уменьшается. А поскольку процесс тестирования автоматизирован, то сокращаются трудозатраты тестировщиков, соответственно, возрастает объем тестов, и, как следствие программное обеспечение тестируется все более и более полно. Выгоды от использования технологии IBM Rational в цифрах и фактах.
    Рисунок 6. Число ошибок, найденных в версиях программного обеспечения. Следующий рисунок демонстрирует рост числа тестов в ходе разработки. Отправной точкой были 50 ручных тестов, по которым тестировалось программное обеспечение. Ручной труд, как известно, не очень производителен.
    Автоматизация тестирования позволила кардинально увеличить число тестов (теперь тестировать не только самые главные функции) и довести его число до 450. Разумеется, ручное выполнение такого количества тестов потребовало бы огромных ресурсов. При автоматизации тестирования удалось обойтись прежним количеством тестировщиков. Выгоды от использования технологии IBM Rational в цифрах и фактах.
    Рисунок 7. Число тестов во времени. И в заключении еще несколько характеристик объема тестирования. 50 тестов выполнялись изначально для одной версии, сейчас же проводится тестирование 4-х версий для 30 заказчиков. При этом затраты на тестирование каждой версии уменьшились. К сожалению, в рамках одной статьи мы не можем более полно рассказать о видах тестов, например, о нагрузочных тестах моделирования нагрузок на клиент-серверные системы (снова же из практики: одна из компаний отказалась от разработки своей перспективной коммуникационной системы после проведения всего комплекса нагрузочных тестов, так как система не была готова к реальной нагрузке. Компания оценила риски, сделала выводы, и основываясь на полученных знаниях по другому спроектировала и реализовала систему). Давайте подведем итоги. Использование RUP обеспечивает:
  • снижение основных рисков заказчика, и разработчика;
  • экономию ресурсов за счет автоматизации регрессионного тестирования;
  • улучшение качества ПО за счет многократных проверок изменений;
  • улучшение качества тестирования за счет использования современных технологий.


  • Жизненный цикл продукта и Тестирование

    Все чаще в наше время используются итеративные процессы разработки ПО. Одним из примеров такого подхода является RUP. При использовании такого подхода Тестирование перестает быть процессом "на отшибе", который запускается после того, как программисты написали весь необходимый код. Тестирование оказывается вовлеченным в гущу событий буквально с самого начала работы над проектом. Работа над тестами начинается с самого начального этапа выявления требований к будущему продукту и тесно интегрируется с текущими задачами. И это предъявляет новые требования к тестировщикам. Их роль не сводится просто к выявлению ошибок как можно полнее и как можно раньше. Они должны участвовать в общем процессе выявления и устранения наиболее существенных рисков проекта. Для этого на каждую итерацию определяется цель тестирования и методы ее достижения. А в конце каждой итерации определяется, насколько эта цель достигнута, нужны ли дополнительные испытания, и не нужно ли изменить принципы и инструменты проведения тестов. В свою очередь, каждый обнаруженный дефект должен пройти через свой собственный жизненный цикл. Дефект заносится в базу дефектов. Аналитик определяет, не является ли он повтором внесенного ранее дефекта. Действительно ли он является дефектом? Руководитель утверждает исполнителя, который приступает к устранению дефекта в соответствие с назначенным дефекту приоритетом. Тестировщик повторяет выполнение теста и убеждается (или не убеждается) в устранении дефекта. Строгое соблюдение жизненного цикла дефекта позволяет существенно улучшить управление проектом, а также избежать "расползания" требований под видом исправления ошибок. И избежать ненужной работы по излишней "полировке" продукта.

    

        Программирование: Языки - Технологии - Разработка