Тестирование софта - статьи
Документальная поддержка.
В комплект приобретённого инструмента должен входить пакет сопроводительной документации, который включает описание технических возможностей и требований к окружению системы (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). Виды интеграции. Какие сервисы может предоставить современная система автоматизации тестирования в разрезе взаимодействия с системой разработки?Обучение и сертификация персонала, работающего с набором инструментов и/или методологией.
Время, затраченное персоналом на обучение работе с инструментом, является на этапе внедрения инструмента или технологии тестирования одним из ключевых моментов, на который стоит обращать внимание при разработке планов использования приобретаемого инструмента. Компания производитель, поставляющая средства автоматизированного тестирования, кроме самой продажи средств должна обеспечивать и сервис для обучения персонала (непосредственных тестировщиков, системных администраторов, администраторов самих систем автоматизации) и менеджеров (руководителей отделов и проектных менеджеров) принципам работы и функциональности, предлагаемой в инструменте. С развитием методик тестирования, также становится всё более необходимым обучение новым методам и подходам в тестировании. Зачастую производитель выходит на рынок не только с инструментом или линейкой интегрированных средств, но и выводит новую методику, воплощая её в оригинальном программном решении. Кроме того, так как сложные системы автоматизации тестирования на данном этапе развития представляют собой не только среду для проведения тестирования, но и инструмент разработки тестовых скриптов, управления тестовыми сценариями и оценки качества программных продуктов, стоит говорить об обучении нескольких рангов пользователей:Поддерживаемые процессы тестирования.
Так как система автоматизации тестирования тесно связана с реальными процессами разработки программных систем, а также опирается на определённые процессы тестирования, при анализе необходимо в первую очередь обращать внимание на поддержку инструментом или набором инструментария определённых процессов/технологий тестирования и жизненного цикла разработки ПО. Итак, первый критерий анализа: Поддерживаемые процессы тестирования. Стоит оговориться, что на данном этапе развития рынка систем автоматизации тестирования, существует два подхода к построению инструментария тестирования. Одним типом можно считать продукты, которые охватывают все технологии тестирования, обработку разноплановой информации, которая описывает этапы разработки и требования к программному продукту, а также осуществляют поддержку процессов генерации проектной документации. Другой тип инструментария можно оценивать как узкопрофильный, то есть не охватывающий все этапы тестирования и жизненного цикла ПО, а осуществляющий полноценную поддержку какого-либо важного функционала. К примеру, в последнее время получили широкое распространение так называемые bug-tracking системы, то есть системы управления ошибками. Для более полного анализа стоит разносить в процессе оценки инструменты разных типов, по разным категориям, одновременно расширяя набор критериев для узкоспециализированных инструментов. Рассмотрим более подробно технологии и процессы тестирования программного обеспечения, которые могут поддерживаться инструментальными средствами тестирования.Поддерживаемые технологии.
Под технологиями будем понимать - организованную совокупность процессов, элементов, устройств и методов, используемых для обработки информации. К примеру, технологии .NET, CORBA, OLE, COM, DCOM, COM+ и т.д. Под поддержкой технологий инструментарием тестирования будем понимать возможность записи и воспроизведения тестовых скриптов, которые будут производить тестовые вызовы элементов программного кода выполненных в стандартах той или иной технологии. Следует оговориться, что уровень поддержки той или иной технологии существенно различается на уровне реализации тестовых скриптов. К примеру, большинство производителей инструментальных средств тестирования реализуют возможность записи и воспроизведения скриптов имитирующих действия пользователя по отношению к пользовательскому интерфейсу, то есть поддерживаются возможности выбора из набора графического интерфейса программы определённого элемента (контрола) и передачи ему фокуса с последующим выполнением нажатия. Такая поверхностная поддержка, конечно, производит впечатление на потенциального заказчика (довольно эффектно выглядит, к примеру, автоматическое заполнение многостраничных форм регистрации за несколько секунд, даже с генерацией случайных данных из заданного диапазона), но без возможности непосредственной работы с объектной моделью приложения, этот функционал остаётся лишь красивым дополнениям к системе автоматизированного тестирования, чем её основой. Приведём пример: Есть форма с элементом выпадающий список. Количество элементов списка определяется количество записей в таблице базы данных и есть динамически изменяемая величина. Значения элементов списка представляют, к примеру, числовые данные. Список отсортирован не по величине значений, а по дате последнего изменения этого значения. Задача: при выполнении тестового скрипта выбрать из выпадающего списка максимальное (или минимальное) значение. Напомним, что список не отсортирован по величине, и выбрать первый или последний элемент, координаты которого можно рассчитать не получится.Кроме того, количество элементов списка также величина динамическая - выбор последнего элемента затруднён. Нужно получить список всех значений из списка, заполнить ими массив, отсортировать, найти нужное значение. Обратиться к элементу список, споцизионироваться на нужный элемент (задача не просто решаемая перемещением курсора мышки на определённую координату), имитировать нажатие. Без возможности доступа к объектной модели элемента такая задача не решается в приемлемые сроки. Как результат, встречаются скрипты, которые в подобной ситуации просто выбирают какое-то (неуправляемое) значение. Общая картина работы такого тестового скрипта более напоминает тыканье слепого в интерфейс пользователя, чем заполнение формы тестовыми данными. Суть поддержки той или иной технологии инструментом тестирования состоит в возможности работать через средства инструмента с объектной моделью приложения, вызовами процедур и методов из тестовых скриптов, генерации вводимых данных на основе анализа диапазона передаваемых параметров. Говоря неформальным языком, поддерживаемой можно считать такую технологию, при работе с которой инструмент как минимум "видит" объектную модель приложения, выполненного согласно стандартов технологии. Если в описании инструмента заявлено, что с его помощью можно проводить тестирование приложений выполненных с поддержкой практически всех технологий, следует внимательно уточнять уровень этой самой поддержки, потому что в большинстве случаев компания разработчик имеет в виду именно запись/воспроизведение имитационных сценариев. Такая поддержка никоим образом не охватывает непосредственно тестирование серверных компонентов или сервисов приложений, выполняющих бизнес-логику приложений. Также практически невозможно оценить в целом уровень производительности приложений, моделируя пользовательскую нагрузку имитацией реальных действий пользователя через пользовательский интерфейс. Отдельно хотелось бы оговорить специализированные инструменты, которые поддерживают технологии работы с данными, серверные компоненты и сервера баз данных.
В целом картина на рынке таких инструментов отличается от состояния дел на рынке инструментов для тестирования функциональности. Суть таких инструментов, не только создать нагрузку на сервер базы данных (для этого существует отдельный класс так называемых нагрузочных и стрессовых инструментов), с тем, чтобы зафиксировать время отклика и/или выполнения того или иного запроса или вызова. Средства, которые поддерживают технологию какой-то определённой СУБД (Oracle, Informix, DB2, MS SQL) должны обладать не только функционалом вызова, к примеру, хранимых процедур, или подобной функциональностью, которая будет создавать нагрузку и анализировать поведения сервера, но в идеале и проводить анализ конфигурации серверного окружения, структур и схем хранения данных, контроль за следованием стандартам обращений к данным с тем. Результатом работы такого средства может служить сгенерированный набор аналитических данных на основе которых сервер или окружение будет конфигурироваться под конкретную архитектуру базы данных и возможно настраиваться согласно выбранной технологии реализации клиентского приложения. Указанные требования накладывают существенные ограничения на функциональность подобных средств. Зачастую компания-производитель поддерживает на глубоком уровне только одну конкретную СУБД, разработка инструмента ведётся в тесной интеграции с компанией разработчиком самой СУБД с учётом особенностей архитектуры и технологических решений. Инструментарий получается довольно узкоспециализированный и дорогостоящий, его применения оправдано в случае внедрения долгосрочных промышленных систем автоматизации или систем повышенной отказоустойчивости.
Поддерживаемые типы тестов.
Вторым критерием анализа, логично было бы выделить поддержку различных типов тестов, которые автоматизируются системой. Сообществом тестировщиков и практической работой, выделены основные типы тестов, описание которых поддаётся формализации. Рассмотрим их.Представительство компании-разработчика в странах ближнего зарубежья.
Почему критерий наличия у компании разработчика представительства в странах ближнего зарубежья (а в идеальном случае и в стране, где находится компания, которая приобретает средство автоматизации) настолько важен? Одной из причин, по которой компания-разработчик должна иметь представительство или компанию, которая представляет продукты на отечественном рынке, является цена приобретаемого за границей продукта. В самом деле, на современном этапе развития платёжных систем, сложности при переводе денег, даже для частного лица, в другую страну не может служить поводом для отказа от приобретения инструмента. Одним из моментов, который стоит учитывать, это увеличение стоимости самого инструмента, а стало быть и сроков его окупаемости, в случае переводов за границу и затрат на доставку. В нашей стране вопросы также возникают вокруг процесса получения самого продукта, если он в целях безопасности не передаётся по каналам связи, а высылается почтой. "Растаможивание" программного обеспечения также является фактором, увеличивающим его стоимость. А так как серьёзные программные продукты идут в поставке с документацией и дополнительными компонентами, комплект поставки может составлять не один десяток носителей типа компакт-диск. Стоимость услуг "растаможивания" продукта определяется количеством компакт дисков. Прибавим сюда компакт с рекламой и презентацией и вполне реально получаем солидную прибавку к стоимости продукта, цена которого нас устраивала вначале. В пользу важности этого аргумента может послужить тот факт, что многие компании в СНГ и в Украине в частности переводят подписки на продукцию компании 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), где
G. Дадим несколько определений. Расширением грамматики G (или просто расширенной грамматикой) называется грамматика G' = (T,N',P',S'), где S' – новый нетерминальный символ, а к множеству правил добавлено правило S' > S. Сентенциальной формой будем называть последовательность грамматических символов – нетерминалов и токенов. Далее, греческими буквами из начала алфавита (?, ?, ?, ?, ...) мы будем обозначать какие-либо сентенциальные формы. Пустую сентенциальную форму будем обозначать через ?. Правосентенциальной формой называется сентенциальная форма, для которой существует правый вывод из стартового правила. Пример. Рассмотрим следующую грамматику:
| S > AB |
| A > cd |
| B > eCf |
| C > Ae |
> Активным префиксом правосентенциальной формы называется префикс, не выходящий за границы самой правой основы этой формы. Пунктом грамматики 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. Действия анализатора определяются этими двумя символами:
к надежности которого чрезвычайно высоки.
Компилятор является инструментом, требования к надежности которого чрезвычайно высоки. И это неудивительно, ведь от правильности работы компилятора зависит правильность работы всех скомпилированных им программ. Из-за сложности входных данных и преобразований задача тестирования компиляторов является весьма трудоемкой и непростой. Поэтому вопрос автоматизации всех фаз тестирования (создания тестов, их прогона, оценки полученных результатов) стоит здесь особенно остро. Синтаксический анализ является частью функциональности любого компилятора. От корректности синтаксического анализа зависит корректность практически всей остальной функциональности – проверки семантических ограничений, оптимизирующих преобразований, генерации кода. Поэтому решение задачи тестирования синтаксических анализаторов является базой для решения задач тестирования всех остальных компонент компилятора. Для очень многих языков программирования существует формальное описание синтаксиса – описание грамматики языка в форме BNF, а для тех языков, для которых существуют только эталонные компиляторы (например, COBOL), делаются активные попытки построить такое описание (см.[9, 10, 14]). BNF языка является одновременно и спецификацией функциональности синтаксического анализа, таким образом, в этой области наиболее привлекательным является тестирование на основе спецификаций (см. [17]). Существование формального описания позволяет автоматизировать процесс построения тестов, что существенно снижает трудозатраты, а систематичность тестирования повышает доверие к его результатам. Построением тестов по грамматике занимались многие авторы. Основополагающей работой в этой области является работа [18], в которой сформулирован следующий критерий покрытия для множества позитивных тестов: для каждого правила в данной грамматике в множестве тестов должно присутствовать предложение языка, в выводе которого используется это правило. В той же работе Пардом предложил метод построения минимального тестового набора, удовлетворяющего этому критерию.Однако указанный критерий оказался недостаточным. Ламмель в работе [9] показал, что тестовые наборы, построенные алгоритмом Пардома, не обнаруживают простейших ошибок. Ламмель также предложил более сильный критерий покрытия, состоящий в том, что покрывается каждая пара правил, одно из которых можно применить непосредственно после другого. Предлагаемые другими авторами методы являются вероятностными (см. [7, 13, 11, 12]) и не описывают критериев покрытия, и потому для них возникает вопрос остановки генерации тестов, который решается, например, с помощью введения вероятностей появления правил и уменьшения этих вероятностей при каждом новом появлении правила в выводе. В любом случае завершение работы алгоритма за конечное время является проблемой. Кроме того, произвольность остановки генерации нарушает систематичность тестирования. Все приведенные выше работы касаются генерации позитивных тестов для синтаксического анализатора (т.е. тестов, являющихся предложениями целевого языка). В настоящее время работы, предлагающие методы генерации негативных тестов для синтаксических анализаторов (т.е. тестов, не принадлежащих целевому языку), практически отсутствуют. Однако такие тесты также важны, поскольку пропуск неверной последовательности лексем на этапе синтаксического анализа может привести к аварийному завершению компиляции. В работе [8] высказано предположение, что если имеется генератор предложений языка из грамматики (генератор позитивных тестов для синтаксического анализатора), то для генерации негативных тестов для синтаксического анализатора можно использовать метод мутационного тестирования (mutation testing)1 (см. [6, 15]). Идея состоит в том, что в исходную грамматику вносятся изменения (мутации) для получения грамматик, описывающих близкие, но не эквивалентные исходному языки. Эти мутированные грамматики подаются на вход генератору тестов для получения потенциально негативных тестов. Общие проблемы данного подхода состоят в следующем:
Такие мутанты должны быть выявлены и не должны использоваться для генерации тестов.
Тестирование софта - статьи
Анализаторы и метрики
Измерение тестового покрытия производится с помощью соответствующих анализаторов, которые делятся на две группы: инструментирующие исходный код и "бинарники". Принцип действия в обоих случаях состоит во вставке в код приложения вызовов специальных функций между инструкциями. Затем, во время работы приложения, подсчитывается количество вызванных функций, отношение которых к их общему числу и составляет коэффициент тестового покрытия. Библиотеки для генерации 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%). С другой стороны, тестирование радикально сокращает затраты времени на:
Иногда программисту кажется, что тот или иной класс или метод невозможно протестировать. Скорее всего, такому специалисту просто нужна помощь в нахождении технического решения. Несмотря на то что существуют области, традиционно считающиеся трудными для тестирования (GUI, к примеру), ситуаций, когда тестирование невозможно в принципе, пока не выявлено.
Вместе с тем в отдельных случаях дизайн модулей, классов или методов действительно мешает тестированию. Обычно это происходит в системах, где TDD вводится постфактум и где уже существует большое количество кода, не покрытого тестами. В такой ситуации перед тестированием приходится модифицировать существующий код. Часто удается ограничиться изменением областей видимости классов и методов.
Наконец, программисты порой не считают нужным тестировать, утверждая, что код, который они написали, работает и так. Иногда такая позиция оправдывается. Тем не менее необходимо помнить, что модульное тестирование надежно выявляет некоторые классы ошибок. Если код не тестировался, никто не может дать гарантии, что эти ошибки в нем отсутствуют.Кент Бек выражает эту же мысль более резко: единственное безопасное предположение, которое можно сделать относительно кода, который не тестировался, - это то, что он не работает.
Практические рекомендации
Причины, позволяющие программистам оправдать отсутствие тестов, сами по себе с введением TDD никуда не исчезают. Но TDD предоставляет простые средства, позволяющие почти полностью нейтрализовать их негативное влияние. Перейдем к практическим рекомендациям. Несмотря на то что концепция модульного тестирования относительно проста, использование TDD в реальных проектах требует от программистов, и особенно от "техлидов", определенных навыков. Прежде всего, для успешного применения TDD необходимо умение собирать и интерпретировать некоторые стандартные тестировочные метрики. В XP-группе для оценки качества тестирования применяются:Три других показателя обычно имеет смысл рассматривать вместе. В отличие от coverage, количество тестов, asserts и строк кода интереснее всего наблюдать в динамике. При нормальном использовании TDD все три значения должны расти ежедневно и равномерно, причины резких изменений необходимо выявлять.
Некоторый интерес представляет анализ отношений между этими и другими метриками: например, большая разница между количеством asserts и тестов может говорить о том, что тесты в среднем крупнее, чем нужно. Подтвердить или опровергнуть это утверждение может среднее количество строчек кода в тесте. Иногда имеет смысл рассматривать такие показатели, как среднее количество ежедневно добавляемых тестов, отношение тестов к основному коду по количеству строк и другие, но это скорее уже экзотика.
Время исполнения тестов выступает важной метрикой по двум причинам. Во-первых, резкое ухудшение времени исполнения тестов является достаточно надежным сигналом появления проблем с производительностью приложения. Во-вторых, чем дольше выполняются тесты, тем менее удобно с ними работать и тем реже программисты их запускают. Поэтому необходимо следить за тем, чтобы время работы тестов не увеличивалось по небрежности, а иногда имеет смысл даже прилагать некоторые усилия по их оптимизации.
В заключение разговора о метриках отмечу, что в XP-группе хорошо зарекомендовала себя практика их ежедневного автоматизированного сбора с рассылкой report'а команде. Анализом результатов, как правило, занимаются "PM" и "техлид". Метрики, несмотря на удобство работы с ними, в большинстве случаев не позволяют оценить тесты по целому ряду важных неформальных критериев. Поэтому существует набор требований к тестам, отслеживаемых, как правило, на code review. К ним относятся:
Выполнение этого правила позволяет отлаживать тесты произвольными группами.
В соответствии с идеями TDD в большинстве случаев, кроме тестов, необходимых для обеспечения coverage, программисты пишут дополнительные тесты на ситуации, которые они по каким-либо причинам хотят проверить дополнительно. Такая практика приветствуется. Модульное тестирование, как уже отмечалось, не является "серебряной пулей" и может использоваться далеко не всегда. Так, например:
Модульное тестирование - это специфическая область программирования. Чтобы получить общее представление о его особенностях, рассмотрим некоторые паттерны, применяющиеся в программировании тестов.
Необходимость запускать тесты отдельными наборами заставляет использовать механизмы структурирования тестов. В зависимости от конкретной среды, тесты организуют либо в иерархические наборы (Boost Test Library), либо в пространства имен (NUnit). Многие frameworks, кроме того, предоставляют возможность задать категорию теста, это удобно для того, чтобы запускать тесты из разных веток одновременно. Правила категоризации тестов имеет смысл определять в стандартах кодирования.
Важнейшим паттерном модульного тестирования, поддерживаемым всеми развитыми frameworks, являются SetUp/TearDown-методы, предоставляющие возможность выполнения кода перед и после запуска теста или набора тестов. Как правило, существуют отдельные методы SetUp/TearDown уровня тестов и test suites. Важной и очень удобной возможностью являются SetUp/TearDown-методы для всех тестов (уровень сборки в терминах mbUnit).
Основная задача SetUp/TearDown - как правило, создание тестовых наборов данных. При тестировании кода, работающего с базами данных на запись, в этих методах производится backup и восстановление базы либо создаются и откатываются транзакции.
Mock-объекты - тестировочный паттерн, суть которого состоит в замене объектов, используемых тестируемым кодом, на отладочные эквиваленты. Например, для тестирования кода, обрабатывающего обрыв коннекции к базе данных, вместо настоящей коннекции можно использовать специальный mock-объект, постоянно выбрасывающий нужное исключение.
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 позволяет получить простой способ оценки полноты интерфейсов: необходимым и достаточным считается такой интерфейс, который позволяет выполнить все написанные тесты. Все, что находится за этими рамками, считается ненужным. Наконец, использование тестов в качестве инструмента дизайна заставляет программиста в первую очередь концентрироваться на интерфейсе, а уже во вторую - на имплементации, что также положительно влияет на результат.
Значение тестов для рефакторинга переоценить невозможно. Любой, даже самый небольшой рефакторинг, как известно, требует наличия написанных тестов. Эта мысль важна настолько, что Мартин Фаулер в своей книге "Refactoring" посвятил модульному тестированию целую главу. Конечно, некоторые виды рефакторингов, такие, как, например, переименование полей, вполне возможно применять и без тестов. Но набор модульных тестов, покрывающих большую часть приложения, позволяет модифицировать систему значительно более агрессивно и со значительно более предсказуемыми результатами. Именно сочетание рефакторинга и тестов позволяет XP-программистам быстро изменять систему в любом нужном заказчику направлении. И именно поэтому мы можем позволить себе не искать гибких решений, а использовать самые простые (цена изменений при наличии тестов не слишком высока).
Разработчики знают, как тяжело порой бывает отладить какой-нибудь метод, глубоко закопанный в большом приложении. Иногда не удается проверить только что написанный код, потому что он еще нигде и никак не используется. Часто воспроизведение тестовой ситуации занимает чересчур много времени. В некоторых ситуациях программисты даже разрабатывают специальные утилиты, позволяющие отладить тот или иной компонент отдельно от всей системы.
При использовании модульных тестов подобных проблем просто не возникает. Если нужно что-нибудь проверить - пишем тест. Тесты, вообще говоря, представляют собой практически идеальную отладочную среду. Они находятся полностью под контролем программиста и при правильном применении позволяют вызвать любой код в широком диапазоне условий.
Кроме того, модульное тестирование ведет к сокращению общих затрат времени на отладку. Для большинства ошибок, найденных при модульном тестировании, отладка вообще не требуется, их видно сразу. Даже сложные "баги" отлаживать становится проще, поскольку точно известны место их возникновения (код, который был написан только что) и условия воспроизведения (тест, который сейчас отлаживается).Искать те же самые ошибки в работающем приложении почти всегда оказывается значительно более сложным и долгим делом.
Тестирование софта - статьи
Чем могут помочь модели
В голове разработчика и тестировщика всегда присутствует та или иная «модель» устройства программы, а также «модель» ее желаемого поведения, исходя из которой, в частности, составляются списки проверяемых свойств и создаются соответствующие тестовые примеры. (Заметим, что это разные модели; первые часто называют архитектурными, а вторые — функциональными или поведенческими.) Они зачастую составляются на основе документов или обсуждений в неформальном виде.Разработка моделей и спецификаций связана с «математизацией» программирования. Попытки использовать различные математические подходы для конструирования и даже генерации программ предпринимались с первых лет возникновения компьютеров. Относительный успех был достигнут в теории компиляторов, реляционных баз данных и в нескольких узкоспециальных областях; серьезных результатов в большинстве практических областей достичь не удалось. Многие стали относиться к формальным методам в программировании скептически.
Новый всплеск интереса к формальным методам произошел в первой половине 90-х. Его вызвали первые результаты, полученные при использовании формальных моделей и формальных спецификаций в тестировании.
Преимущества тестирования на основе моделей виделись в том, что:
Модель — некоторое отражение структуры и поведения системы. Модель может описываться в терминах состояния системы, входных воздействий на нее, конечных состояний, потоков данных и потоков управления, возвращаемых системой результатов и т.д.
Для отражения разных аспектов системы применяются и различные наборы терминов. Формальная спецификация представляет собой законченное описание модели системы и требований к ее поведению в терминах того или иного формального метода. Для описания характеристик системы можно воспользоваться несколькими моделями в рамках нескольких формализмов. Обычно, чем более общей является нотация моделирования, тем больше трудностей возникает при автоматизации тестирования программы на основе модели/спецификации, описанной в этой нотации. Одни нотации и языки больше ориентированы на доступность и прозрачность описания, другие — на последующий анализ и трансляцию, в частности, трансляцию спецификации в тест. Предпринимались попытки разработки языка формальных спецификаций, удовлетворяющего требованиям промышленного использования (например, методология RAISE), однако широкого применения они не нашли.
Имеется несколько ставших уже классическими нотаций формальных спецификаций: VDM, Z, B, CCS, LOTOS и др. Некоторые из них, например, VDM, используются преимущественно для быстрого прототипирования. Язык B удобен для анализа, в частности для аналитической верификации моделей. Все эти языки активно используются в рамках университетских программ. В реальной практике для описания архитектурных моделей используется UML, а для построения поведенческих моделей — языки SDL/MSC, исполнимые диаграммы UML и близкие к ним нотации.
Перечисленные языки и нотации для поведенческих моделей, к сожалению, не обладают достаточной общностью. Они хорошо себя зарекомендовали в телекоммуникационных приложениях и практически бесполезны для описания функциональности программных систем «общего вида»: операционных систем, компиляторов, СУБД и т.д.
На роль инструментов разработки тестов для подобных систем претендует новое поколение средств описания моделей/спецификаций и средства генерации тестов на проверку согласованности поведения реализации заданной модели.
Инструменты тестирования на основе моделей
Test Real Time — один из первых представителей этой группы. Более широкие возможности предоставляет Jtest компании Parasoft. Интересен инструментарий компании Comformiq. Семейство инструментов разработки тестов на основе моделей предлагает Институт системного программирования РАН в кооперации с компанией ATS. Поскольку семейство UniTesK авторам знакомо существенно ближе, мы изложим общую схему подхода тестирования на основе моделей на примерах из UniTesK.
|
| Рис. 1. Фазы процесса разработки спецификаций и тестов |
Первая фаза относительно коротка, но в реальных проектах она важна. Именно здесь закладывается уровень абстрактности модели. Модель должна быть максимально простой: это позволит требовать исчерпывающего набора тестов. В то же время, модель должна быть содержательной, раскрывать специфику тестируемой реализации. Таким образом, задача первой фазы — найти компромисс между абстрактностью и детальностью.
Задача второй фазы — описание требований к поведению системы. Многие подходы (например, SDL) предлагают описывать исполнимые модели, которые можно рассматривать как прототипы будущей реализации. Задание требований в таком случае определяется формулой «реализация должна вести себя так же, как модель». Подход понятен, но, к сожалению, во многих реальных ситуациях он не работает. Допустим, в заголовке некоего сообщения, построенного моделью, указано одно время, а в аналогичном заголовке от реализации — несколько другое. Это ошибка или нет? Еще один пример. Модель системы управления памятью сгенерировала указатель на свободный участок памяти, а реальная система выдала другой указатель: модель и система работают в разных адресных пространствах. Ошибка ли это?
При записи скрипта можно делать
При записи скрипта можно делать остановки для того, чтобы указывать, какие ответы системы в конкретной ситуации надо рассматривать как правильные, какие вариации входных данных пользователя возможны и т.д. При наличии таких вариаций при очередном воспроизведении теста инструмент самостоятельно будет выбирать одну из определенных альтернатив. При несовпадении ответа системы с ожидаемым ответом будет фиксироваться ошибка.Впрочем, возможности данного вида тестирования ограничены:
Отвечает ли этот инструмент требованию опережающей разработки тестов? В целом, да: для создания теста достаточно описания интерфейсов компонентов. Но есть и слабые места, которые, впрочем, присущи и большинству других инструментов. Так, сценарий тестирования приходится писать вручную. Кроме того, нет единой системы задания критериев тестового покрытия и связи этих критериев с функциональными требованиями к системе.
Последний из рассматриваемых здесь классов инструментов — инструменты тестирования модулей. Примером может служить 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%.
Есть ли принципиальные ограничения в применимости данного подхода? Его практически невозможно применять в случае, когда по той или иной причине никто в цепочке заказчик — разработчик — тестировщик не смог или не захотел четко сформулировать требования к целевой системе. Впрочем, это не только ограничение, но и дополнительный стимул для улучшения процессов разработки, еще один повод объяснить заказчику, что вложения в фазу проектирования с лихвой окупаются сокращением общих сроков разработки и стоимости проекта.
Литература
Рис. 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)
Подходы к улучшению качества программ
«Борьба за качество» программ может вестись двумя путями. Первый путь «прост»: собрать команду хороших программистов с опытом участия в аналогичных проектах, дать им хорошо поставленную задачу, хорошие инструменты, создать хорошие условия работы. С большой вероятностью можно ожидать, что удастся разработать программную систему с хорошим качеством.Второй путь не так прост, но позволяет получать качественные программные продукты и тогда, когда перечисленные условия соблюсти не удается — не хватает хороших программистов, четкости в поставке задачи и т.д. Этот путь предписывает стандартизировать процессы разработки: ввести единообразные требования к этапам работ, документации, организовать регулярные совещания, проводить инспекцию кода и проч. Одним из первых продвижений на этом фронте стало введение понятия жизненного цикла программной системы, четко определявшее необходимость рассмотрения многих задач, без решения которых нельзя рассчитывать на успех программного проекта.
В простейшем варианте набор этапов жизненного цикла таков:
Водопадная модель требовала точно и полно сформулировать все требования; изменение требований было возможно только после завершения всех работ.
Водопадная модель не давала ответ на вопрос, что делать, когда требования меняются или меняется понимание этих требований непосредственно во время разработки.
В конце 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 приведен пример сценарного метода.
|
| Рис. 3. Пример сценарного метода, тестирующего вставку элемента в очередь с приоритетами |
В UniTesK используется единая архитектура тестов, подходящая для тестирования систем различной сложности, относящихся к разным предметным областям, и обеспечивающая масштабируемость тестов. Компоненты тестов, требующие написания человеком, отделены от библиотечных и генерируемых автоматически (см. рис. 4).
В реальных системах количество различимых состояний и количество допустимых в каждом из них тестовых воздействий очень велико, что приводит к комбинаторному «взрыву состояний».
Для борьбы с этим эффектом разработан механизм факторизации модели: те состояния целевой системы, различие между которыми несущественно с точки зрения задач данного теста, объединяются в одно обобщенное состояние модели; аналогичным образом объединяются в группы и тестовые воздействия. Процесс факторизации предоставляет разработчику свободу творчества, но, вместе с тем, он поддержан строгими исследованиями, определяющими достаточные условия, при соблюдении которых гарантированы корректность результатов и существенное сокращение времени тестирования при сохранении достигаемого тестового покрытия.
|
| Рис. 5. Использование UniTesK в среде разработки Forte 4.0 |
Тестирование софта - статьи
Аннотация
Рассматриваются вопросы использования типовых решений (паттернов проектирования) для построения тестовых программ, основанных на обобщенных моделях тестируемых систем в форме неявно заданных конечных автоматов. В качестве базовой технологии применяется технология 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} в зависимости от дерева, соответствующего исходному обобщенному состоянию. Этот недетерминизм легко преодолеть, заменив итерацию вершин итерацией различных элементов мультимножества обобщенного состояния. Элементу мультимножества при выполнении перехода ставится в соответствие произвольная вершина дерева с заданным числом непосредственных детей. Таким образом, имеем следующие переходы:
Краткое описание
В качестве обобщенного состояния выбирается длина списка, присутствующего в модельном состоянии или сконструированного на его основе.В качестве обобщенного состояния выбирается размер множества, присутствующего в модельном состоянии или сконструированного на его основе.
В качестве обобщенного состояния выбирается произведение других обобщенных состояний.
В качестве обобщенного состояния выбирается мультимножество, элементами которого являются числа - количество непосредственных детей для каждой вершины дерева.
Область применения
Применяется для присутствующих в модельном состоянии списка, очереди, стека и любых других перечислений. Использование паттерна предполагает, что тестовые ситуации не зависят от элементов списка. Для покрытия разнообразных элементов списка предполагается использование дополнительных технических приемов: дополнительных сценарных методов, паттерна выделение элементов.Применяется для множества, присутствующего в модельном состоянии, или сконструированного на его основе. Использование паттерна предполагает, что тестовые ситуации не зависят от элементов множества. Для покрытия ситуаций, зависящих от элементов множества, предполагается использование дополнительных технических приемов: дополнительных сценарных методов, паттерна выделение элементов.
Применяется совместно с другими паттернами. Позволяет объединять тестовые ситуации, задаваемые элементами декартова произведения. При увеличении количества элементов декартова произведения количество состояний резко возрастает. Если между состояниями нет зависимостей, то количество состояний произведения есть произведение количеств состояний, задаваемых каждым элементом. Опыт показывает, что допустимо произведение лишь небольшого числа элементов, в пределах десяти.
Применяется при тестировании программ, для описания поведения которых в качестве состояния спецификации используется дерево. Считается, что в спецификации описаны методы, позволяющие изменять структуру дерева и свойства узлов.
Обобщенное состояние
Используется целочисленное или натуральное состояние 0, 1, 2, … . Функция вычисления обобщенного состояния возвращает количество элементов списка. Для обеспечения конечности обобщенных состояний вводится ограничение на количество состояний, задаваемое как параметр сценария.Используется целочисленное или натуральное состояние 0, 1, 2, … . Функция вычисления обобщенного состояния возвращает количество элементов множества. Для обеспечения конечности обобщенных состояний вводится параметр сценария, который может задавать ограничение как на количество состояний, так и на количество разнообразных элементов, итерируемых в сценарных методах.
Тип состояния зависит от типов элементов произведения. В общем случае можно пользоваться PairComplexGenState и ListComplexGenState, конструируемыми из пары и списка обобщенных состояний соответственно. Для произведения целочисленных состояний можно пользоваться классами обобщенных состояний IntPairGenState, IntTripleGenState, IntListGenState.
В качестве обобщенного состояния выбирается мультимножество, элементы которого - количество непосредственных дочерних вершин. Рассмотрим пример. На рис. 7 показаны два дерева, в которых одна вершина имеет две дочерних, одна имеет одну дочернюю, и две вершины в каждом дереве не имеют дочерних вершин. Таким образом, обоим этим деревьям соответствует одно и тоже обобщенное состояние, мультимножество {0,0,1,2}.
Рис. 7. Примеры деревьев
Результаты подсчета количества обобщенных состояний в зависимости от числа вершин приведены в таблице 2. Как видно, количество обобщенных состояний значительно меньше числа корневых деревьев для того же числа вершин. Это делает данное обобщенное состояние практически пригодным для использования при тестировании.| Число корневых деревьев | 1 | 9 | 719 | 87811 | 12826228 | 2067174645 |
| Число обобщенных состояний | 1 | 5 | 30 | 135 | 490 | 1575 |
Описание паттернов
В этом разделе приводятся подробные описания четырех паттернов: длина списка, размер множества, декартово произведение и мультимножество чисел детей. Паттерн размер отображения очень похож на паттерн размер множества применительно к множеству ключей отображения, отличие состоит только в дополнительной итерации значений отображения при добавлении элементов. Паттерны число вершин дерева и код дерева - это упрошенные версии паттерна мультимножество чисел детей. Первый паттерн не покрывает деревья разнообразной структуры, однако позволяет тестировать деревья с большим количеством вершин. Второй паттерн позволяет протестировать деревья всевозможных структур, однако применим лишь для небольшого числа вершин. Паттерн среднее состояние позволяет тестировать достижение максимального количества элементов в различных структурах данных (списках, множествах, отображениях, деревьях) за счет объединения всех промежуточных состояний между максимальным и минимальным состояниями в одно обобщенное состояние. Паттерн единственное состояние применяется для тестирования методов, не зависящих от состояния, и для построения простого тестового сценария, в котором разнообразие покрываемых тестовых ситуаций полностью зависит от выбора итераций параметров методов. Паттерн выделение элементов основан на выделении элементов, обладающих некоторыми свойствами. Он применяется совместно с другими паттернами: длина списка, размер множества, размер отображения, число вершин дерева и другими. Цель применения - покрытие тестовых ситуаций, зависящих от свойств элементов. Данный паттерн может применяться совместно с паттерном декартово произведение с целью достижения детерминизма.Понятие паттерна
Паттерны представляют собой удачные решения часто встречающихся задач. Там, где использование процесса разработки тестового сценария приводит к хорошему результату, паттерны позволяют повторно использовать полученные результаты. Там, где использование процесса затруднительно, знание паттернов позволяет выделить части, к которым они применимы. Паттерны позволяют использовать инструментальную поддержку. Паттерны, описываемые в данной статье, получены на основе анализа более чем десятилетнего опыта разработки тестов ИСП РАН [] в различных проектах:| Длина списка | В качестве обобщенного состояния выбирается длина списка | 13% | 41% |
| Размер множества | В качестве обобщенного состояния выбирается размер множества | 17% | |
| Размер отображения | В качестве обобщенного состояния выбирается размер отображения | 8% | |
| Число вершин дерева | В качестве обобщенного состояния выбирается число элементов дерева | 3% | |
| Декартово произведение | В качестве обобщенного состояния выбирается декартово произведение других обобщенных состояний | 18% | 39% |
| Выделение элементов | Паттерн основан на выделении элементов обладающих некоторыми свойствами | 5% | |
| Единственное состояние | В качестве обобщенного состояния выбирается одно единственное состояние | 10% | |
| Мультимножество чисел детей | В качестве обобщенного состояния выбирается мультимножество, элементами которого являются числа - количество непосредственных детей для каждой вершины дерева | 3% | |
| Код дерева | В качестве обобщенного состояния выбирается код дерева, однозначно определяющий его структуру | 2% | |
| Среднее состояние | Все промежуточные состояния объединяются в одно обобщенное состояние | 1% | |
| ? (без паттерна) | 20% |
Эти паттерны используются в более чем половине случаев применения паттернов. В большой части паттернов явным образом определяется обобщенное состояние; это такие паттерны, как длина списка, размер множества, размер отображения, число вершин дерева, единственное состояние, мультимножество чисел детей, код дерева, среднее состояние. Оставшиеся два паттерна декартово произведение и выделение элементов явным образом состояние не определяют, а используются совместно с другими паттернами. Паттерны покрывают большинство распространенных структур данных: списки, множества, отображения, деревья. Для объединения нескольких обобщенных состояний, соответствующих разным структурам данных, используется паттерн декартово произведение. В паттерне выделение элементов учитываются свойства элементов, так как в остальных паттернах при выборе обобщенного состояния предполагается, что тестовые ситуации от свойств элементов не зависят. Описание каждого паттерна состоит из следующих частей:
Метод удаляет элемент по индексу
List modelList; // Метод добавляет элемент e в список. void add(Integer e); // Метод удаляет элемент по индексу index из списка. // Если индекс выходит за границы списка, // вырабатывается исключение IndexOutOfBoundsException. void remove(int index) throws IndexOutOfBoundsException; Тестовые ситуации для метода add:Пример 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:
Процесс разработки тестового сценария
В технологии UniTesK задание конечного автомата, на основе которого строится тестовая последовательность, вынесено в отдельный компонент - тестовый сценарий []. В тестовом сценарии задаются обобщенные состояния и итерации параметров методов. Обобщенные состояния определяют состояния конечного автомата и задаются с помощью функции, возвращающей обобщенное состояние на основе состояния модели. Таким образом, состояния модели разбиваются на классы с одинаковым обобщенным состоянием. Обобщенные состояния позволяют уменьшить количество состояний автомата. Итерации параметров методов задают перебор параметров, с которыми следует вызвать методы, определяя тем самым возможные переходы автомата. Однако при таком задании переходов состояние после выполнения метода неизвестно до вызова метода. Поэтому окончательный вид автомата определяется только в процессе обхода автомата. На основе описания обобщенного состояния и итераций параметров методов специальный компонент тестовой системы, называемый обходчиком, строит обход переходов автомата, получая тестовую последовательность. В процессе обхода, выполняя вызовы методов, обходчик определяет окончания переходов автомата. Процесс разработки тестового сценария начинается с определения тестовых ситуаций (рис. 1). Тестовая ситуация определяет множество состояний, метод, и наборы параметров при которых она может быть покрыта. Тестовая ситуация является достижимой в данном состоянии, если в этом состоянии существует такой набор параметров, что метод, вызванный с этим набором параметров в данном состоянии, покрывает эту ситуацию. Тестовые ситуации позволяют определить, какие состояния являются "интересными" для тестирования, а также судить о разнообразности обобщенных состояний. Обобщенные состояния являются разнообразными, если они позволяют покрыть выбранные тестовые ситуации. Основным источником тестовых ситуаций является покрытие, определенное в спецификациях, так как именно оно является основным критерием завершенности тестирования в технологии UniTesK.При определении тестовых ситуаций могут учитываться также дополнительные соображения, не нашедшие отражения при определении покрытия в спецификации. Например, если в качестве модельного состояния используются деревья, то дополнительным соображением может быть наличие разнообразных деревьев: высоких, широких, сбалансированных и т.д. Подобные свойства сложно отображать в спецификации. Другой пример дополнительного соображения - перебор значений свойства, не определяющего внешнее поведение реализации и, соответственно, не отраженнного в спецификации. Однако внутреннее поведение реализации может зависеть от этого свойства, используемого для оптимизации.
Рис. 1. Процесс разработки тестового сценария
На следующем шаге процесса разработки тестового сценария происходит выбор обобщенного состояния и итераций параметров методов. Выбор обобщенного состояния является непростой задачей Это поиск компромисса между количеством состояний, их разнообразием и возможностями обходчика, использующего данное состояние для построения тестовой последовательности. Количество состояний непосредственным образом отражается на длине тестовой последовательности и времени работы тестов. Приемлемое количество состояний зависит от скорости работы системы. Опыт показывает, что для быстрых систем приемлемое количество состояний - несколько сотен, для медленных - несколько десятков. Обобщенные состояния должны быть достаточно разнообразными, чтобы обход всех переходов автомата покрывал выбранные тестовые ситуации. Для этого достаточно в качестве обобщения взять разбиение состояний на группы, в которых достижимы одинаковые наборы тестовых ситуаций. Такое разбиение обычно не приводит к большому количеству состояний, так как количество тестовых ситуаций обычно невелико. Рассмотрим пример обобщения. На рис. 2 в левой части показаны состояния, у каждого из которых нарисованы возможные переходы. Переходы, соответствующие разным тестовым ситуациям, показаны стрелками с разной штриховкой. Обобщением является объединение состояний с одинаковым набором штриховок выходящих стрелок.
Очевидно, что обход, покрывающий все переходы автомата, покроет все возможные тестовые ситуации.
Рис. 2. Пример начального обобщения
Если количество обобщенных состояний слишком велико, то приходится жертвовать разнообразием, укрупняя обобщенные состояния. Укрупнение обобщенных состояний может приводить к тому, что в двух модельных состояниях одного обобщенного состояния, достижимыми являются разные множества тестовых ситуаций. Может получиться так, что в одном модельном состоянии тестовая ситуация достижима, а в другом - нет. Однако можно надеяться, что в процессе обхода будут получены оба состояния, и все тестовые ситуации будут все равно покрыты. В ситуации, показанной на рис. 3, обобщение состояний приводит к тому, что не всякий обход автомата покроет переходы, заштрихованные вертикальными линиями, сеточкой и точками. На рисунке эти стрелочки обозначены пунктирной линией.
Рис.3. Укрупнение состояний
В ряде случаев достигнуть покрытия, не меняя обобщенного состояния, удается с помощью введения дополнительных сценарных методов или путем разработки дополнительного тестового сценария, возможно, с другим обобщенным состоянием. На этом же шаге процесса разработки тестового сценария выбираются итерации параметров методов. Итерации задаются в сценарных методах тестового сценария. В общем случае одному тестируемому методу может соответствовать несколько сценарных методов, и один сценарный метод может задавать итерации сразу для нескольких методов, а также последовательность вызовов. В наиболее частом случае каждому методу соответствует один сценарный метод. Итерация параметров может быть выполнена с фильтрацией по критерию покрытия. В этом случае итерируются идентификаторы элементов покрытия (тестовых ситуаций), и для каждого элемента подбирается набор параметров, покрывающий выбранный элемент. Фильтрацию также называют обобщением переходов, так как она разбивает параметры и состояния на группы, соответствующие разным элементам тестового покрытия. Фильтрация может использоваться как для сокращения количества переходов, так и для детерминизации автомата. После начального выбора обобщенного состояния и итераций следует проверить выполнение требований обходчика.
На данный момент в инструментах UniTesK есть пять видов обходчиков:
Использование метода дробления позволяет во многих случаях достичь детерминированности автомата. Многие предлагаемые в данной статье паттерны могут быть получены применением метода дробления. Однако для применения метода требуется представление модели в виде конечного автомата. Преставление модели в виде конечного автомата зачастую бывает слишком сложным из-за слишком большого числа получающихся состояний и переходов. Кроме того метод применим не для всех автоматов. Метод дробления может построить автомат, удовлетворяющий требованиям обходчика, однако число его состояний может быть слишком велико. Тогда для уменьшения количества состояний можно жертвовать разнообразностью состояний. Метод связующих переходов используется совместно с обходчиком автоматов, имеющих детерминированный, сильно связный покрывающий подавтомат. К уже имеющемуся недетерминированному переходу добавляется дополнительный детерминированный переход, гарантированно переводящий систему в заданное состояние. Например, если метод в зависимости от некоторых свойств переводит систему в два разных состояния, можно ввести дополнительный переход (сценарный метод), при выполнении которого перед вызовом метода свойства устанавливаются таким образом, чтобы перейти в требуемое состояние. На рис. 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
{
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
{
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
{
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-технологии также могут быть использованы для повышения переносимости программных продуктов. Можно выделить два основных способа построения приложений, использующих эти технологии:Основной недостаток подхода является следствием его достоинств — в разных браузерах одна и та же страница может отображаться по-разному — как вследствие пользовательских настроек, так и из-за особенностей конкретного браузера. Безусловно, существуют различные стандарты на 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:Переиспользование исходного кода
К сожалению, в большинстве случаев совместимость на бинарном уровне выполняется в рамках систем одного производителя, однако лишь немногие системы способны загружать исполняемые файлы, предназначенные для выполнения на платформах других поставщиков. Альтернативой использованию одних и тех же бинарных файлов явилось использование одного и того же исходного кода для сборки приложения на различных системах. Исторически, возможность переноса исходного кода между различными платформами появилась не сразу. В те времена, когда программы писались на ассемблере конкретной аппаратной платформы, добиться компиляции приложения на системе с другой архитектурой было практически нереально. Существенной подвижкой в этом направлении стало создание высокоуровневых языков программирования, не привязанных к конкретной архитектуре и позволяющих использовать одни и те же конструкции на различных системах. Например, именно озабоченность некоторых сотрудников 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 |
Примеры использования различных подходов к обеспечению функционирования приложений как в Windows, так и в Linux. Использование виртуальных машин также не требует больших усилий со стороны разработчиков ПО, хотя этот способ достаточно накладен, как в смысле производительности, так и ввиду необходимости иметь лицензии на все используемые операционные системы. Применение виртуализации оправдано в тех случаях, когда перенос приложения каким-то другим способом представляется экономически неэффективным. В частности, это относится ко многим унаследованным системам, для которых портирование на новую платформу означало бы практически полное переписывание приложения. В таблице 1 приведены примеры использования различных подходов к обеспечению переносимости ПО, которые используются разработчикам для создания программ, функционирующих как в Windows, так и в Linux. Интересно отметить подход Google, который не побоялся положиться на слой эмуляции wine для запуска Google Picasa в ОС Linux, несмотря на практически полное отсутствие успешных примеров крупных приложений, официально использующих такой метод. Подход с использованием библиотек-медиаторов более традиционен и применяется не один десяток лет. Вопрос обеспечения переносимости следует рассматривать в самом начале проекта, на стадии проектирования и выбора технологий и инструментов, которые будут использованы при его реализации. К сожалению, вряд ли можно сформулировать универсальное правило выбора средств для увеличения переносимости – такой выбор сильно зависит от конкретных требований, предъявляемых к приложению. На взгляд авторов, в настоящее время потенциально наибольший охват дает использование интерпретируемых языков – многие интерпретаторы работают на достаточно большом числе программно-аппаратных платформ. Естественно, использование таких языков имеет свои недостатки, и для ряда приложений может оказаться неприемлемым. Следующим по масштабу «охвата», на наш взгляд, идет использование кросс-платформенных библиотек и других медиаторов.
Однако медиаторов, реализующих нужную функциональность, может и не существовать, а разработка собственных – оказаться достаточно трудоемким процессом. Тем не менее, даже при отказе от поддержки нескольких систем ввиду отсутствия средств и при ориентации на одну конкретную платформу, стоит строить архитектуру приложения таким образом, чтобы по возможности отделить части, использующие специфические для данной платформы интерфейсы и сервисы. Подводя итоги, отметим, что возможны ситуации, когда отказ от обеспечения переносимости разрабатываемого ПО оправдан; например, с достаточно большой долей вероятности такой выбор будет выгоден в краткосрочной перспективе. Однако при создании продуктов, которые планируется поддерживать в течении достаточно длительного периода, обеспечение переносимости может оказаться одним из ключевых факторов успеха. Повышение мобильности приложения в общем случае всегда связано с увеличением расходов на его разработку; однако чем раньше об этой проблеме задумаются разработчики и архитекторы, тем меньше будет стоимость переноса приложения на новую платформу.
Тестирование софта - статьи
Аналогии
Есть замечательная реклама: Как делают автомобили SAAB? Берут самолёт и отсекают всё лишнее, что помогает ему взлетать.Попробуем взять шаблон RUP-а для MTP (Master Test Plan) и отсечь всё лишнее, что не касается темы данной статьи. Самое интересное, что отсечь пришлось практически весь шаблон, оставив только одно приложение и табличку, для расчета ресурсов. Добавим перевод, вырежем лишнее.
Деятельность/Задачи тестирования (Testing Activities)
Рассмотрим более подробно существующие активности/задачи связанные с тестированием:Классификация задач и ролей в тестировании, основанная на методологии 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), тестовые классы и тестовые наборы (пакеты)Ответственность:
|
Тестирование софта - статьи
Аллилуя, то есть Славься!
Я не преследовал цели посвятить вас в самые сложные вещи 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 — это как бы "мегапатч" к стареньким си-либам, позволяющий: 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 [], например:Инструмент CTesK
Для построения тестового набора в данной работе использовался инструмент CTesK, который позволяет эффективно распараллелить разработку тестового набора и автоматизировать разработку тестовых сценариев. Ниже приведено краткое описание возможностей этого инструмента и обзор технологии UniTESK, которую он реализует. В инструменте CTesK объектом тестирования является целевая система. У этой системы имеется набор интерфейсов, с помощью которых она взаимодействует с окружением. Набор этих интерфейсов изначально определен и специфицирован. Все требования, которые предъявляются к системе, накладываются на наблюдаемые действия системы, которые можно отследить через интерфейсы ее взаимодействия с окружением. Эти требования не затрагивают внутренней реализации системы, т.е. тестирование производится по принципу "черного ящика". При использовании инструмента CTesK требования, предъявляемые к целевой системе, должны быть описаны на формальном языке SeC, который является спецификационным расширением языка C. Впоследствии из таких требований автоматически генерируется исполняемый код, который используется в процессе тестирования. Взаимодействие с целевой системой осуществляется при помощи стимулов и реакций. Стимулами называются средства, при помощи которых можно передавать целевой системе какую-либо информацию. Целевая система может непосредственно возвращать некоторый ответ на стимул, а также изменять свое внутреннее состояние. Такой ответ и изменение состояния, если имеется доступ к состоянию системы, возвращаются в тестовую систему, и производится проверка соответствия поведения системы наложенным на нее требованиям. Требования описываются на формальном языке в виде постусловий. Для стимулов в виде предусловия описываются ограничения, при которых можно вызывать данный стимул. Дополнительно могут накладываться ограничения на возможные значения данных определенного типа в виде инвариантов. Некоторые следствия работы системы и результаты воздействия на нее определенных стимулов могут наблюдаться не сразу, а после завершения внутренних процессов.Такое поведение приводит к тому, что реакция на стимул поступает наружу через какое-то время после воздействия на систему одного или нескольких стимулов, или может даже проявляться без воздействия стимулов на систему. Такую информацию не во всех случаях можно охарактеризовать как ответ на какой-либо стимул, и она называется реакцией (отложенной реакцией) системы. На реакции системы обычно также накладывается ряд требований. Аналогично случаю со стимулами, в инструменте CTesK они описываются в виде пред- и постусловий. Предусловия определяют ограничения на состояния системы, при которых может происходить данная реакция. А постусловия описывают ограничения на саму реакцию, то есть на данные, которые передаются окружению при помощи этой реакции, и на изменение состояния целевой системы, которое может происходить при возникновении такой реакции. Для описания требований к стимулам и реакциям тестовый набор должен обладать информацией о состоянии целевой системы и о его изменениях. Для этого тестовым набором поддерживаются структуры данных, моделирующие это состояние. Далее состояние этих структур данных будет называться модельным состоянием. Если имеется доступ к внутренним структурам данных целевой системы, описывающих это состояние, то возможна синхронизация состояния модели и целевой системы. В таком случае возможна проверка требований, касающихся изменения состояния внутренних структур данных. В противном случае состояние модели и состояние целевой системы изменяются независимо и синхронизируются лишь на начальном этапе тестирования. При этом требования накладываются только на действия системы, наблюдаемые извне. Нарушение требований обработки внутренних структур данных проверяются лишь неявно при последующих некорректных ответах на стимулы и возникновении некорректных реакций. Тестовый набор, разрабатываемый при помощи инструмента CTesK, может быть ориентирован не на конкретную целевую систему, а на группу целевых систем, поведение которых описывается одними и теми же требованиями.
Причем некоторые целевые реализации могут предоставлять доступ к внутренним структурам данных, а другие нет. Для обеспечения универсальности в тестовый набор включается специальная часть, которая называется медиатором. Для каждого отдельного стимула медиатор осуществляет преобразование данных из модельного представления тестового набора в представление целевой системы, а для каждой реакции выполняет обратное преобразование. Таким образом, медиатор зависит от конкретной целевой системы. Поэтому, для того чтобы произвести тестирование новой целевой системы, необходимо заново разработать для нее медиаторы. Вся информация о ходе тестирования сохраняется в специальном файле, который называется тестовой трассой. По данным, которые содержатся в этом файле, генерируется отчет об ошибках и покрытии. Трасса хранится в формате XML, что позволяет легко разрабатывать средства для ее анализа. В инструменте CTesK имеются средства для автоматического измерения тестового покрытия. Для этого в описание стимула добавляется специальный блок coverage, в котором, исходя из модельного состояния и параметров вызова стимула, определяется, какую из ветвей функциональности целевой системы задействует данный стимул. Инструмент CTesK включает в себя утилиту, которая после завершения тестирования по информации, содержащейся в тестовой трассе, генерирует подробную статистику и отчет о тестовом покрытии. Стимулы и реакции на языке SeC описываются в виде спецификационных функций. Для стимула такая спецификационная функция включает в себя предусловие, постусловие и блок coverage. Для реакции - только предусловие и постусловие. Для каждой спецификационной функции разрабатывается медиатор, который оформляется в виде функции. Медиатор для стимула имеет два блока: блок call, который отвечает за передачу соответствующего стимула реализации и за получение результата, и блок state, который отвечает за обновление модельного состояния. Для реакции медиатор содержит только блок state, который выполняет аналогичную функцию. В инструменте CTesK термин тестовый сценарий используется для обозначения группы тестовых операций, обычно предназначаемых для тестирования логически связанных аспектов функциональности целевой системы.
Если в ходе исполнения тестового сценария обнаруживается ошибка, то тестовый сценарий завершается. Такая мера является вынужденной, так как после возникновения ошибки состояния тестового набора и целевой системы оказываются не синхронизированными. Выполнение тестового сценария заключается в вызовах сценарных функций. Сценарная функция содержит исполняемый код и вызовы стимулов. В ходе выполнения сценарной функции собираются реакции от системы (см. Рис. 4). После завершения сценарной функции запускается механизм, называемый сериализацией. В процессе сериализации перебираются возможные порядки вызовов стимулов и наступления реакций (см. Рис. 5). Чтобы поведение целевой системы могло считаться корректным, требуется, чтобы хотя бы один из порядков удовлетворял требованиям в виде пред- и постусловий стимулов и реакций.
Рис. 4. Исполнение сценарной функции
Рис. 5. Один из сценариев сериализации В процессе проверки одного из порядков сериализации тестовый сценарий может проходить через последовательность состояний модели. Такие промежуточные состояния называются нестационарными. Вызов сценарной функции должен обязательно закончиться в стационарном состоянии, иначе поведение целевой системы является некорректным. Кроме того, все допустимые последовательности сериализации, которые заканчиваются в стационарном состоянии, должны приводить к одному и тому же состоянию. Иначе выполнение тестового сценария будет прервано с сообщением об ошибке. В функциях, выполняющих сериализацию, используются методы сохранения состояния модели, восстановления состояния модели, а также определения, является ли данное состояние стационарным. Если имеется информация о частичном порядке вызовов стимулов и поступления реакций, то можно существенно сократить процесс сериализации по сравнению с полным перебором или даже свести ее к рассмотрению одного единственного варианта. В инструменте CTesK предусмотрены средства для разбиения множества модельных состояний на группы. Такое разбиение называется факторизацией состояний, а полученные обобщенные состояния - состояниями тестового сценария.
На выбор факторизации накладывается следующее ограничение: после вызова определенной сценарной функции из определенного начального состояния тестового сценария тестовый сценарий всегда должен перейти в одно и то же конечное состояние. Пример факторизации модельных состояний схематично представлен на Рис. 6.
Рис. 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. В результате из-за описанных выше проблем от использования стационарных модельных состояний целевой реализации пришлось отказаться. Основные причины использования стационарного тестирования заключаются в следующем:
В предлагаемом подходе для различных групп требований предусмотрены три группы действий:
С учетом того, что в работе имитируются только виртуальные сети со стационарными маршрутизаторами, домашними агентами и узлами-корреспондентами, состояние виртуальной сети определяется лишь ее топологией, набором ее узлов и точкой подключения мобильного узла. Топология виртуальной сети и набор узлов задаются перед запуском тестового сценария. Таким образом, состояние виртуальной сети определяется лишь точкой подключения мобильного узла. Такие модельные состояния являются стационарными, так как не зависят от поведения реализации и являются объединениями нескольких приближенно стационарных состояний мобильного узла. Для обеспечения такого соответствия необходимо использовать достаточно большой интервал времени, который отводится на исполнение сценарной функции, чтобы тестируемая реализация успела осуществить переход в приближенно стационарное состояние. Кроме того, необходимо описать все действия, определенные стандартом при данном переходе. Конечное приближенно стационарное состояние мобильного узла после перехода в один и тот же сегмент виртуальной сети зависит от поведения других узлов этой сети. В результате введения описанных выше состояний может существенно ухудшиться качество тестирования, поэтому помимо текущей точки подключения мобильного узла в модельных состояниях должен учитываться также и способ, которым мобильный узел был переведен в данную сеть. Сеть, в которой мобильный узел находился до осуществления смены точки подключения, в модельном состоянии не учитывается, так как для любого способа, которым мобильный узел может быть переведен в данную сеть, при обходе графа модельных состояний автоматически будут произведены переходы из всех сетей, из которых это возможно. В предлагаемом подходе не учитывается также и предыдущая история переходов. В подавляющем большинстве случаев предыдущие переходы не оказывают влияния на поведения мобильного узла, а учет таких переходов экспоненциально увеличивает количество модельных состояний. В качестве сценарных функций в тестовом наборе задаются возможные способы перевода мобильного узла из одной сети в другую.
Задача обходчика в таком случае заключается в осуществлении всех возможных переходов для мобильного узла всеми возможными способами. Под различными способами здесь понимается различное поведение узлов виртуальной сети после осуществления смены точки подключения мобильного узла в этой сети, которое определяется отправляемыми ими сообщениями. Для обеспечения полноты тестирования необходимо оказать воздействие всеми возможными стимулами во всех достижимых состояниях, включая начальное состояние. Однако приведение тестируемой реализации мобильного узла в начальное состояние только при помощи обмена сообщениями через сетевой интерфейс не представляется возможным. Это связано с тем, что даже после удаления привязки при возвращении в домашнюю сеть мобильный узел сохраняет некоторую информацию в структурах данных, описывающих его состояние, например, сохраняет адрес домашнего агента. На Рис. 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, а именно:Тестирование софта - статьи
Анализ результатов тестирования
На рисунке показана статистическая выкладка, выведенная Coverage по окончании тестирования приложения.
Поле Calls определяет число вызовов функции Из полученной таблицы видна статистическая информация о том, какие строки и сколько раз исполнялись. К особенностям работы PureCoverage отнесем тестирование только тех исполняемых файлов, которые уже имеют отладочную информацию от компилятора. Соответственно, тестирование стандартных приложений и библиотек данным продуктом невозможно. Впрочем, и не нужно. Особые преимущества инструмент демонстрирует при совместном тестировании с Robot при функциональном тестировании, подсчитывая строки исходных текстов в момент воспроизведения скрипта. Тем самым на выходе тестировщик (или разработчик) получает информацию о стабильности функциональных компонент, плюс, область покрытия кода (область охвата).
Поле %Lines Hit показывает процентное отношение протестированных строк кода для отдельной функции.
Вид окна перехода на уровень работы с исходным текстом. Из него видно, что PureCoverage ведет нумерацию строки и подсчитывает число исполнений каждой. Не исполнившиеся фрагменты подсвечиваются красным цветом (палитра может регулироваться пользователем)
API
Это дополнительная возможность, предоставляемая Quantify по полному управлению процессом тестирования. API представляет собой набор функций, которые можно вызывать из тестируемого приложения по усмотрению разработчика. Для получения доступа к API необходимо выполнить ряд действий по подключению "puri.h" файла с определением функций и с включением "pure_api.c" файла в состав проекта. Единственное ограничение, накладываемое API — рекомендации по постановке точек останова после вызовов Quantify при исполнении приложения под отладчиком. Рассмотрим имеющиеся функции API Quantify:{
int i; QuantifyAddAnnotation("Тестирование проводится под Quantify с использованием API");
QuantifySetThreadName("Основной поток приложения");
for(i=0;i<12;i++){
QuantifySaveData();
recursive();
}
return 0;
} В листинге показано как можно использовать основные функции API для извлечения максимального статистического набора данных. Рисунки показывают слепки фрагментов экрана Quantify по окончании тестирования.
Рис. Данный рисунок демонстрирует вид окна после выполнения команды снятия слепка или вызова функции API QuantifySaveData()
Рис. Обратите внимание на поле аннотации
Рис. У 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 также реализует поиски рассогласовании в именах.
Основные параметры вывода
Основные свойства средств Purify, Quantify и PureCoverage
Интегрируются с ClearQuest, для документирования и отслеживания возникающих в процессе тестирования ошибок;
Имеют гибкая настройка в соответствии с потребностям разработчиков и тестировщиков;
Особенности запуска
Запуск приложения ведется точно таким же образом, как и в остальных случаях. Здесь нового ничего нет. Из особенностей можно отметить то, что тестировать можно только то приложение, которое содержит отладочную информацию. Данная особенность выделяет PureCoverage из линейки инструментов тестирования для разработчиков, которые могут тестировать как код с отладочной информацией, так и без нее. При попытке исполнить приложение, не содержащее отладочной информации, на экран, по окончании тестирования, будет выведено сообщение об ошибке.Параметры тестирования
Перед исполнением тестируемого приложения, возможно, задать дополнительные настройки, которые смогут настроить Purify на эффективное тестирование. Запуск приложения производится точно также как и в случае с Quantify (по F5 или file->Run). Параметры настройки находятся пункте Settings, появившегося окна. Первая страница диалога представлена на рисунке
Отметим основные особенности, которые качественно могут сказаться на результирующем отчете: По умолчанию, активирован пункт "Show first message only". В этом случае по окончании тестирования, Purify выводит сокращенный отчет по модулям, то есть, если в одном модуле найдено 10 утечек памяти, то информация об утечках на основном экране будет описывать только имя ошибки, число блоков и имя модуля. То есть мы получаем обобщенную информацию по ошибкам в блоке. В случае необходимости получения отдельного отчета по каждому отдельному блоку, отключаем данный пункт.
Quantify, Purify и PureCoverage
Для осуществления всех функций по тестированию программных модулей, все три продукта используют специальную технологию Object Code Insertion, при которой бинарный файл, находящийся под тестированием, насыщается отладочной бинарной информацией, обеспечивающей сбор информации о ходе тестирования. Отметим общие черты для всех трех программных продуктов. Для сбора и обработки информации программам тестирования нужны два файла: исполняемый модуль и его исходный текст. Исполняемый модуль насыщается отладочным кодом, а наличие исходного текста позволяет разработчику легко переключаться между схематическим отображением и кодом тестируемого приложения. На код накладываются дополнительные особые ограничения, а именно: тестируемый код должен быть получен при помощи компиляции с опцией "Debug", то есть должен содержать отладочную информацию. В противном случае Quantify, Purify и PureCoverage не смогут правильно отображать (вообще не смогут отображать) имена функций внутренних модулей тестируемого приложения. Исключения могут составлять только вызовы внешних модулей из dll-библиотек, поскольку метод компоновки динамических библиотек позволяет узнавать имена функций. Отсюда можно сделать простой вывод: тестировать приложения можно даже не имея отладочной информации в модуле и при отсутствии исходных текстов, но в этом случае разработчик получает статистику исключительно по внешним вызовам. Все три продукта способны проводить особые виды тестирования, такие как тестирование сервисов NT\2000\XP. Программные продукты могут работать в трех режимах:Работа с фильтром
Чтобы не загромождать пользовательский интерфейс лишними данными, в Purify предусмотрена система гибких фильтров. Система фильтров Purify способна регламентировать тип ошибок и предупреждений и ошибок (группировка производится по категориям) к программе, но и число исследуемых внешних модулей (чтобы разработчик мог концентрироваться только на ошибках собственного модуля). Таким образом возможно создание универсальных фильтров с осмысленными именами, которые ограничивают поток информации. Число создаваемых фильтров ничем не ограничено. Фильтры создаются и назначаются и модифицируются через верхнее меню (View->CreateFilter и View->FilterManager). По умолчанию Purify выводит все сообщения и предупреждения.
Рисунок показывает внешний вид окна создания фильтра (View->CreateFilter). Здесь мы имеем возможность по выбору сообщений, которые нужно отфильтровывать. Пункт General — управляет именем фильтра и комментарием, его сопровождающим, Source — определяет местоположение исходных файлов, для которых необходимо вывести сообщения. Подход используется в том случае, когда происходит вызов одного модуля из другого, дабы ограничить количество информации в отчете. Следующий рисунок демонстрирует вид окна настроек фильтров. Здесь имеется возможность по активации\деактвации фильтров и модулей.
Выше упоминалось, что Purify не ограничивает число фильтров. Следует понимать, что не ограничивается не только общее число фильтров, но и их количество на одно протестированное приложение. Ограничение по модулям, которое также можно выставить в данном диалоге, определяет число внешних модулей, предупреждения от которых появляются в отчете.
Работа с PureCoverage
По принципу работы PureCoverage слегка напоминает Quantify: также подсчитывает количество вызовов функций. Правда, получаемая статистика не столь исчерпывающая как в Quantify (в визуальном отношении), но для проверки области охвата кода вполне и вполне пригодна. Система отчетности представлена 4-я различными видами отчетов:"Run Summary"
Плавно переходим к следующей стадии тестирования, собственно, к сбору информации. По окончании процесса насыщения отладочным кодом модулей тестируемого приложения, Quantify переходит к его исполнению, производимым, обычным образом, за одним исключением: запись состояний тестируемого приложения продолжает проводиться в фоновом режиме.
Рисунок 3 демонстрирует фрагмент окна "Summary", в котором производится запись состояния тестируемого приложения. Причем, что очень примечательно, тестирование производится не только для простого приложения, но и для много поточного. В последнем случае (см. рисунок 3), тестируется каждый поток отдельно. В любом случае, даже если приложение однопоточное, то имя основного (единственного) потока именуется как ".main_0", что представляется вполне логичным. Информационный график постепенно наполняется квадратами разного цвета, демонстрирующими текущее состояние тестируемого приложения. Отметим некоторые из них: (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) в четырех различных представлениях, позволяющих наиболее эффективным образом получить информацию о ходе тестирования. Рассмотрим варианты сохранения:
Данные из инструмента тестирования сохраняются в текстовом файле (как и в двух предыдущих случаях). Текстовый формат выдачи информации делает возможным включать различные обработчики отчетов основанные на скриптовых языках (например, при помощи 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.
Сообщения об ошибках и предупреждениях
Для того, чтобы иметь представление о возможностях продукта, опишем то, какие ошибки и потенциальные ошибки могут присутствовать в тестируемом приложение. Отметим разницу между ошибкой и потенциальной ошибкой и опишем: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
{
char a;
delete[] &a;//FIM: в динамической памяти нет объектов для уничтожения
return(0);
} FMM: Freeing Mismatched Memory. Сообщение указывает, что программа пробует освобождать память с неправильным ВЫЗОВОМ API для того типа памяти Категория: Allocations and deallocations #include
{
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
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 * 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 демонстрирует окно с включенной сортировкой по числу вызовов каждой функции. В качестве дополнительной информации включен список формальных параметров вызовов функций. Подобную информацию можно получить только в том случае, когда тестируется модуль с отладочным кодом, к которому прилагается исходный текст. Единицы измерения длительности работы функций могут быть следующими:
Полученная таблица вызовов анализируется тестером или разработчиком для выяснения узких мест в производительности приложения. К сожалению, для принятия решения о производительности приложения, а точнее, производительности отдельных его функций можно принимать только рассматривая данный вопрос в комплексном разрезе. А именно, принимается во внимание и число вызовов каждой функции, и среднее время доступа к функции и общее время работы функции, и, наконец, то использовались ли при компиляции определенные специфические настройки компилятора. Это комплексный подход, не предполагающий однозначного совета. Сначала рассмотрим описание столбцов в появившейся таблице. Хотя многие из пунктов и являются интуитивно понятными, все же попробуем дать им короткое описание: Причем, число вызовов не всегда может быть показателем медлительности функции (вызывается часто, а работает быстро). Трудно давать какие либо советы по оптимизации кода, тем более, что в этой редакции мы не ставили перед собой подобных целей. По теории оптимизации написаны громадные труды, к которым мы с радостью и отправляем читателей. Можно, конечно, дать общие рекомендации по улучшению производительности кода и его эффективности: Использовать конструкцию "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 уровнях исполнения:Сравнивание запусков "Compare Runs"
В большинстве случаев требуется иметь не только сведения об отдельных запусках, но и сравнения разных запусков в различных комбинациях для прогнозирования и анализа. Ведь всегда интересно знать быстро работает исправленная функция или медленно, по сравнению с тем, что было до этого. Подобная аналитическая информация позволить иметь достаточно четкое представление о том находятся ли функции в прогрессирующем или в регрессирующем состоянии.
Для вызова модуля сравнения необходимо воспользоваться кнопкой (Compare Runs), выделив один из запусков, и указав на любой другой (каждый новый запуск отображается в истории запусков на левой части рабочего поля Quantify). Для осуществления не пустого сравнения, в пример, рассмотренный выше, намеренно были внесены изменения, увеличившие число вызовов функций. Данные были сохранены и перекомпилированы и снова исполнены в Quantify. Результат представлен на рисунке:
Сравнение запусков позволяет проводить сравнительный анализ между базовым запуском (base – том, с которого все началось) и новым (new). Результаты сравнения также остаются в проекте Quantify и сохраняются на протяжении жизненного цикла разработки проектов. Наравне со сравнением запуск можно воспользоваться суммированием, нажав на кнопку
. Эта функция вызывает простое суммирование чисел от двух запусков. Можно сравнивать и складывать также запуски вместе со слепками (snapshot), которые позволяют оформить текущее количественное состояние в работе приложения в виде отдельного запуска. В дальнейшем над ним можно провести любую логическую операцию. Ценность слепков проявляется тогда, когда необходимо узнать число вызовов каждой функции до свершения определенного события, например, до входа в определенный пункт меню в тестируемом приложении.
Тестирование сервисов Windows NT//XP
Одно из важных преимуществ перед конкурентами — возможность тестирования специальных приложений, таких как сервисы. собенность работы заключается в том, что сервис нельзя запускать из среды разработки. Инструменты тестирования понимают это, и предлагают свое решение, по которому необходимо насытить код откомпилированного сервиса (с отладочной информацией) отладочной информацией (не запустив при этом). Полученные в результате файл зарегистрировать в качестве сервиса. Сделать это можно как из GUI так и из командной строки. Мы рассмотрим последовательность шагов для командной строки, демонстрируя ее возможности (в примере, используется ссылка на Purify, но вместо него подставить имя любого средства тестирования):Приведем список программных продуктов, рекомендованных
Приведем список программных продуктов, рекомендованных к использованию в тестировании на разных этапах. Вот их наименования и краткое описание: 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", в результате которого можно выбрать имя внешнего модуля и аргументы его вызова. В качестве параметров настройки можно выбрать метод вставки отладочного кода:
Начало насыщения тестируемого приложения сопровождается появлением окна инструментирования, в котором построчно отображаются все модули, вызываемые основным. Данные модули, как говорилось выше, насыщаются отладочным кодом и помещаются в специальную директорию "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 и произведены соответствующие изменения скриптов. Помимо этого, были произведены следующие действия по устранению нестыковок и неоднородностей:
Генерация текста стандарта
База данных активно используется при генерации текста стандарта 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 предоставляет средства для генерации не только самого текста стандарта, но и различных объектов, зависящих от этого стандарта. К таким объектам относятся:Информация о дистрибутивах
Особенностью стандарта 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). Более точно, в этой базе данных содержится информация о следующих объектах:
Рис.1.ER-диаграмма сущностей, описываемых стандартом LSB.
Также в базе данных хранятся связи между указанными объектами - каждая константа привязана к заголовочному файлу, в котором она объявляется, интерфейс - к библиотеке, в которой он содержится, либо к классу, если это метод класса, и т.д. ER-диаграмма существующей базы данных приведена на Рис. 1. Все сущности группируются в так называемые модули согласно своему назначению (например, модуль LSB_Сpp содержит все, относящееся к стандартной библиотеке C++, LSB_Toolkit_Qt3 - все, относящееся к библиотеке Qt3, и т.д.). Информация о форматах файлов ELF и RPM относится к модулю LSB_Core, однако база данных этого факта никак не отражает - об этом "знают" только скрипты, генерирующие текст стандарта. Связи типа "многие ко многим" реализуются посредством использования отдельных таблиц. Самой сложной структурой обладает взаимосвязь таблиц ClassInfo и Interface. Для ее реализации используется несколько вспомогательных таблиц, содержащих информацию о виртуальных таблицах класса (таблица Vtable; каждый класс может иметь одну либо две виртуальные таблицы) и о наследовании (таблица BaseTypes реализует обычное наследование, при описании множественного наследования используется также таблица VMIBaseTypes). Для удобства интерфейсы и классы, входящие в одну библиотеку, разбиваются на группы согласно их назначению.
Информация о таких группах, на которые разбиваются библиотеки, содержится в таблице LibGroup. Так, например, библиотека librt (функции реального времени) разделена на три группы:
Элементарные тесты
Одной из главных задач стандартизации является проведение сертификационного тестирования на соответствие реализации стандарту. LSB, как и большинство промышленных стандартов, имеет большой объем, и написать тесты для проведения сертификации вручную не представляется возможным. Необходима автоматизация процесса создания сертификационного тестового набора. Наличие базы данных стандарта позволяет сгенерировать на ее основе набор элементарных тестов, определяющих, присутствуют ли в системе все объекты, описанные в стандарте 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"). Существует два возможных пути разрешения подобных ситуаций.
Основные результаты этой работы следующие.
Ряд требований, касающихся целостности данных некоторых типов, может быть отражен в самом описании их структуры в спецификациях. При этом проверка соответствующих ограничений происходит при преобразовании из данных реализации в модельные данные. Если эти ограничения нарушаются, такое преобразование становится невозможным и спецификация не может с ними работать, но сообщение об их проверке и обнаруженном несоответствии появится в трассе теста. Некоторые требования можно проконтролировать только в результате многократных обращений к тестируемой функции и проверки каких-то свойств всей совокупности полученных при этом результатов. Эти требования часто неудобно оформлять в виде спецификаций - для них создается отдельный тестовый сценарий (см. ниже),
Формальная спецификация подсистемы управления памятью
На вход генератору тестовых программ подаются формальные спецификации инструкций. Спецификация отдельной инструкции включает в себя описание интерфейса инструкции, функцию выполнения инструкции и ассемблерный формат. В интерфейсе инструкции описываются ее операнды и предусловие. Функция выполнения инструкции вычисляет значения выходных операндов инструкции и обновляет модельное состояние микропроцессора. Ассемблерный формат определяет запись инструкции на языке ассемблера. Для того чтобы описать семантику инструкций, работающих с памятью, необходимо смоделировать устройства 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 различают три типа кэш-памяти:Подготовка тестового воздействия
Рассмотрим подробнее, как генератор конструирует цепочки инициализирующих инструкций, осуществляющих подготовку тестовых воздействий. Для каждой тестовой ситуации описывается способ ее подготовки — последовательность инструкций, которая инициализирует нужные регистры и память. На первый взгляд, задача является тривиальной — генератору достаточно последовательно подготовить тестовые ситуации для инструкций, входящих в состав тестового воздействия. Для подсистемы управления памятью такой способ не работает. Для инструкций, работающих с памятью, подготовка тестовых ситуаций состоит из двух основных частей: инициализация буфера трансляции адресов и инициализация кэш-памяти. Инструкции подготовки буфера трансляции адресов могут изменить состояние кэш-памяти, поэтому при построении программы подготовки тестового воздействия целесообразно сначала осуществлять подготовку буфера трансляции адресов для всех инструкций тестового воздействия, а затем — подготовку кэш-памяти (подготовка кэш-памяти не затрагивает 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 года. К этому времени планируется сделать все ее результаты доступными в виде открытого кода. В составе этих результатов будет следующее.Он будет представлять собой набор программ, которые в результате своего выполнения строят HTML-отчеты о проверенных требованиях и найденных несоответствиях.
Тестовый набор будет иметь систему конфигурации, которая позволитнастраивать тесты на особенности конкретной тестируемой системы, допускаемые стандартом, а также установить параметры, определяющие состав тестируемых модулей и глубину тестирования. В начале проекта 1532 функции LSB были разбиты на 147 групп функционально связанных операций, которые в свою очередь группируются в большие подсистемы в соответствии с общей функциональностью - потоки, межпроцессное взаимодействие, файловая система, управление динамической памятью, математические функции, и т.д. За первые 3 месяца работы были проспецифицированы и снабжены базовыми тестовыми сценариями 320 функций из 41 группы. В процессе формализации было выделено около 2150 отдельных требований стандарта к этим функций. При этом к некоторым функциям предъявляется всего лишь несколько требований, в то время как к другим - несколько десятков. На основе достигнутой производительности можно утверждать, что первая фаза проекта потребует около 15 человеко-лет. Чтобы начать работать в этом проекте, опытному разработчику программного обеспечения (не обычному тестировщику!) требуется около месяца для обучения особенностям технологии и изучения сопутствующих процессов. Полученные предварительные результаты позволяют надеяться, что рассмотренный подход может успешно применяться для проектов такого масштаба.
Разработка тестов на соответствие стандарту
В основе процесса разработки тестов на соответствие стандарту лежит технология UniTesK, разработанная в ИСП РАН на базе многолетнего опыта проектов по тестированию сложного промышленного ПО. Эта технология использует подход к формальному тестированию, разработанный в работах Верно (Bernot) [], Бринксмы (Brinksma) и Тритманса (Tretmans) [,] и описанный в телекоммуникационных стандартах ISO 9646 [] и ITU-T Z.500 []. Основные положения этого подхода можно сформулировать следующим образом ( иллюстрирует связи между упоминаемыми ниже понятиями).Примерами таких критериев являются покрытие ветвей в коде постусловия и покрытие дизъюнктов из дизъюнктивной нормальной формы условий этих же ветвлений. Есть возможность вводить произвольные критерии, описывая дополнительные ситуации, в которых работу системы необходимо проверить.
Сравнение с существующими подходами
Нам известен только один подход к автоматизированному построению тестовых программ для MMU, использующий формальные спецификации подсистемы, — DeepTrans, разработка исследовательской лаборатории IBM в г. Хайфе (IBM Haifa Research Lab) . Эта методика нацелена на тестирование части подсистемы, отвечающей за преобразование виртуальных адресов. В DeepTrans используется язык спецификации, специально созданный для моделирования механизмов трансляции адресов. В спецификации процесс преобразования адреса представляется в виде ориентированного ациклического графа. Вершины графа соответствуют отдельным стадиям трансляции адреса, ребра — переходам между стадиями. Из каждой вершины может выходить несколько дуг, каждая из которых помечена своим условием перехода. Подход DeepTrans предполагает ручную разработку шаблонов тестовых программ, но в шаблонах можно указывать тестовые ситуации, связанные с трансляцией адресов, извлеченные из спецификации. На основе шаблонов генератор Genesys-Pro путем разрешения ограничений строит набор тестовых программ [6, 7, 8]. Сравнение DeepTrans (в паре с генератором Genesys-Pro) с используемым нами подходом (MicroTESK) приводится в таблице 1. Таблица 1. Сравнение DeepTrans с MicroTESK| Поддержка пользовательских шаблонов тестовых программ | Genesys-Pro предоставляет развитый язык описания шаблонов тестовых программ | MicroTESK позволяет описывать лишь сравнительно простые шаблоны тестовых программ |
| Поддержка автоматической генерации тестовых шаблонов | Автоматическая генерация тестовых шаблонов не поддерживается | Поддерживается автоматическая генерация несложных тестовых шаблонов |
| Поддержка описания тестовых ситуаций | Поддерживается | Поддерживается |
| Поддержка описания зависимостей по адресам | Специальной поддержки нет, зависимости вручную описываются в тестовом шаблоне | Поддерживается описание сложных зависимостей по адресам |
| Поддержка моделирования подсистемы управления памятью | DeepTrans предоставляет специальный декларативный язык описания механизмов трансляции | В MicroTESK реализована библиотека классов на языке Java, моделирующая устройства MMU |
| Поддержка генерации самопроверяющих тестов | Поддерживается | Поддерживается |
Тестирование подсистемы управления памятью
Подсистема управления памятью современного микропроцессора имеет очень сложную организацию, что существенно затрудняет ее ручное тестирование. Эффективная проверка MMU возможна только при использовании методов автоматизации разработки тестов. Одним из наиболее распространенных способов тестирования микропроцессоров и их подсистем является прогон так называемых тестовых программ, то есть программ специально разработанных для целей тестирования. В процессе выполнения тестовые программы создают разнообразные ситуации в работе микропроцессора, результаты выполнения программ протоколируются и используются для анализа правильности функционирования микропроцессора. Данная работа посвящена автоматизации генерации тестовых программ, предназначенных для функционального тестирования подсистемы управления памятью.Типы тестовых ситуаций для подсистемы управления памятью
Под тестовой ситуацией для инструкции понимается ограничение на значения входных операндов и состояние микропроцессора перед началом выполнения инструкции. Множество возможных тестовых ситуаций выявляется на основе анализа описания архитектуры микропроцессора. При тестировании MMU структура тестовых ситуаций определяется функциональностью подсистемы, связанной с обработкой запросов к памяти. Например, на первом этапе обработки определяется сегмент адресного пространства, к которому относится виртуальный адрес: если сегмент является отображаемым, трансляция адреса осуществляется с помощью TLB; в противном случае, вычисление физического адреса производится в обход буфера трансляции адресов. Для данного этапа определены две тестовые ситуации: Mapped = true и Mapped = false. Далее, если виртуальный адрес является отображаемым, возможны две альтернативные ситуации: TLBHit = true, когда страница виртуальной памяти находится в TLB (попадание), и TLBHit = false, когда страницы в буфере нет (промах). Выделение ситуаций можно продолжить до тех пор, пока данные из памяти не будут загружены в регистр или, наоборот, сохранены из регистра в память (в зависимости от операции). Для подсистемы управления памятью можно выделить следующие типы элементарных ситуаций (ситуаций для отдельных этапов обработки), на основе композиций которых строятся результирующие тестовые ситуации:
Рисунок 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:
Рисунок 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).

Рисунок 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 имеет, тем не менее, некоторые недостатки, из-за которых использование её не всегда оправдано.
CUnit
Система CUnit [6] может использоваться в тех же случаях, что и Check [3], но обладает в целом более скромными возможностями.Основным недостатком CUnit по сравнению с Check является то, что все тесты, a также система их запуска и сбора результатов, в данном случае выполняются в одном и том же процессе. Т.е. сбой какого-либо из тестов может привести к повреждению области памяти, используемой другими тестами или самой средой CUnit.
Также в отличие от Check в CUnit нет защиты от "зависания" тестов (нельзя задать максимально допустимое время работы теста).
CUnit имеет и некоторые преимущества по сравнению с Check.
Тем не менее, указанные в предыдущем разделе недостатки системы Check в полной мере относятся и к CUnit. Test Environment Toolkit (TET), о котором речь пойдёт ниже, свободен от некоторых из этих недостатков.
Достоинства и недостатки существующих решений
Мы рассмотрели 5 подходов к разработке тестов для программных интерфейсов на языке программирования Си. Достоинства и недостатки каждого из них сведены в Таблице 1.Все рассмотренные подходы обладают определёнными достоинствами. Тем не менее, с точки зрения тестирования программных интерфейсов на соответствие требованиям стандарта все они обладают существенным недостатком, заключающемся в отсутствии поддержки прослеживаемости проверок в тестах к требованиям стандарта. Кроме того, ни один из этих инструментов не сочетает в себе все указанные выше достоинства, хотя явных противоречий между ними не существует.
При разработке набора инструментов T2C была поставлена задача обеспечить прослеживаемость требований стандарта, реализовав при этом все достоинства существующих решений, представленные в Таблице 1, за исключением иерархической организации пакетов тестов. Данное исключение связано с тем, что возможность иерархической организации пакетов не оказывает существенного влияния на разработку и эксплуатацию тестов на соответствие требованиям стандарта.
|
|
MANUAL |
Check |
CUnit |
TET |
GTKVTS |
| Параметризация тестов |
– |
– |
– |
– |
+ |
| Прослеживаемость требований стандарта |
– |
– |
– |
– |
– |
| Запуск тестов в отдельном процессе |
– |
+ |
– |
– |
– |
| Автоматическая обработка исключительных ситуаций |
+ |
+ |
– |
+ |
+ |
| Ограничение времени работы теста |
– |
+ |
– |
+ |
+ |
| Иерархическая организация пакетов |
+ |
– |
– |
– |
– |
| Удобство отладки тестов |
+ |
+ |
+ |
– |
– |
| Переносимость тестов |
– |
+ |
+ |
+ |
– |
| Использование стандартных видов вердиктов [5] |
– |
– |
– |
+ |
+ |
Генерация кода тестов, 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.
Подготовка каталога требований
По размеченной документации с помощью инструмента ReqTools, входящего в состав T2C, в web-браузере создаётся каталог требований для данной группы интерфейсов. Этот каталог используется при работе теста: если проверка в тесте показывает, что некоторое требование нарушено, из каталога загружается формулировка этого требования с соответствующим идентификатором и выводится в трассу теста.Применение T при разработке тестов для LSB Desktop
Система T2C использовалась (и используется в настоящее время) при разработке тестов для интерфейсных операций ("интерфейсов") библиотек Linux, входящих в стандарт Linux Standard Base (LSB). C её помощью подготовлены тесты базовой функциональности для следующих библиотек:В Таблице 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 |
Примечание 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% |
Процесс разработки тестов с помощью системы 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 являются:
Наиболее существенными достоинствами TET, на наш взгляд, являются следующие:
Эти преимущества TET облегчают анализ результатов работы тестов. В частности, программные средства для обработки журнала TET при построении статистики по результатам тестов могут не учитывать специфику проведённых тестов.
С другой стороны, средства TET, в основном, автоматизируют выполнение тестов и сбор результатов их работы. TET не предоставляет ни инструментов для автоматизации разработки тестов, ни API для выполнения проверок в тестах. Соответственно, есть несколько причин, из-за которых применение TET "в чистом виде" (без использования каких-либо "надстроек" над ним) не очень удобно.
Ниже речь пойдёт как раз о "надстройках" над 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 позволяет повысить эффективность разработки таких тестов за счет использования следующих основных возможностей, минимизирующих ручной труд на создание окружения и дублирование кода, не принципиального для специфической логики конкретного теста:
Среда выполнения тестов, созданных по технологии 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 | 190 | 16380 |
Обход полного графа и его модификаций
В данном разделе будут рассмотрены экспериментальные данные по построению обхода для следующих видов графов.| 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 |
E(KM(KN)) = M × E(KN) + E(KM) = M × N × (N - 1) + M × (M - 1).
| 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 |
D(KM(KN))=M × D(KN) + D(KM). Можно предположить, что при обходе графа, состоящего из нескольких слабо связанных (при помощи одной-двух дуг) частей, длина его обхода получается сложением длин обходов частей и количества проходов по связывающим дугам. Оценка длины обхода графа KM KN снизу также равна количеству его дуг
E(KM × KN) = M × E(KN) + N × E(KM) = M × N × (N + M - 2).
| 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 |
Сфера применения тестирования на основе спецификаций
Тестирование на основе спецификаций максимально эффективно при тестировании групп функций с объемным, логически связным текстуальным описанием, а также функций с закрытым кодом. Рассмотрим преимущества подхода тестирования на основе спецификаций, на примере технологии 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 - выталкивание функции из этого стека.| 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 |
Тестирование софта - статьи
Планирование тестов
Первый вопрос, который встает перед нами: "Сколько нужно тестов". Ответ, который часто дается: тестов должно быть столько, чтобы не осталось неоттестированных участков. Можно даже ввести формальное правило:Код с не оттестированными участками не может быть опубликован Проблема в том, что хотя неоттестированный код почти наверняка неработоспособен, но полное покрытие не гарантирует работоспособности. Написание тестов исходя только из уже существующего кода только для того, чтобы иметь стопроцентное покрытие кода тестами — порочная практика. Такой подход со всей неизбежностью приведет к существованию оттестированного, но неработоспособного кода. Кроме того, метод белового ящика, как правило, приводит к созданию позитивных тестов. А ошибки, как правило, находятся негативными тестами. В тестировании вопрос "Как я могу сломать?" гораздо эффективней вопроса "Как я могу подтвердить правильность?". Это наглядно демонстрирует статья 61 тест, который потряс программу. В первую очередь тесты должны соответствовать не коду, а требованиям. Правило, которое следует применять:
Тесты должны базироваться на спецификации. Пример такого подхода можно посмотреть в статье Тривиальная задача.
Один из эффективных инструментов, для определения полноты тестового набора — матрица покрытия.
На каждое требование должен быть, как минимум, один тест. Неважно, ручной или автоматический. При подготовке тестового набора рекомендую начать с простого позитивного теста. Затраты на его создание минимальны. Да вероятность создания кода, не работающего в штатном режиме, гораздо меньше, чем отсутствие обработки исключительных ситуаций. Но исключительные условия в работе программы редки. Как правило, все работает в штатном режиме. Тесты на обработку некорректных условий, находят ошибки гораздо чаще, но если выяснится, что программа не обрабатывает штатные ситуации, то она просто никому не нужна. Простой позитивный тест нужен т.к. несмотря на малую вероятность нахождения ошибки, цена пропущенной ошибки чрезмерно высока. Последующие тесты должны создаваться при помощи формальных методик тестирования. Таких как, классы эквивалентности, исследование граничных условий, метод ортогональных матриц и т.д.. Тестирование накопило довольно много приемов подготовки тестов и если эти приемы создавались, то видимо было зачем.
Последнюю проверку полноты тестового набора следует проводить с помощью формальной метрики "Code Coverage". Она показывает неполноту тестового набора. И дальнейшие тесты можно писать на основании анализа неоттестированных участков.
Наиболее эффективный способ создания тестового набора — совместное использование методов черного и белого ящиков.
Распределение обязанностей
Где-то я читал следующую фразу: "Попросите программиста составить для вас (тестера) план тестов". А потом тестер будет кодировать тесты. Генеральный директор рисовать дизайн, а администратор баз данных писать руководство пользователя. Не очень воодушевляющая картина.Кодировать модульные тесты проще всего программисту, который пишет исходный код. Но перед этим их нужно придумать. Кодирование тестов и разработка тестовых сценариев это две разные задачи. И для последней нужны навыки дизайнера сценариев. Если программист ими обладает, то все прекрасно. Если нет, то имеет смысл поручить это тестировщику. Нужно просто четко определить решаемые задачи и навыки, необходимые для их решения.
| Определение методов обеспечения качества ПО | Отличное знание теории тестрования | Ведущий тестировщик проекта |
| Создание тестов | Хорошее знание методов тестирования | Дизайнер тестовых сценариев |
| Кодирование тестов | Средние навыки программирования | Программист автоматических тестов |
| Выполнение тестов | Знание среды выполнения тестов | Тестер |
Не важно кто конкретно будет выполнять работу, и как будет называться должность. Главное, чтобы сотрудник обладал необходимыми навыками.
Стратегия модульного тестирования
Модульное тестирование является одной из ключевых практик методологии экстремального программирования. Сторонники XP приводят следующие доводы в защиту этой практики:Ключевой фактор при оценке перспективности любого метода — стоимость проекта. Дополнительная работа по созданию тестов, их кодированию и проверке результатов вносит существенный вклад в общую стоимость проекта. И то, что продукт окажется более качественным не всегда перевешивает то, что он будет существенно дороже.
Известно, что продукт оптимальный по набору бюджет/функциональность/качество получается при применении различных способов обеспечения качества. Бездумное применение тотального модульного тестирования почти гарантированно приведет к получению неоптимального продукта. И никакие "запасы прочности" и "быстрый вход в рабочий ритм" не спасут проект от провала.
На мой взгляд, модульное тестирование оправдано, если оно:
Если в результате исправления ошибок интеграции меняется исходный код, в нем с большой вероятностью появляются ошибки. Если в результате добавления новой функциональности меняется исходный код, в нем с большой вероятностью появляются ошибки. И искать их лучше с помощью ранее созданных модульных тестов.
Цель модульного тестирования: Получение работоспособного кода с наименьшими затратами.
И его применение оправдано тогда и только тогда, когда оно дает больший эффект, нежели другие методы. Отсюда следует несколько выводов:
Терминология
Unit testing (юнит тестирование или модульное тестирование) — заключается в изолированной проверке каждого отдельного элемента путем запуска тестов в искусственной среде. Для этого необходимо использовать драйверы и заглушки. Поэлементное тестирование — первейшая возможность реализовать исходный код. Оценивая каждый элемент изолированно и подтверждая корректность его работы, точно установить проблему значительно проще чем, если бы элемент был частью системы.Unit (Элемент) — наименьший компонент, который можно скомпилировать.
Драйверы — модули тестов, которые запускают тестируемый элемент.
Заглушки — заменяют недостающие компоненты, которые вызываются элементом и выполняют следующие действия:
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 были:В 2003 году был проведен пилотный проект применения CTesK для тестиро-вания ПО реального времени (алгоритмы управления навигационными устройствами, ГосНИИАС). Несмотря на сжатые сроки проекта удалось найти серьезную ошибку в алгоритме. В середине 2002 года была готова первая коммерческая версия инструмента J@T, инструмента разработки тестов для Java программ .
Основное же их преимущество в том, что они позволяют автоматически построить оракулы [Parnas,KVEST,ADLt], проверяющие соответствие поведения целевой системы спецификациям, и критерии тестового покрытия, которые достаточно близки к критериям покрытия требований.
Например, при тестировании блоков оптимизации в компиляторах разработка спецификаций функциональности такого блока в полном объеме, если и возможна, то очень трудоемка, поскольку они должны, например, выражать тот факт, что оптимизация программы действительно была проведена. В то же время, сравнить быстродействие и проверить неизменность функциональности тестовых программ специального вида довольно легко, выполняя их на конечном наборе входных значений, что дает способ построения оракулов, хотя и не столь общий, как описанный выше, но достаточный для практических целей [OPT].
Подход UniTesK к разработке тестов: достижения и перспективы
А.В. Баранцев, И.Б. Бурдонов, А.В. Демаков, С.В. Зеленов, А.С. Косачев, В.В. Кулямин, В.А. Омельченко, Н.В. Пакулин, А.К. Петренко, А.В. ХорошиловТруды
Построение тестовых последовательностей
UniTesK использует конечно-автоматные модели целевого ПО в виде тестовых сценариев для динамической генерации последовательностей тестовых воздействий. Сценарий определяет, что именно рассматривается как состояние автомата и какие операции с какими наборами аргументов должны быть вызваны в каждом состоянии. Во время выполнения теста обходчик строит некоторый "исчерпывающий" путь по переходам автомата, порождая тем самым тестовую последовательность.Такой метод построения теста гарантирует, что состояние системы изменяется только за счет вызовов целевых операций, и только достижимые этим способом состояния будут возникать во время тестирования. Таким образом, перебор состояний осуществляется автоматически, и разработчику теста достаточно указать только нужный способ перебора аргументов вызываемых операций.
При разработке сценария можно использовать некоторый критерий покрытия спецификаций в качестве целевого и определить набор состояний и переходов таким образом, чтобы обход всех переходов в получившемся автомате гарантировал достижение нужного покрытия. Для этого достаточно рассмотреть набор предикатов, определяющий элементы выбранного критерия покрытия для некоторой тестируемой операции, как набор областей в пространстве состояний и аргументов этой операции, и взять проекции полученных областей на множество состояний.
Построив все возможные пересечения таких проекций для всех тестируемых операций, мы получим набор таких множеств состояний, что, вызывая любые операции в двух состояниях из одного такого множества, можно покрыть одни и те же элементы по выбранному критерию покрытия. Следовательно, все такие состояния системы эквивалентны с точки зрения выбранного критерия покрытия, и можно объявить состоянием результирующего автомата полученные множества. Стимулами в таком автомате считаются классы эквивалентности вызовов операций по выбранному критерию покрытия, т.е. покрывающие один и тот же его элемент. Может потребоваться дополнительно преобразовать полученный автомат, чтобы сделать его детерминированным, подробности см.
в [FACTOR].
При проведении тестирования можно использовать автоматически сгенерированные из спецификаций фильтры, отсеивающие наборы аргументов, не дающие вклада в уже достигнутое покрытие. Наличие таких фильтров позволяет во многих случаях не тратить усилий человека на вычисление необходимых для достижения нужного покрытия аргументов, а указать в качестве перебираемого набора их значений некоторое достаточно большое множество, которое наверняка содержит нужные значения. Так UniTesK позволяет проводить тестирование, нацеленное на достижение высоких уровней покрытия, не затрачивая на это значительных ресурсов.
Тестовый сценарий представляет конечный автомат в неявном виде, т.е. состояния и переходы не перечисляются явно, и для переходов не указываются конечные состояния. Вместо этого определяется способ вычисления текущего состояния и метод сравнения состояний, способ перебора допустимых воздействий (тестируемых операций и их аргументов), зависящий от состоя-ния, и процедура применения воздействия. Хотя такое представление авто-матных моделей необычно, оно позволяет описать в компактном виде довольно сложные модели, а также легко вносить модификации в полученные модели.
Сценарий может определять состояния описываемой автоматной модели, осно-вываясь не только на модельном состоянии, описанном в спецификациях, но и учитывая какие-то аспекты реализации, не нашедшие отражения в специфи-кациях. С другой стороны, можно также абстрагироваться от каких-то деталей в спецификациях, уменьшая тем самым число состояний в результирующей модели (см. [FACTOR]). Таким образом, способ построения теста может варьироваться независимо от спецификаций, и, следовательно, независимо от механизма проверки корректности поведения при единичном воздействии. Тестовые сценарии можно разрабатывать вручную, но в большинстве случаев они могут быть сгенерированы при помощи интерактивного инструмента, шаблона построения сценариев, который запрашивает у пользователя только необходимую информацию, и может использовать разумные умолчания.
Шаблон построения сценариев помогает строить как сценарии, не использующие фильтрацию тестовых воздействий, так и нацеленные на достижение высокого уровня покрытия по одному из извлекаемых из спецификаций критериев.
Тестовые сценарии, написанные в терминах спецификаций, тем самым опреде-ляют абстрактные тесты, которые можно использовать для тестирования любой системы, описываемой данными спецификациями. Кроме того, сценарии имеют дополнительные возможности для переиспользования при помощи ме-ханизма наследования. Сценарий, наследующий данному, может переопреде-лить в нем процедуру вычисления состояния и переопределить или пополнить набор тестовых воздействий, оказываемых на систему в каждом состоянии.
Для тестирования параллелизма и распределенных систем UniTesK предполагает использование специального вида обходчиков, которые генерируют пары, тройки и более широкие наборы параллельных воздействий в каждом состоянии, и слегка расширенных спецификаций. В дополнение к спецификациям операций, моделирующих воздействия на целевую систему и ее синхронные реакции на эти воздействия, можно специфицировать асинхронные реакции системы, каждая из которых оформляется в виде операции без параметров, имеющей пред- и постусловия.
Без спецификаций асинхронных реакций можно тестировать системы, удовлетворяющие аксиоме чистого параллелизма (plain concurrency axiom): результат параллельного выполнения любого набора вызовов операций такой системы такой же, как при выполнении того же набора вызовов в некотором порядке. Для систем, не удовлетворяющих этой аксиоме, можно ввести дополнительные "срабатывания", соответствующие выдаче асинхронных реакций или внутренним, не наблюдаемым извне, изменениям состояния системы, таким образом, что полученная модель уже будет "чистой" (plain).
Автоматные модели, используемые для тестирования таких систем, являются некоторым обобщением автоматов ввода/вывода [IOSMA]. При проведении тестирования "чистой" системы используется следующий метод проверки корректности ее поведения.
Если обработанные системой воздействия и полу- ченные от нее асинхронные реакции можно линейно упорядочить таким образом, что в полученной последовательности перед каждым вызовом или реакцией будет выполнено его/ее предусловие, а после - постусловие, то система ведет себя корректно. Фактически, это означает, что ее наблюдаемое поведение не противоречит спецификациям. Если такого упорядочения построить нельзя, значит обнаружено несоответствие поведения системы спецификациям.
Помимо указанных выше возможностей, тестовые сценарии UniTesK дают пользователю возможность проводить тестирование, основанное на обычных сценариях, т.е, последовательностях воздействий, формируемых по указанному разработчиком теста правилу, корректность поведения системы при которых тоже оценивается задаваемым разработчиком способом. В качестве таких сценариев для системного тестирования можно, в частности, использовать сценарии, уточняющие варианты использования целевой системы.
Другой способ построения сценариев дают аксиоматические спецификации, описывающие правильное поведение системы в виде ограничений на результаты выполнения некоторых цепочек вызовов целевых операций. Каждая такая цепочка вместе с проверкой наложенных на ее результаты ограничений может быть оформлена в тестовом сценарии в виде одного воздействия, которое будет выполняться во всех состояниях, где оно допустимо. Аксиомы алгебраического вида, требующие эквивалентных результатов от двух или нескольких цепочек вызовов, также могут быть проверены за счет оформления каждой цепочки в виде отдельного воздействия и сравнения ее результатов с результатами ранее выполненных в том же самом состоянии цепочек. Тестовые сценарии предоставляют удобный механизм для хранения промежуточных данных (в данном случае, результатов предыдущих цепочек) при идентификаторе состояния.
Процесс построения тестов по UniTesK
В приведенной ниже таблице процесс разработки спецификаций и тестов и собственно тестирования в UniTesK представлен как последовательность шагов. На практике, конечно, бывает необходимо вернуться к ранее пройденным шагам и пересмотреть уже принятые решения, но общая логика процесса - это движение от требований к получению тестов и анализу результатов тестирования.| 1. Анализ функциональных требо-ваний к целевому ПО на основе имеющихся документов или знаний участников проекта, запись требований в виде формальных спецификаций. | ![]() |
| 2. Формулировка требования к качеству тестирования - какой уровень тестового покрытия будет считаться достаточным, чтобы прекратить тестирование. | ![]() |
| 3. Разработка набора тестовых сценариев, обеспечивающего достижение нужного уровня покрытия. Сценарии разрабатываются на основе спецификаций и не привязаны к конкретной реализации целевого ПО или его конкретной версии. | ![]() |
| 4. Привязка полученных тестовых сценариев к конкретной реализации целевой системы. Для этого надо разработать набор медиаторов. | ![]() |
| 5. Получение готовой к исполне-нию тестовой программы. Для того нужно оттранслировать спецификации, медиаторы и сценарии с расширения языка программирования в целос-тную тестовую систему на целевом языке программи-рования и скомпилировать ее. | ![]() |
| 6. Выполнение тестовой прог-раммы, возможно, отладка спецификаций, медиаторов и тестовых сценариев. | ![]() |
| 7. Анализ результатов, а именн,о анализ полноты тестового пок-рытие и принятие решения и продолжении или прекра-щении тестирования. Оформ-ление описания ошибок. | ![]() |

Рис. 2. Получение компонентов теста по UniTesK.
Теперь рассмотрим действия, которые нужно предпринимать на разных шагах технологии более подробно.
Сравнение с другими подходами к разработке тестов на основе моделей
Хотя практически любая функциональная характеристика технологии UniTesK может быть найдена и в других технологиях и методах разработки тестов, иногда даже в более развитой форме, ни один из имеющихся подходов к разработке тестов, предлагаемый в академическом сообществе или в индустрии разработки ПО, не обладает всей совокупностью характеристик UniTesK.В кратком обзоре, помещенном в этом разделе, мы сконцентрировали внимание на методах разработки тестов, поддержанных инструментами и нацеленных на использование в промышленной разработке ПО. Таким образом, множество интересных методов и техник осталось за рамками обзора.
Имеющиеся подходы к разработке тестов, в основном, используют стандартную, сложившуюся еще несколько десятилетий назад архитектуру теста. Тест в ней представляет собой набор тестовых вариантов (test cases), каждый из которых служит для проверки некоторого свойства целевой системы в определенной ситуации. В UniTesK тесты строятся в виде сценариев, каждый из которых, по существу, исполняет роль целого набора тестовых вариантов, проверяющих работу целевой системы при обращении к выделенной группе интерфейсов в различных ситуациях. В результате, в тестовом наборе UniTesK больше уровней иерархии, что удобно при тестировании больших и сложных систем. С другой стороны, тестовые варианты позволяют более эффективно воспроизводить нужные ситуации при повторном тестировании, например, на наличие ранее обнаруженной ошибки. Для регрессионного тестирования обе схемы пригодны в равной степени, поскольку при этом обычно требуется возможность прогона всего набора тестов.
Автоматическое построение тестовых оракулов на основе спецификаций отличает UniTesK от инструментов, таких как JUnit [JUNIT], автоматизирующих только выполнение тестов. Вместе с тем, оно поддерживается очень многими существующими инструментами, например, следующими:
В инструменте 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 [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):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 описывает семантику поведения сервиса в синхронном стиле. По этим причинам в рассматриваемом методе к формализации требований мы моделируем обмены сообщениями между клиентом и ИПО Грид как процедурные вызовы. В модельное состояние ресурса необходимо включить переменные для представления свойств ресурса и окружения:Что же касается второго и третьего пунктов, то на них стоит остановиться поподробнее. Свойство ресурса как элемента 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 |
Синтаксические требования налагают ограничения на структуру сообщений и связи между полями одного сообщения. Функциональные требования представляют собой ограничения на функциональность обработки запросов и связь между содержимым запроса и ответом на запрос.
| WS-BaseFaults | WS-Resource | WS-Resource Properties | WS-Resource Lifetime | WS-ServiceGroup | |
| Синтаксис | 27 | 1 | 50 | 12 | 5 |
| Семантика | 2 | 11 | 109 | 39 | 68 |
Разработка медиатора
Основная функция медиатора, как компонента тестового набора, ? это установление соответствия между модельным объектом (объектом спецификационного класса) и целевой системой. В случае тестирования ИПО Грид возможность непосредственного доступа к полям и методам системы отсутствует, потому воздействовать на ГС можно было лишь посредством отправки соответствующих сообщений-запросов, а реакции принимать ? обрабатывая отклики. Соответственно, главной функцией медиатора является генерация сообщений-запросов и разбор сообщений-откликов с целью выявления полезной информации о выполнении операции. В рассматриваемом методе медиаторы преобразуют параметры вызова спецификационного метода в XML сообщение, преобразуют в сообщение протокола SOAP/HTTP и отсылают его в целевую систему по установленному TCP соединению. Полученные ответы медиатор разбирает, проверяет соответствие ответа синтаксическим требованиям и формирует возвращаемое значение спецификационной функции. Одна из особенностей разработки медиаторов для ИПО Грид заключается в том, что существующие реализации (в частности, GlobusToolkit 4.2) не удовлетворяют синтаксическим требованиям стандарта. Для проведения тестирования потребовалась адаптация медиаторов под нарушения стандарта, допускаемых реализацией. К примеру, Globus Toolkit 4.2 реализует один из последних предварительных проектов стандарта (draft) и поддерживает устаревшее пространство имен XML элементов (namespace); в результате реализация отвергала запросы, сформированные в соответствии со стандартом. Также путем анализа исходного кода ИПО ГС Globus Toolkit 4.2, а также ряда экспериментов было выяснено, что реализацией не поддерживаются следующие обмены сообщениями ? PutResourceProperties, SetResourceProperties.Разработка тестового сценария
Следующим этапом разработки тестового набора являлась разработка тестовых сценариев. Тестовым сценариям в технологии UniTESK отводится немаловажная роль. Как уже указывалось ранее, для их построения используется конечно-автоматная модель целевого ПО. При таком выборе модели актуальны ответы на следующие вопросы:Другим важным недостатком такого подхода является его избыточность. Действительно, с точки зрения тестирования методов add и remove совершенно необязательно обходить все ребра такого графа (т.к. все свойства такого WS-ресурса с “точки зрения” данных методов эквивалентны). Достаточно обработать лишь некоторые (например, ребра, идущие из состояния, в котором все n свойств WS-ресурса равны 1 и ребра, идущие из начального состояния). Потому в ряде случаев представляется разумным выбирать другую модель состояния тестируемой системы. При разработке тестового сценария для тестирования соответствия ИПО Грид стандарту WSRF в качестве идентификатора состояния автомата теста мы предлагаем использовать мощность RPD WS-ресурса, т.е. количество свойств WS-ресурса. С одной стороны, такой способ существенно уменьшает размер предполагаемого графа состояний а, следовательно, и время работы тестов. С другой стороны, при таком задании состояния сохраняется детерминированность графа, что принципиально для корректной работы обходчика UniTESK. Ответ на второй вопрос традиционен для большинства приложений UniTESK ? переходы между состояниями автомата осуществляются посредством подачи стимулов в целевую систему. В качестве стимулов в случае построения тестового набора для ИПО Грид выступают, естественно, вызовы медиаторных методов. Последние же представляют собой не что иное, как отправки соответствующих XML-сообщений целевому ресурсу. Теперь перейдем к обсуждению третьего вопроса ? заданию автомата теста. Так как используется синхронная модель целевой системы, то для каждого спецификационного метода можно задать отдельный сценарный метод, который будет перебирать параметры метода и вызывать (неявно) медиатор для оказания тестового воздействия и оракул для проверки правильности возвращаемого значения.
Регламентирующие документы и требования к реализациям ИПО Грид
Как упоминалось во введении, в настоящее время при разработке ИПО Грид используются два семейства стандартов: OGSA и WSRF. Рассмотрим особенности формализации требований указанных стандартов. При изучении стандарта OGSA оказалось, что:Так, например, большая их часть функциональных требований снабжена ключевыми словами стандарта 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 описаны следующие обмены сообщениями:
Результаты тестирования
В данной работе была протестирована реализация ИПО ГС 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 для функционального тестирования ИПО Грид, включая тестирования соответствия реализации стандарту. В частности, рассматриваются следующие вопросы:Существующие методы и подходы тестирования ИПО Грид
В 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 этапов:Тестирование реализации
Разработанный с помощью инструмента 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) . Наиболее распространенными подходами к тестированию ГС являются следующие:В данной работе решалась задача
В данной работе решалась задача тестирования соответствия реализации ИПО ГС Globus Toolkit 4.2 стандарту систем управления распределенными ресурсами WSRF 1.2. Данный стандарт был проанализирован и на его основе составлен его каталог требований. Также была исследована структура и интерфейсы компонента Java WS Core программного пакета Globus Toolkit 4.2. На основе полученных данных был разработан тестовый набор для указанного программного пакета с применением технологии тестирования UniTESK (JavaTESK) и проведено тестирование. Тестирование показало, что реализация Globus Toolkit 4.2 соответствует стандарту WSRF, хотя выявило отсутствие выполнения ряда необязательных требований, как семантических, так и синтаксических. Также тестирование показало, что технология UniTESK применима для тестирования ИПО ГС, в частности, имеют место следующие особенности её применения:Тестирование софта - статьи
Аннотация.
В работе обсуждаются вопросы применимости технологии автоматизированного тестирования UniTesK к разработке функциональных тестов для моделей аппаратного обеспечения. Предлагаются способы расширения базовой архитектуры тестовой системы UniTesK для функционального тестирования моделей на языках Verilog HDL и SystemC. Для каждого из указанных классов моделей описывается процесс разработки теста с помощью инструмента CTesK и приводятся оценки возможности автоматизации шагов этого процесса.Архитектура тестовой системы UniTesK
Архитектура тестовой системы UniTesK [] была разработана на основе многолетнего опыта тестирования промышленного программного обеспечения из разных предметных областей и разной степени сложности. Учет этого опыта позволил создать гибкую архитектуру, основанную на следующем разделении задачи тестирования на подзадачи []:Архитектура тестовой системы
При расширении базовой архитектуры тестовой системы CTesK мы учитывали то обстоятельство, что используемый симулятор Icarus Verilog не позволяет напрямую управлять симуляцией через интерфейс VPI [], используемый нами для интеграции тестовой системой с симулятором. Предлагаемая архитектура тестовой системы показана на Рис. 5. Тестовая система состоит из двух потоков:Тестовая система 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, к которым относятся:Краткий обзор технологии 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, множество всех тестовых воздействий должно быть разбито на группы, каждая из которых описывается одной спецификационной функцией. Один из возможных подходов разбиения состоит в следующем. Каждому процессу, определенному в модели, ставится в соответствие отдельная спецификационная функция, параметры которой отражают множество событий из списка чувствительности, реализуемые на управляющих входах, а также значения подаваемых на информационные входы сигналов. В предусловии спецификационной функции, помимо разного рода смысловых проверок, должна осуществляться проверка того, что значения сигналов на управляющих входах процесса позволяют реализовать указанные в параметрах события, а в постусловии - то, что указанные события действительно были реализованы. Проиллюстрируем этот подход на примере. Рассмотрим модуль на языке 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-модели можно осуществить автоматически по следующей схеме:
После того, как создан C-медиатор, разработка медиатора осуществляется по следующей схеме:
Разработка модуля взаимодействия потоков
Модуль взаимодействия потоков реализует функции синхронизации потока симулятора Verilog и потока тестовой системы CTesK. Эти функции используются медиатором и VPI-модулем, поэтому модуль взаимодействия потоков рекомендуется разрабатывать перед разработкой медиатора или VPI-модуля. Модуль взаимодействия потоков должен реализовывать следующие функции:Разработка модуля запуска тестовой системы
Разработка модуля запуска тестовой системы осуществляется по следующей схеме:Разработка теста
В этом разделе подробно рассматривается процесс разработки теста для 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 Процесс разработки теста состоит из следующих шагов:В этом разделе подробно рассматривается процесс разработки теста для 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); } }; Процесс разработки теста состоит из следующих шагов:
Разработка Verilog-окружения
Verilog-окружение представляет собой модуль верхнего уровня на языке Verilog HDL и разрабатывается по следующей схеме:Разработка VPI-модуля
VPI-модуль разрабатывается на языке программирования C с использованием интерфейса VPI. Он должен реализовывать следующие системные функции и задачи:Тестирование 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Вершинами дерева перебора являются глобальные состояния, а дуги в дереве - это возможные переходы из соответствующих глобальных состояний. Пусть заданы обобщенное состояние 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, обладают рядом важных свойств:Этим обеспечивается атомарность обновления данных в базе данных.
Дополнительное обеспечение корректности тестового набора
Описанные в предыдущем разделе техники, дополнительные к базовому подходу технологии 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. Примеры запрещенных иерархий
Реализация методов компонента 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Тестирование
В.В.Кулямин, ИСП РАН
Дэвид Лордж Парнас
Перевод: Виктор Кулямин
, , Труды Института системного программирования РАН
, , , Труды Института системного программирования РАН
, , , Труды Института системного программирования РАН
, , Труды Института системного программирования РАН
, Труды Института системного программирования РАН
, , Труды Института системного программирования РАН
, Труды Института системного программирования РАН
, Труды Института системного программирования РАН
, Труды Института системного программирования РАН
, , , Труды Института системного программирования РАН
, , , , , Труды Института системного программирования РАН
Зацепин Д.В., Шнитман В.З., Труды Института системного программирования РАН
С.В. Зеленов, Н.В. Пакулин, Труды Института системного программирования РАН
, Труды Института системного программирования РАН
Гингина В.В., Зеленов С.В., Зеленова С.А., Труды Института системного программирования РАН
, Труды Института системного программирования РАН
Д.Ю. Кичигин, Труды Института системного программирования РАН
К.А. Власов, А.С. Смачёв, Труды Института системного программирования РАН
, Труды Института системного программирования РАН
А. С. Камкин, Труды Института системного программирования РАН
, Российско-Армянский (Славянский) государственный университет, Ереван, Армения
Труды Института системного программирования РАН
, , Труды Института системного программирования РАН
, Труды Института системного программирования РАН
, Труды Института системного программирования РАН
А.В. Демаков, С.В. Зеленов, С.А. Зеленова, Труды Института системного программирования РАН
, Труды Института системного программирования РАН
С.В. Зеленов, Д.В. Силаков, Труды Института системного программирования РАН
Препринт Института Системного Программирования РАН
, , ,
, ,
, ,
Сергей Мартыненко
Сергей Белов, менеджер проекта компании 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), которые позволяют найти последовательность событий, нарушающих данную темпоральную формулу или доказать, что такой последовательности не существует. Методы проверки моделей работают с замкнутой системой, т.е.
системой, которая не принимает входных воздействий и не выдает реакций. Для верификации других систем, например, предоставляющих программный интерфейс, требуется предоставить окружение, взаимодействующее с системой. От размеров окружения напрямую зависит количество состояний, получающихся в процессе поиска. С другой стороны, от окружения зависит качество верификации, и далеко не всегда можно ограничиться простым окружением. Методы проверки моделей обладают рядом ограничений:
сброса системы в начальное состояние и повторного выполнения. Мы сформулируем задачу для случая, когда в реализации нет механизма отката и нет возможности сброса в начальное состояние. Мы будем предполагать, что вместо отката алгоритму поиска предоставляется возможность возврата в одно из состояний, которое принадлежит некоторому множеству, называемому обобщенным состоянием. Мы предполагаем, что последовательность воздействий на систему строится на основе обхода графа состояний конечного автомата [,]. Состояния автомата являются множествами исходных состояний системы и называются обобщенными состояниями. Множества выбираются, как разбиения состояний системы на классы эквивалентности. Возможность обобщенного отката реализуется за счет требования сильной связности графа состояний, необходимого для построения обхода графа. Таким образом, обобщенный откат - это путь в графе состояний, ведущий в требуемое обобщенное состояние. В следующем разделе описан пример, на котором будет продемонстрирован метод тестирования. В разделе "Алгоритм поиска" описан алгоритм, позволяющий перебирать различные порядки вызовов методов. Далее следует описание архитектуры, необходимой для его работы.
В статье описываются техники, позволяющие
В статье описываются техники, позволяющие адаптировать общий подход технологии 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 целевая система рассматривается как "черный ящик". На входы ей подаются стимулы, а на выходах наблюдаются реакции. Для целей тестирования "черного ящика" важны только возможные соотношения между подаваемыми стимулами и получаемыми реакциями, и неважно, что именно происходит внутри целевой системы. Именно на основе этого принципа определяются границы целевой системы, и строится предлагаемая классификация. Например, пусть имеются три различных системы. Система А обладает одним собственным потоком управления и может в ответ на полученный стимул начать некую собственную активность ограниченной продолжительности; в ходе этой активности она может как принимать новые стимулы, так и выдавать реакции на старые. Система Б аналогична по внешнему поведению, но внутри нее имеется несколько собственных потоков управления. Система В никакой собственной активности не ведет, но также может сразу выдать в ответ на стимул несколько реакций, причем между системой В и тестовой системой находится сеть, которая вносит недетерминированные задержки в передачу как стимулов, так и реакций (случай тестирования через сложную среду). С точки зрения тестирования все эти три системы эквивалентны: про них известно только то, что после подачи стимула в течение некоторого времени возможен как приём реакций от них, так и подача новых стимулов. Поэтому с точки зрения тестирования все они относятся к классу систем с отложенными реакциями []. В случае систем А и Б это определяется наличием их собственных активных потоков, а в случае системы В - функционированием сетевого оборудования и системного ПО. В последнем случае у теста может отсутствовать возможность определить, по чьей вине реакция была искажена, потеряна или задержана - системы В или промежуточной сети. Поэтому разумно включить в рамки целевой системы также и сеть и тестировать именно совместное поведение системы В и сети - вместе они составляют некую систему с отложенными реакциями В', правильность работы которой и проверяется.Если же среда допускает возможность сделать такую проверку (например, взаимодействие тестовой и целевой систем происходит по протоколу TCP, который маскирует все эти искажения), то система В может быть отнесена к более простому классу, позволяющему применять более простые модели и инструменты тестирования. В ходе исследований выделены следующие различающиеся с точки зрения тестирования "черного ящика" конфигурации потоков управления.
Литература
| 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. Целевая система считается относящейся к данному классу, если она удовлетворяет следующим требованиям:Полуконтролируемое тестирование
Пример: Аналогично случаю 3, но целевая система регулярно выполняет по расписанию какую-то работу, влияющую на ее состояние. В качестве такого дополнительного источника воздействий на целевую систему при этом выступает таймер, неподконтрольный тестовой среде. Если в данном примере тестовая среда имеет возможность изолировать таймер и создавать стимулы от него целевой системе тогда и только тогда, когда это необходимо для целей тестирования, то такую целевую систему следует отнести к классу 3. Из-за наличия неконтролируемых источников воздействий на целевую систему данный случай не может быть сведен к случаям 3, а тем более случаям 1 и 2, поскольку в рассмотренных выше случаях предполагается полная подконтрольность всех стимулов, получаемых целевой системой. При этом для целей тестирования неважно, сколько активных потоков реально существует и откуда поступают воздействия на целевую систему: от внешних активных потоков или от других объектов, существующих на аналогичных условиях под управлением единого потока управления. В любом случае, когда возможна неконтролируемая тестом активность целевой системы, сложность модели целевой системы времени тестирования будет аналогична сложности модели, используемой в случае 7, который обсуждается ниже. С точки зрения особенностей реализации тестовой системы, она аналогична случаю 3.Именно из доступных ограничений такого рода и нужно исходить при моделировании. Мы можем предложить только некоторые общие рекомендации по уменьшению степени недетерминизма модели, позволяющие свести систему такого рода к одному из более простых случаев.
Система с отложенными реакциями
Пример: 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.Тестирование софта - статьи
Генерируемый SeC-код
В первую очередь необходимо чётко определить, как должен выглядеть генерируемый код, что именно и как именно он будет проверять. За основу, разумеется, были взяты созданные к этому времени наработки, написанные вручную. Весь однотипный код удобно заменить набором макросов (они поддерживаются в языке SeC, т. к. он включает в себя все возможности языка C). Это сделало бы исходный текст более наглядным и более простым для написания и отладки. В стандарте LSB присутствуют несколько разных типов требований на поведение функций при возникновении ошибочной ситуации. Возможные варианты поведения функции в случае ошибки могут быть описаны следующим образом:Именно поэтому в первую очередь должен отработать блок проверки ошибочных ситуаций, и только если ошибка не была возвращена (и не ожидалась!), управление передаётся дальше, на код проверки основных требований. Для удобства проверка кодов ошибок оформлена в виде блока, ограниченного операторными скобками 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):
Например, с префикса POSIX_PTHREAD_SETSPECIFIC начинаются соответствующие конфигурационные константы, такие как:
Конфигурационные константы.
Стандарт 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.Методика автоматизированной проверки
К. А. Власов, А. С. Смачёв, Труды Института системного программирования РАНОформление непроверяемых требований
В силу различных причин проверка некоторых требований может быть затруднена или даже невозможна. Для таких ситуаций были введены два дополнительных макроса.Пересечение требований
Может оказаться, что один и тот же код ошибки встречается в нескольких требованиях. Хорошо, если эти требования лишь дополняют друг друга. Но может сложиться ситуация, когда эти требования противоречат друг другу, а именно, когда одно из них требует, чтобы функция вернула код ошибочного завершения, а другое - утверждает, что в этой ситуации данный код ошибки не может быть возвращён. Такая ситуация может и не встретиться в ходе выполнения тестов, но подобное противоречие условий в любом случае является ошибкой в стандарте. Другая ситуация: под одно и то же условие попадают разные коды ошибок, при этом стандарт для обоих кодов требует обязательного возврата этого кода. В этом случае при выполнении условий появления ошибки тесты зафиксируют некорректность в работе тестируемой системы, независимо от того, какой из этих кодов ошибки был возвращён функцией, так как проверка другого кода зафиксирует некорректность поведения. В такой ситуации обычно из текста стандарта понятно, какой код ошибки должен возвращаться в каждом конкретном случае, и, следовательно, условия возникновения ошибок должны быть дополнены так, чтобы не выполняться одновременно ни в каких ситуациях. Если же из контекста неясно, какой код ошибки должен быть возвращён, налицо явное противоречие в стандарте. Таким образом, пересечение требований является серьёзной проблемой, и разработчик тестов должен обращать на них особое внимание.Постановка задачи
При написании тестов для пакета OLVER используется система CTesK []. Она включает в себя язык SeC [] - спецификационное расширение языка C, а также библиотеки для работы с различными типами данных, такими как массив, список, строка и т. д. Добавление новых тестируемых функций в тестовый набор обычно выглядит следующим образом. Сначала программист читает текст стандарта и размечает его, выделяя атомарные требования при помощи конструкций языка HTML и присваивая им уникальные идентификаторы. Затем при открытии в браузере веб-страницы с текстом стандарта автоматически запускается скрипт, написанный на языке JavaScript, который анализирует список отмеченных требований, строит по нему шаблонный код на языке SeC и отображает его на странице. Теперь программисту достаточно просто скопировать этот код в SEC-файлы проекта, и большая часть рутинной работы оказывается выполненной. Дальнейшая работа - это, в основном, создание тестовых сценариев и написание непосредственно кода проверки для всех атомарных требований, т. е. творческая работа, которая практически не поддаётся автоматизации. В процессе работы над тестовым набором выяснилось, что проверки кодов ошибок, возвращаемых различными тестируемыми функциями, имеют много общего, в результате чего существенная часть времени тратится на одну и ту же работу по организации взаимодействия проверок для разных кодов ошибок друг с другом и с проверками других требований. Поэтому было решено, насколько это возможно, попытаться автоматизировать и эту задачу. Наиболее удобный путь для такого рода автоматизации - это модификация существующего скрипта, чтобы он генерировал шаблонный код ещё и для проверки ошибок. Тем самым будет сохранена общая методика работы по разметке стандарта.Проверка
В макросе ERROR_BEGIN в первую очередь проверяется, что если функция завершилась с ошибкой, то код ошибки не равен EOK (коду, обозначающему отсутствие ошибки). Обычно признаком ошибки является сам факт отличия кода от EOK, и эта проверка превращается в тавтологию. Но бывают и другие случаи, когда, например, признаком ошибки является возвращаемое значение функции, равное -1, и тогда эта проверка необходима. Проверка кодов ошибок не ограничивается случаем, когда функция завершилась с ошибкой. Условие типа SHALL должно обеспечивать также проверку в обратную сторону: если условия вызова функции ошибочны, то функция обязана вернуть ошибку, и если этого не произошло, поведение считается некорректным. Заметим, что независимо от того, успешно ли отработала функция, необходимо проверять все требования к кодам возврата, поскольку выполнение одного требования ещё не означает, что другие требования не нарушены. Таким образом, решение о правильной работе функции должно приниматься только в макросе ERROR_END, не раньше. На первый взгляд, это соображение очевидно, но именно поэтому его так легко упустить из виду. Для каждого требования имеется четыре базовых случая:При ненулевом значении этой константы поведение функции не противоречит стандарту. Случай третий, когда ошибка ожидалась, но не произошла. Если это условие типа 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 для осуществления своих действий ищет сочетания сущностей абстрактного представления программы, которые удовлетворяют некоторым шаблонам (например, наличие в программе циклов, наличие в теле цикла конструкций с определенными свойствами, наличие в процедуре общих подвыражений, наличие между инструкциями зависимости данных некоторого вида и пр.). При этом могут учитываться сущности лишь части терминов. Для построения модели данных рассматриваются только те термины, которые именуют сущности, задействованные хотя бы в одном шаблоне. Итак, в результате анализа алгоритма выделяются термины и шаблоны, используемые в алгоритме. Далее на основании этой информации описывается множество модельных строительных блоков:
Методы верификации
Верификация встроенных возможностей языка основывается на методе OTK (см. предыдущий подраздел). Целевые конструкции моделируются на формальном языке TreeDL, из формальной модели генерируются тестовые программы. Оракул строится на основе сравнения трассы исполнения тестовой программы, откомпилированной тестируемым компилятором, с трассой выполнения программы, откомпилированной на эталонном компиляторе. Далее мы будем рассматривать только вопросы тестирования библиотек, предоставляющих поддержку времени исполнения. Верификация библиотек времени исполнения основывается на методе функционального тестирования с использованием технологии автоматизированного тестирования UniTESK.[]. Рассмотрим основные особенности UniTesK в приложении к тестированию программных интерфейсов:Моделирование
Для формального задания модели целевого языка используется представление грамматики языка в форме 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.
Систематический подход к верификации функций компилятора
Функции компилятора тесно связаны, поэтому при верификации компилятора необходимо проверять корректность каждой из них. В предлагаемом подходе задача верификации компилятора декомпозируется на основе декомпозиции функциональности компилятора. Мы выделяем следующие задачи:Во всех задачах верификации методы верификации строятся по единой схеме (рис. 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) как сгенерированному коду, так и окружению, и, в зависимости от поставщика, различные дополнительные возможности. Задачу верификации подсистемы поддержки исполнения можно разделить на следующие подзадачи:Верификация синтаксического анализатора
Синтаксический анализ входного текста является частью функциональности любого транслятора. Парсером мы называем булевскую функцию, заданную на множестве последовательностей токенов и принимающую значение "истина", если последовательность является предложением данного формального языка, и "ложь" - в противном случае. Задачу верификации парсера можно разделить на следующие подзадачи:Языки высокого уровня являются основным
Языки высокого уровня являются основным средством разработки программных систем. Спецификация языка высокого уровня задаёт класс текстов, принадлежащих этому языку, и определяет семантику исполнения программ, написанных на этом языке. Задача перевода текстов с языка высокого уровня в представление, выполнимое на вычислительной системе, решается комплексами программ, которые по традиции называют компиляторами. Ошибки в компиляторах приводят к тому, что выполнение результирующих исполнимых модулей отличается от поведения, определяемого спецификацией языка. Дефекты исполнимых модулей, вызванные ошибками в компиляторе, очень сложно выявлять и исправлять, и в целом корректность исполнимых модулей, построенных некорректным компилятором, вызывает сомнения. Корректность компилятора является необходимым требованием корректной и надёжной работы программного обеспечения, разработанного на соответствующем языке высокого уровня. Тем самым, задачу верификации компилятора можно считать одним из важнейших средств обеспечения надёжности программного обеспечения. Основной источник трудностей при верификации компилятора заключается в том, что компилятор принимает на вход данные со сложной структурой, большим количеством внутренних связей и обладающие очень сложной семантикой времени исполнения. Для снижения сложности задачи верификации компилятора мы предлагаем декомпозировать её на отдельные подзадачи, которые в совокупности покрывают всю функциональность компилятора . Функциональность, типичную для большинства компиляторов, можно условно декомпозировать на следующие задачи:В рамках данной статьи мы будем пользоваться термином "подсистема поддержки исполнения". Подсистема поддержки исполнения тесно связана с компилятором. Например, каждый компилятор Си и Си++ поставляется со своим набором стандартных библиотек, причём библиотеки одного компилятора нельзя использовать для построения исполнимых файлов другим компилятором. Даже в тех случаях, когда промежуточный язык и стандартные библиотеки стандартизированы, существуют тесные зависимости между компилятором и средой исполнения. Например, для исполнения байт-кода 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) и, соответственно, меньшие — все слагаемые в формуле (*) имеют максимальные возможные значения. Осталось выяснить два момента.какому допущению относительно свойств тестируемой системы, соответствует выбранное понимание «наиболее разнообразного» поведения слова? Для какого рода систем этот подход дает действительно оптимальное покрытие различных возможных ситуаций?
Продолжение слов де Бройна
Нас, однако, интересует еще вопрос возможности продолжения слова де Бройна шага 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" означает, что соединения с сервером нет). Соответственно, интерпретировать эту диаграмму следует так:Кроме этого с каждым объектом на диаграмме (модель, диаграммы, управляющие элементы, переходы) связаны требования (спецификации) к данному объекту. Эти требования размещаются в поле 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 | |
| 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 |
Кроме самых распространённых, имеется много других инструментов для автоматического прогона тестов, созданных вручную. Но эти инструменты не предоставляют возможности автоматической генерации тестов (исключая процесс записи теста).
В основе всех этих инструментов лежат средства формального описания тестов (разного рода скриптовые языки). Тесты пишутся вручную. Данный подход имеет существенные недостатки. Один из них состоит, как и при полностью ручном подходе, в недостаточной систематичности. Другим серьёзным недостатком является трудность при организации регрессионного тестирования. При изменении тестируемой системы возникает необходимость переписывания (редактирования) большого количества тестов. То есть, например, система изменилась в одном месте – а тесты приходится модифицировать в несравненно большем количестве мест. Причём если некоторые такие проблемы можно обойти созданием набора вызываемых функций, которые также можно изменять в одном месте, то другие (например, изменение в логике программы) так просто не решаются. Кроме того, остаётся проблема дублирования одних и тех же действий (написание одного и того же тестового кода) при совершении одного и того же события из разных состояний. Такой метод сменил ручной прогон тестов на ручное написание самих тестов для дальнейшего автоматического прогона, но само их написание по-прежнему остаётся трудоёмким занятием.
Доступные инструменты для автоматической генерации тестов для GUI нам не известны. Поэтому мы в первую очередь сконцентрировали свои усилия именно на автоматизации генерации тестов для GUI по формальной спецификации. Причем при разработке средств формальной спецификации GUI мы старались максимально использовать существующие, привычные для пользователей методы и инструменты.
Описание подхода
Правильность функционирования системы определяется соответствием реального поведения системы эталонному поведению. Для того чтобы качественно определять это соответствие, нужно уметь формализовать эталонное поведение системы. Распространённым способом описания поведения системы является описание с помощью диаграмм UML (Unified Modeling Language) [4]. Стандарт UML предлагает использование трех видов диаграмм для описания графического интерфейса системы:Широко распространено использование UML/State Chart diagram для спецификации поведения системы, и такой подход очень удобен с точки зрения генерации тестов. Но составление спецификации поведения современных систем с помощью конечных автоматов есть очень трудоёмкое занятие, так как число состояний системы очень велико. С ростом функциональности системы спецификация становится всё менее и менее наглядной.
Перечисленные недостатки этих подходов к описанию поведения системы преодолеваются с помощью диаграмм действий (Activity). С одной стороны нотация UML/Activity diagram является более строгой, чем у сценариев использования, а с другой стороны предоставляет более широкие возможности по сравнению с диаграммами конечных автоматов. Следующие причины определяют пригодность диаграмм действий для моделирования графического интерфейса:
Действительно, модальные диалоги (те, которые перехватывают управление) удобно моделировать с помощью отдельной диаграммы состояний (для верхней диаграммы это поддиаграммы), инкапсулируя тем самым их функциональность.
Итак, перейдём к детальному рассмотрению предлагаемого подхода.
Для создания прототипа работающей версии данного подхода использовался инструмент Rational Rose [6]. Он использовался в первую очередь для спецификации графического интерфейса пользователя при помощи диаграмм действий UML.
Для прогона сгенерированных по диаграмме состояний тестов использовался инструмент Rational Robot [6]. Из возможностей инструмента в работе мы использовали следующие:
1. Возможность выполнять тестовые воздействия, соответствующие переходам между состояниями в спецификации.
2. Возможность проверять соответствие свойств объектов реальной системы и эталонных свойств, содержащихся в спецификации. Из тех возможностей, которые доступны с помощью этого инструмента, используется проверка следующих свойств объектов:
Рассмотрим ее компоненты более подробно. Поставленная задача
Разработка и реализация методов автоматизированной генерации набора функциональных тестов для 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 переменных с начальными значениями, приведёнными ниже:
| 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 прекращает его выполнение, вынося соответствующий вердикт – тест не прошёл.Развитие подхода
Предложенный подход генерации набора тестов, на наш взгляд, дальше можно развивать в следующих направлениях:Список литературы
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-диаграммы моделей
Рис. 5. Модель структуры БД (без ограничений)
Рис. 6. Модель ссылочных ограничений в SQL
| 1(к тексту) | Этот транслятор разрабатывался в ИСП РАН в рамках проекта по созданию инструментальной поддержки для автоматической генерации тестовых данных сложной структуры []. |
В качестве примера применения предложенного
В качестве примера применения предложенного подхода рассмотрим тестирование генератора кода в трансляторе , который отображает описание схемы БД на языке SQL в описание структуры таблиц на языке RelaxNG [] и в описание ограничений, наложенных на эту структуру, на языке 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 |
Как видно из этой таблицы, в модели, описывающей ограничения вида "CHECK", около 60% кода описания модели и около 40% кода компонентов генератора тестов переиспользуется из других моделей.
| Ограничения вида "CHECK" | 33 | 3816 / 276 | 37 | 2 | 60 / 1717 | 8 / 271 |
| Структура БД | 151 | 826 | 5471 / 140 |
| Ссылочные ограничения | 38 | 353 | 9297 / 235 |
| Ограничения вида "CHECK" | 46 | 915 | 19891 / 455 |
Тестирование трансляторов: проблема построения оракула для генератора кода
Гингина В.В., Зеленов С.В., Зеленова С.А.Труды Института системного программирования РАН
к тому, что во все
Развитие компьютерной индустрии и информационных технологий привело к тому, что во все большем числе областей бизнеса обработка документов производится автоматически с помощью компьютеров. Чтобы быть пригодными для компьютерной обработки, данные оформляются в виде формального текста, т.е. текста на некотором формальном языке. Транслятор - это программа, осуществляющая обработку формального текста и перевод его в некоторое другое представление. Примерами трансляторов являются компиляторы и интерпретаторы языков программирования, XML-процессоры, браузеры HTML-страниц, системы поддержки специфицирования и моделирования, текстовые процессоры и издательские системы, серверы запросов СУБД и проч. Трансляторы используются при разработке ПО, при организации работы сетевых приложений, при создании разного рода распределенных информационных систем и т.д. Широкое распространение трансляторов, их повсеместное как прямое, так и косвенное использование в различных областях компьютерной индустрии, включая критические, обуславливает высокие требования к качеству трансляторов. Существует много разных подходов к достижению качества программного обеспечения (ПО). Одним из подходов является проведение аналитического доказательства корректности ПО. Вопросам теоретического обоснования корректности поведения компиляторов на основе использования различных логических исчислений посвящены, например, работы [], [], []. Однако, при разработке программных систем высокой сложности, какими являются реальные трансляторы, более практичным подходом является тестирование []. При тестировании встают две основные проблемы:Поэтому очень актуальна проблема автоматизации как процесса создания тестовых данных, так и процедуры вынесения вердикта. Наиболее многообещающим в плане автоматизации подходом является тестирование на основе формальных спецификаций и моделей []. В трансляторах традиционно выделяют следующие функциональные блоки: front-end, осуществляющий синтаксический разбор и семантический анализ входного текста и построение его внутреннего представления, и back-end, осуществляющий дополнительный анализ входных данных, возможно, некоторые оптимизирующие преобразования, а также собственно генерацию выходного представления (см. Рис. 1).
Рис. 1.Функциональные блоки транслятора
Тестирование сложных программных систем нужно проводить как на системном уровне, так и на уровне отдельных модулей и блоков и на уровне проверки отдельных аспектов функциональности. В настоящее время известно большое количество исследовательских работ, посвященных тестированию отдельных функциональных блоков трансляторов (см. раздел 5). Можно утверждать, что для блоков, входящих во front-end транслятора, если рассматривать эти блоки как булевские функции, существуют приемлемые подходы к автоматизации их тестирования - систематической генерации тестов и анализу результатов работы тестируемой системы (см. [], [], [], [], [], []). В вопросе проверки качества работы back-end'а можно утверждать, что в настоящее время существуют удовлетворительные подходы к решению проблемы автоматического построения тестовых данных (см. [], [], [], []). Что же касается проблемы оракула при тестировании back-end'а, то существующие практически применимые подходы основаны на идее анализа наблюдаемого поведения выполнения результата трансляции, и таким образом применимы лишь к компиляторам и интерпретаторам (см. [], [], [], [], [], [], []). Однако существует большой класс трансляторов, которые продуцируют "неисполнимые" тексты, а значит, к ним не применимы имеющиеся подходы к решению проблемы оракула. В настоящей статье предлагается подход к автоматическому построению тестов для трансляторов, обеспечивающий решение проблемы оракула для генератора выходного представления. Статья состоит из введения, пяти разделов и списка литературы.В первом разделе приводятся предварительные сведения, касающиеся подхода к генерации тестов для оптимизаторов на основе моделей. Во втором разделе дается описание предлагаемого в настоящей статье подхода. В третьем разделе рассматривается пример применения предлагаемого подхода на практике. В четвертом разделе приводится обзор близких работ. В заключение работы обсуждаются достоинства и недостатки, а также направления дальнейшего развития предлагаемого подхода.
В статье предложен общий подход
В статье предложен общий подход к построению автоматического оракула для тестирования генераторов кода в трансляторах текстов на формальных языках, а также предложена инструментальная поддержка для практического использования этого подхода. Архитектура оракула основана на идее одновременной генерации как тестовых входных данных для транслятора, так и соответствующей эталонной системы показателей, характеризующих ключевые свойства выходных данных транслятора. Несмотря на то, что в ходе разработки необходимых компонентов генератора тестов фактически требуется разработка своего рода "эталонного генератора кода", количество требуемых для этого ресурсов, как правило, существенно меньше того количества ресурсов, которое требуется на разработку реального генератора кода в тестируемом трансляторе. Экономия достигается за счет следующих факторов:Тестирование софта - статьи
Алгоритм построения тестового набора
Разработанный алгоритм сначала проверяет полученный на вход граф состояний документа согласно следующим ограничениям целостности:Все построенные тестовые ситуации считаются при дальнейшей оптимизации тестового набора равноценными. Далее из построенного множества путей строится тестовый набор минимальной сложности, покрывающий все возможные тестовые ситуации, с помощью нижеописанного эвристического алгоритма. В записи алгоритма используются следующие условные обозначения:
Если проверяемый тест признан хуже или ни одна проверка не выносит вердикт, что один тест лучше другого, то продолжаем считать лучшим тот же тест, что и раньше.
Генерация готового к исполнению тестового набора
Основным результатом работы инструмента является набор тестов, в котором каждый тест представляет собой пошаговую инструкцию для тестировщика. Каждая такая инструкция представляет собой последовательный список, состоящий из воздействий на документ и проверок, которые надо выполнить. Инструмент генерирует проверки следующих видов:В частности, сюда относятся воздействия, которые выполняются не пользователями системы: например, тайм-аут можно выполнить, войдя в систему под любой ролью или даже вообще не входя в неё.
Генерация оптимизированных для
, ,Труды Института системного программирования РАН
Критерии тестового покрытия
Элементарными событиями будем называть события следующих видов:События такого вида мы будем называть тестовыми ситуациями. При этом первый критерий покрытия порождает все возможные тестовые ситуации вида «B после A», где в качестве элементарного события A выступает начальное состояние графа, а в качестве элементарного события B ? все определённые в графе состояний документа элементарные события. Второй критерий покрытия порождает все возможные тестовые ситуации видов «B после A» и «B не после A», где в качестве A выступают существенные в истории элементарные события, а в качестве B ? определённые в графе состояний документа элементарные события. Элементы третьего критерия покрытия переводятся в тестовые ситуации очевидным образом. При разработке тестов документооборота для оценки полноты тестового покрытия используется составной критерий достижения всех тестовых ситуаций, построенных для модели ЖЦ документа по вышеописанным правилам из трех вышеописанных критериев. В дальнейшем под критерием покрытия имеется ввиду только этот критерий покрытия.
Модель приложения
Документ в системе документооборота может находиться в различных состояниях. Переход между состояниями осуществляется при помощи воздействий на документ через ГИП. В системе документооборота определены роли пользователей, при этом воздействия делятся на 3 класса в зависимости от того, кем они могут осуществляться:Оптимизация и балансировка тестового набора
Далее производится фильтрация и сортировка построенного набора тестов R; при этом используется упорядоченность тестового набора по времени добавления в него тестов. Для фильтрации вычисляется полный набор тестовых ситуаций, покрываемых каждым тестом из построенного тестового набора, и для каждого теста проверяется, добавляет ли он какое-либо тестовое покрытие к суммарному покрытию тестов, добавленных в набор после него (напоминаем, что при добавлении «поздних» тестов в набор учитывалось только то покрытие, которое они добавляли к уже имеющемуся на момент добавления); если все тестовые ситуации, покрываемые тестом, покрываются объединением более поздних тестов, то он удаляется из построенного набора. Отфильтрованный таким образом набор тестов сортируется по длине: самые короткие (по количеству тестовых воздействий) тесты ставятся в начало набора, а самые длинные ? в его конец; это позволяет в дальнейшем сбалансировать количество проверок, выполняемых в различных тестах набора.Оптимизация тестового набора
Нами выделены следующие критерии оптимизации, которые необходимо учитывать при создании тестов для ручного тестирования, в порядке понижения их приоритета:Пример применения
Инструмент реализован на языке 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) — компонентов тестовой системы, осуществляющих преобразование потока входных транзакций во входные сигналы тестируемой модели и наоборот преобразование выходных сигналов тестируемой модели в поток выходных транзакций. Примерами транзакторов являются:Основными компонентами этого уровня являются:
Рисунок 1. Слои тестовой системы AVM
Архитектура тестовой системы OVM
В рамках технологии OVM тестовая система собирается из специальных блоков, называемых OVC (Open Verification Component). Каждый блок инкапсулирует тестовое окружение для отдельного модуля аппаратуры и состоит из полного набора компонентов, обеспечивающих генерацию стимулов, проверку правильности поведения и оценку полноты тестирования. Если тестируемая модель аппаратного обеспечения получается путем интеграции нескольких модулей, тестовая система для нее строится путем объединения соответствующих OVC-блоков и построения над ними специального синхронизирующего контроллера (virtual sequencer). Структура OVC-блоков практически полностью соответствует архитектуре тестовой системы AVM. Пример блока тестовой системы приведен на Рис. 2.
Рисунок 2. Блок тестовой системы
Архитектура тестовой системы UniTESK
Тестовая система UniTESK состоит из следующих основных компонентов (см. Рис. 3):
Рисунок 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 является академической разработкой, которая, тем не менее, основана на реальном опыте тестирования программного и аппаратного обеспечения.Основные понятия
Под моделями аппаратного обеспечения в данной статье понимаются программные модели цифровых устройств, описанные на специальных языках. Такие языки можно разбить на две разновидности:Основные принципы 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 используются следующие базовые принципы:Сравнение технологий разработки тестов
Сравним рассмотренные ранее технологии 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() строит маршрут из текущей вершины в вершину, в которой есть еще не пройденные дуги.Подходы к реализации этой операции могут быть различными. Алгоритмы, основанные на обходе остова графа, выбирают самую дальнюю от корня (поиск в глубину) или самую ближнюю к корню (поиск в ширину) незавершенную вершину, достижимую из текущей []. Другим подходом (жадный алгоритм) является выбор ближайшей достижимой незавершенной вершины, но это требует больших затрат памяти. Утверждение. Для графов с уточняемыми вершинами, являющихся:
Архитектура тестовой системы UniTESK
Архитектура тестовой системы UniTESK была разработана на основе многолетнего опыта тестирования промышленного программного обеспечения из разных предметных областей и разной степени сложности. Учет этого опыта позволил создать гибкую архитектуру, основанную на следующем разделении задачи тестирования на подзадачи.
Рисунок 1.Архитектура тестовой системы UniTESK.
Оракул оценивает правильность поведения целевой системы в ответ на единичное тестовое воздействие. Он автоматически генерируется на основе формальных спецификаций, описывающих требования к целевой системе в виде пред- и постусловий интерфейсных операций и инвариантов типов данных. Медиатор связывает абстрактные формальные спецификации, описывающие требования к целевой системе, с конкретной реализацией целевой системы. Медиатор преобразует единичное тестовое воздействие из спецификационного представления в реализационное, а полученную в ответ реакцию - из реализационного представления в спецификационное. Также медиатор синхронизирует состояние спецификации с состоянием целевой системы. Трасса теста отражает события, происходящие в процессе тестирования. На основе трассы можно автоматически генерировать различные отчеты, помогающие в анализе результатов тестирования.
Генерация тестов на основе неопределенных моделей
Теперь рассмотрим естественное обобщение технологии тестирования UniTESK для генерации тестовых последовательностей на основе неопределенных обобщенных моделей. В случае, когда состояние модели неопределено, тестовые воздействия можно разделить на три группы:Очевидно, что требование детерминизма в не полностью определенных обобщенных состояниях является слишком жестким: неопределенные состояния открыты для уточнения, а возможных уточнений, вообще говоря, может быть несколько. Введем некоторые вспомогательные понятия. Определение. Уточнение состояния целевой системы называется существенным, если оно вызывает уточнение обобщенного состояния. В противном случае, уточнение называется несущественным. После существенного уточнения состояния целевой системы тестовая система переходит на другой информационный уровень. В предположении монотонности уточнений состояния целевой системы, тестовая система больше не вернется в обобщенное состояние, предшествующее существенному уточнению. Определение. Модификация состояния целевой системы называется существенной, если она вызывает изменение обобщенного состояния. В противном случае, модификация называется несущественной. Недетерминизм в существенных модификациях часто означает, что поведение целевой системы зависит от еще не определенной части состояния и, поскольку проверить правильность поведения в этом случае все равно нельзя, тестовая система должна исключать такие тестовые воздействия пока не накопит информацию о состоянии целевой системы, достаточную чтобы вынести вердикт о правильности или ошибочности поведения целевой системы. Последнее требование можно ослабить. Если в процессе тестирования тестовая система перешла в обобщенное состояние, которое является уточнением одного из ранее пройденных обобщенных состояний, это означает, что тестовая система получила дополнительную информацию о состоянии целевой системы, следовательно, тестовую последовательность до настоящего момента можно рассматривать как носящую вспомогательный, уточняющий характер. Вместо традиционного для технологии тестирования UniTESK требования сильной связности графа состояний обобщенной конечно-автоматной модели, естественно потребовать сильной связности для графа обобщенных состояний, расширенного дугами, ведущих из уточняющих обобщенных состояний в уточняемые.Такие дуги вполне естественны, поскольку находясь в некотором обобщенном состоянии, тестовая система в то же время находится и во всех уточняемых им обобщенных состояниях.
Графы с уточняемыми вершинами
В данном разделе вводится понятие графа с уточняющими вершинами, используемое для формального представления неопределенных обобщенных моделей. Вершины графа интерпретируются как обобщенные состояния целевой системы, дуги - как переходы между обобщенными состояниями, а раскраска дуг - как стимулы, инициирующие соответствующие переходы. Определение. Ориентированным графом или просто графом G называется совокупность трех объектов (GV, GX, GE): GV - множество вершин, GX - множество раскрасок, GE
и являются полными, то есть в них для любой не полностью определенной вершины существует хотя бы одна уточняющая ее полностью определенная вершина. Определение. Подграф G' = (GV', GX', GE') графа с уточняемыми вершинами G = (GV, GX, GE) называется полностью определенным, если все его вершины полностью определены, то есть GV' Подграф G' = (GV', GX', GE') графа с уточняемыми вершинами G = (GV, GX, GE) называется его информационной проекцией, если:
и содержащий все дуги максимального полностью определенного подграфа 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,
)}, очевидно, является уточняющим. Ему соответствуют три уровня неопределенности:
,
)};
), (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 |
интерпретируется как промежуточное между ложью и истиной. В нашем случае неопределенное значение следует интерпретировать иначе - как неизвестное значение, которое может быть как истиной, так ложью, но какое оно именно - неизвестно. Подобным образом неопределенное значение интерпретируется в трехзначной логике Клини (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 по отношению Рассмотрим пример уточняемого типа ST, представляющего нечеткое множество значений типа T. Нечеткое множество s определяется трехзначной функцией принадлежности fs: T → {true, false,
}. fs(x) интерпретируется следующим образом: если fs(x) = true, то x принадлежит множеству s, если fs(x) = false, то x не принадлежит множеству s, если fs(x) =
, то неизвестно, принадлежит x множеству s или нет. Отношение уточнения естественно определить таким образом: s1 Неполнота информации в тестировании
При разработке тестов часто возникает ситуация, когда нет прямого доступа к внутреннему состоянию целевой системы. Такое тестирование называется тестированием со скрытым состоянием (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 это означает, что функция вычисления постсостояния спецификации в некоторых ситуациях оказывается недетерминированной. Проиллюстрируем понятие сценарной полноты и детерминизма требований на следующем простом примере. Пример. Целевая система является стеком.Интерфейс целевой системы состоит из следующих функций:
и широко распространяются методы, оперирующие
В последнее время в науке и технике интенсивно развиваются и широко распространяются методы, оперирующие с неполной или нечеткой информацией []. Примерами теорий, в рамках которых разрабатываются подобные методы, являются математическая статистика, теория нечетких множеств, теория возможностей и многие другие. Методы анализа и обработки неполной информации хорошо зарекомендовали себя в искусственном интеллекте, теории баз данных, системном анализе и исследовании операций. Сегодня их активно используют для моделирования сложных физических, экономических и социальных явлений []. Таким образом, работа с неопределенной и нечеткой информацией постепенно становится уделом точных наук. Программной инженерии, претендующей на точность и методичность инженерной дисциплины, также приходится иметь дело с неполной информацией. В проектах по разработке программного обеспечения неопределенность возникает на самых ранних фазах. Размытый и туманный характер носят требования к программной системе, которые приходится многократно уточнять, дополнять и согласовывать. После доработок требования используются на следующей фазе, на которой, как правило, имеют дело с более низким уровнем абстракции, поэтому требования вновь кажутся неполными, их снова приходится дорабатывать. Таким образом, на каждой фазе проекта исходная информация оказывается не полностью определенной. В работе рассматривается лишь один из этапов разработки программного обеспечения - тестирование. Обычно на этапе тестирования располагают более-менее завершенным продуктом и достаточно проработанными требованиями. Казалось бы, что на этом этапе в требованиях не должно быть никаких неопределенностей, но, как показывает практика, это не так. Использование требований для целей тестирования в очередной раз выявляет их недостатки. Требования после многочисленных доработок вновь оказываются неполными. С другой стороны, в процессе тестирования возможна ситуация, когда состояние целевой системы не полностью определено. Например, такая ситуация может возникнуть, когда состояние целевой системы скрыто, а начальное состояние в требованиях не определено.Неполнота информации о состоянии целевой системы может приводить к невозможности адекватной оценки правильности поведения целевой системы. Перед тем, как оценивать поведение, необходимо получить представление о состоянии целевой системы путем подачи тестовых воздействий и анализа реакций на них. Статья выполнена в контексте тестирования на основе моделей (MBT, model based testing), более точно, в контексте технологии тестирования UniTESK [-], хотя рассматриваемые в ней вопросы актуальны не только для этой технологии. Модель представляет собой некоторое достаточно абстрактное отображение структуры и поведения целевой системы, созданное на базе требований []. В процессе тестирования состояние модели можно интерпретировать, как располагаемую информация о состоянии целевой системы, сформулированную в терминах требований. В работе предлагается подход к разработке функциональных спецификаций и генерации функциональных тестов, позволяющий работать с неполными требованиями к целевой системе и неполной информацией о ее состоянии. Подход основан на использовании неопределенных значений для моделирования состояния целевой системы, а также трехзначной логики Клини (Kleene) [] для работы с требованиями и описания свойств системы. Результаты работы получены в ходе выполнения проекта по разработке открытого тестового набора OLVER для операционной системы Linux []. Статья построена следующим образом. Во втором, следующем за введением, разделе делается краткий обзор технологии тестирования UniTESK. В третьем разделе рассматриваются вопросы, связанные с неполнотой функциональных требований и неполнотой информации о состоянии целевой системы в процессе тестирования. В четвертом разделе описывается подход к спецификации систем на основе неопределенных значений, уточняемых типов и трехзначной логики Клини. В пятом разделе рассматриваются вопросы построения тестовых последовательностей по неопределенным обобщенным моделям. В шестом разделе предлагается простое расширение технологии тестирования UniTESK на примере инструмента разработки тестов CTesK [].В заключении делается краткое резюме работы.
В работе были рассмотрены вопросы
В работе были рассмотрены вопросы функционального тестирования программных систем в условиях неполной информации. На базе технологии тестирования UniTESK предложен подход к разработке функциональных спецификаций и генерации функциональных тестов, основанный на использовании неопределенных значений для моделирования состояния целевой системы, а также трехзначной логики Клини для работы с неполными требованиями и описания свойств системы. Идеи предложенного подхода нашли свое применение в проекте по разработке открытого тестого набора OLVER для операционной системы Linux.Тестирование софта - статьи
Абстрактное состояние
Абстрактное состояние – это множество структур данных, которые моделируют внутреннее состояние целевой системы. Все действия, которые выполняет целевая система, моделируются в терминах абстрактного состояния. Документация на IPv6 не содержит требований к внутреннему устройству реализации IPv6. Абстрактное состояние было спроектировано таким образом, чтобы предоставить удобные концептуальные структуры данных для спецификаций стимулов и реакций. Абстрактное состояние для MSR IPv6 включает следующие элементы:Функции 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
Neighbor Discovery
Neighbor Discovery (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 можно разделить на несколько фаз:Разбиение MSR IPvна подсистемы
MSR IPv6 отличает хорошая структуризация исходных текстов. Код, который реализует некоторую функцию IPv6, как правило сгруппирован в отдельные файлы, и может рассматриваться как отдельная подсистема MSR IPv6. На рисунке 1 представлено разбиение MSR IPv6 на подсистемы.
В средней части рисунка представлены подсистемы, которые реализуют базовые функции 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 проверяет следующее:Спецификации
Спецификации можно рассматривать как формальное представление требований к целевой системе. В наших спецификациях мы формализовали требования, взятые из стандартов IPv6. В основном, требования извлекались из , но использовались также ряд других RFC. Спецификации представляют собой формализованную запись требования, изложенных в RFC. Для того, чтобы сохранить прослеживаемость требований в тесте спецификаций расставлялись ссылки на соответствующие полуформальные требования из RFC. Благодаря этому по расхождениям между реализацией и моделью, выявленным при прогоне тестов, можно определить, какое требование из RFC нарушено в реализации.Спецификация фрагментации
Функция фрагментации формально относится к функции отправки пакета в сеть. Тем не менее, спецификация стимулов не описывает фрагментацию. Проверка правильности фрагментации возложена на спецификацию для исходящих пакетов. Данная спецификация проверяет, что из фрагментов можно собрать оригинальный большой пакет, собирает большой пакет и затем проверяет, что полученный пакет соответствует одному из пакетов, которые были созданы по запросу протокола верхнего уровня.Спецификация Neighbor Discovery
Подсистема Neighbor Discovery не содержит входных точек, доступных извне модуля MSR IPv6. ND обрабатывает входящие пакеты и сохраняет информацию о соседних узлах во внутренних таблицах. При отправке IPv6 пакета в сеть соответствующая подсистема IPv6 запрашивает у ND информацию об узле назначения пакета. В результате такого запроса ND может выслать один или более служебных пакетов. Спецификация для входящих пакетов распознает пакеты Neighbor Discovery и определяет, какая информация должна сохраняться в таблицах Neighbor Discovery. Спецификация для исходящих пакетов IPv6 проверяет следующее:Спецификация непроцедурных стимулов
Алгоритм обхода графа состояний автомата тестового сценария, реализованный в CTesK-lite, требует, чтобы воздействия на целевую систему оказывались только в стационарных состояниях. Протокол IPv6 не удовлетворяет указанному ограничению, так как некоторые внешние воздействия (входящие пакеты) могут оказываться в нестационарных состояниях. Например, в протоколе ICMPv6 Echo узел испускает пакет ICMPv6 Echo Request и переходит в нестационарное состояние, в котором ожидает входящего пакета ICMPv6 Echo Reply. Таким образом, внешнее воздействие – пакет ICMPv6 Echo Reply – необходимо оказать в нестационарном состоянии. Для того, чтобы обойти ограничение на стационарность начального состояния, непроцедурные стимулы были специфицированы как отложенные реакции на некоторые процедурные стимулы. Мы ввели стимул “Отправить пакет IPv6 в целевую систему”. Спецификация для данного стимула тривиальна. Медиатор для данного стимула отправляет пакет IPv6 с удаленного узла. Собственно входящий пакет рассматривается как отложенная реакция на данный стимул.Спецификация процедурных стимулов
Мы специфицировали два процедурных стимула:Тестовые сценарии
Тестовый сценарий в UniTesK определяет последовательность воздействий, которые оказываются на целевую систему. В UniTesK в качестве теоретической основы для построения тестового сценария выбрана модель конечных автоматов. Тестовый сценарий обладает собственным состоянием, которое, как правило, вычисляется на основе состояния целевой системы. В каждом состоянии задаются воздействия, которые в данном состоянии можно оказать на целевую систему. Тестовый сценарий в процессе работы обходит все состояния и в каждом состоянии оказывает все перечисленные воздействия.
На рисунке 2 изображен пример тестового сценария, в котором насчитывается три состояния (S1, S2 и S3) и семь воздействий (I1_1, I1_2, I2_1, I2_2, I3_1, I3_2, I3_3). При тестировании тестовый сценарий побывает в каждом состоянии и пройдет по каждой дуге. К сожалению, для большинства целевых систем невозможно получить описание тестового сценария из спецификаций полностью автоматически, без помощи человека. Можно указать следующие причины:
В ходе проекта были разработаны 15 тестовых сценариев. Приблизительно тестовые сценарии можно разделить на 4 группы:
Транспорт стимулов и реакций
В данном разделе описывается, как тестовая система оказывает воздействие на целевую систему (MSR IPv6) и собирает реакции целевой системы.В статье представлен опыт разработки
В статье представлен опыт разработки тестового набора для реализации протокола IPv6 на Windows 2000. Тестовый набор предназначался для проверки соответствия реализации протокола IPv6 спецификациям IPv6. Проект по разработке тестового набора проходил при поддержке исследовательского гранта Microsoft Research. Разработка тестового набора проводилась с использованием методологии тестирования UniTesK, которая разработана и развивается в Институте системного программирования РАН. В качестве объекта тестирования была выбрана MSR IPv6 – реализация IPv6, разработанная в Microsoft Research. Перед тестовым набором стояла задача проверить, насколько реализация протокола соответствует стандарту протокола. Кроме того, перед разработчиками тестового набора были поставлены следующие цели:Введение в 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.
Обходчик является библиотечным компонентом тестовой системы UniTESK и предназначен вместе с итератором тестовых воздействий для построения тестовой последовательности. В основе обходчика лежит алгоритм обхода графа состояний обобщенной конечно-автоматной модели целевой системы (конечного автомата, моделирующего целевую систему на некотором уровне абстракции). Обходчики, реализованные в библиотеках инструментов UniTESK, требуют, чтобы обобщенная конечно-автоматная модель целевой системы, была детерминированной и имела сильно-связный граф состояний. Итератор тестовых воздействий работает под управлением обходчика и предназначен для перебора в каждом достижимом состоянии конечного автомата допустимых тестовых воздействий. Итератор тестовых воздействий автоматически генерируется из тестового сценария, представляющего собой неявное описание обобщенной конечно-автоматной модели целевой системы. Тестовый оракул оценивает правильность поведения целевой системы в ответ на единичное тестовое воздействие. Он автоматически генерируется на основе формальных спецификаций, описывающих требования к целевой системе в виде пред- и постусловий интерфейсных операций и инвариантов типов данных. Медиатор связывает абстрактные формальные спецификации, описывающие требования к целевой системе, с конкретной реализацией целевой системы. Медиатор преобразует единичное тестовое воздействие из спецификационного представления в реализационное, а полученную в ответ реакцию - из реализационного представления в спецификационное. Также медиатор синхронизирует состояние спецификации с состоянием целевой системы. Трасса теста отражает события, происходящие в процессе тестирования. На основе трассы можно автоматически генерировать различные отчеты, помогающие анализировать результаты тестирования.
Инструмент разработки тестов CTESK
Инструмент CTESK [], который используется в описываемой работе, является реализацией концепции UniTESK для языка программирования C. Для разработки компонентов тестовой системы в нем используется язык SeC (specification extension of C), являющийся расширением ANSI C. Инструмент CTESK включает в себя транслятор из языка SeC в C, библиотеку поддержки тестовой системы, библиотеку спецификационных типов и генераторы отчетов. Компоненты тестовой системы UniTESK реализуются в инструменте CTESK с помощью специальных функций языка SeC, к которым относятся:Использование 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).
Рис. 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)Разработка спецификаций модуля
Основные требования к буферу трансляции адресов были получены в письменной форме от разработчиков модуля. В процессе формализации требования уточнялись в результате общения с разработчиками и чтения технической документации. Следует отметить, что все сформулированные разработчиками требования были легко представлены в форме пред- и постусловий. Проект продемонстрировал удобство и сравнительно небольшую трудоемкость разработки контрактных спецификаций для моделей аппаратного обеспечения. Спецификации были разработаны одним человеком за ≈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(τ) - конъюнкцию постусловий микроопераций, помеченных тактом τ, то есть Сравнение с существующими подходами
В данном разделе приводится сравнение предлагаемого подхода с существующими методами спецификации аппаратуры, поддерживаемыми современными языками верификации аппаратуры (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 тактов, тогда на каждом такте τПредставление многотактовой операции f в виде последовательности микроопераций (f1, …, fn) будем называть временнó й декомпозицией f. Теперь несколько слов о способах организации выполнения операций. В модулях с поочередным выполнением операций, как видно из названия, очередную операцию можно подавать на выполнение только после того, как полностью завершена предыдущая. В модулях с конвейерным выполнением операций операции можно подавать последовательно друг за другом, не дожидаясь завершения предыдущей операции. В модулях с параллельным выполнением операций несколько операций можно подавать одновременно. Модули с поочередным и конвейерным выполнением операций объединим общим термином - модули с последовательным выполнением операций, поскольку и в том, и в другом случае операции подаются на выполнение последовательно одна за другой. В дальнейшем будем считать, что модули организованы таким образом, что одновременно выполняемые операции не вступают в конфликты (hazards), то есть не влияют друг на друга. Если взаимное влияние все-таки возможно, требования должны описывать, как подавать операции на выполнение, чтобы избежать возникновения конфликтов.
Требования к модулям аппаратного обеспечения
В общем случае операции являются многотактными, то есть выполняются модулем за несколько тактов. Требования на такие операции бывают двух основных типов: требования на операцию в целом, которые не накладывают ограничений на то, на каком именно такте выполняется та или иная микрооперация, и требования на временнýю композицию операции, в которых фиксируется, на каких тактах выполняются конкретные микрооперации. Требования на операцию в целом допускают определенную свободу в реализации модуля. Не важно, на каком такте выполняется некоторая микрооперация, важно, чтобы после завершения всей операции результат этой микрооперации был доступен окружению. В процессе тестирования требования на операцию в целом проверяются после завершения операции. Требования на временнýю композицию операции являются более жесткими. В них указаны такты, на которых выполняются микрооперации. Обычно при тестировании имеет смысл проверять не то, что микрооперация была выполнена на определенном такте τ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 для спецификации и тестирования моделей аппаратного обеспечения. Опыт показывает, что некоторые шаги разработки тестов могут быть полностью или частично автоматизированы. Детальное исследование этого вопроса и разработка инструментальной поддержки для автоматизации шагов разработки тестов является основным направлением дальнейшей работы.Тестирование софта - статьи
Области применимости подхода
Предложенный подход применялся для тестирования компиляторов императивных языков программирования. В составе таких компиляторов тестировались модули, касающиеся внутрипроцедурных оптимизаций и/или анализа. Мы полагаем, что подход применим также и к межпроцедурным оптимизациям, а также к более широкому классу языков, например, к функциональным языкам. Адаптация подхода для применения в этих областях - это задача ближайшего будущего.Подход к решению задачи проверки сохранения семантики программы во время работы оптимизатора
При тестировании важной задачей является анализ правильности работы тестируемой системы. Для случая оптимизатора такой анализ состоит из двух частей:Построение абстрактной модели
Модель строится на основе абстрактного описания алгоритма оптимизации. Алгоритм оптимизации формулируется с использованием терминов, обозначающих сущности некоторого подходящего абстрактного представления программы, такого как граф потока управления, граф потока данных, таблица символов и пр. Оптимизатор для осуществления своих трансформаций ищет сочетания сущностей абстрактного представления программы, которые удовлетворяют некоторым шаблонам (например, наличие в программе циклов, наличие в теле цикла конструкций с определенными свойствами, наличие в процедуре общих подвыражений, наличие между инструкциями зависимости данных некоторого вида и пр.). При этом могут учитываться сущности лишь части терминов. Для построения модели мы будем рассматривать только те термины, которые именуют сущности, задействованные хотя бы в одном шаблоне. Итак, в результате анализа алгоритма выделяются термины и шаблоны, используемые в алгоритме. Далее на основании этой информации описывается множество модельных строительных блоков:Это случается только тогда, когда значение
Такое представление тесно связано с синтаксической структурой программы. Редукция грамматики языка позволяет получить модель, которая состоит из следующих видов строительных блоков:
| (1) |
Практическое применение подхода
С помощью предложенного подхода были построены тесты и протестированы ряд оптимизаторов в нескольких компиляторах для современных архитектур: GCC, Open64, Intel C/Fortran compiler. Были разработаны генераторы для следующих оптимизаторов:Применение модельного подхода для автоматического тестирования оптимизирующих компиляторов
, , ,В статье предлагается концепция автоматизированного построения тестовых наборов и тестовых оракулов для тестирования оптимизаторов. Используется подход, основанный на генерации тестов из моделей. Основные идеи модельного подхода заключаются в следующем: 1) модельный язык неявно разбивает множество программ целевого языка на классы эквивалентности; 2) критерий тестового покрытия формулируется в терминах модельного языка; 3) в соответствии с выбранным критерием генерируется набор тестов. В работе описывается схема построения тестового оракула, который проверяет сохранение семантики программы после ее оптимизации.
Создание генератора тестов
Будем называть тестовым воздействием единичные входные данные для тестируемого оптимизатора, т.е. программу на целевом языке. Термин ко-тестовое воздействие обозначает единичные входные данные для результата компиляции тестового воздействия, т.е. значения параметров для запуска соответствующей откомпилированной программы. Итератор ко-тестовых воздействий - это компонент, обеспечивающий перебор необходимых ко-тестовых воздействий и запуск откомпилированного тестового воздействия. Таким образом, один тест включает в себя тестовое воздействие и итератор ко-тестовых воздействий. Для получения множества тестов для целевого оптимизатора необходимо разработать соответствующий генератор. Этот генератор должен создавать представительное множество тестовых воздействий вместе с соответствующими итераторами ко-тестовых воздействий. Создание генератора представительного множества тестовых воздействий начинается с анализа алгоритма тестируемого оптимизатора, построения абстрактной модели и формулировки критерия тестового покрытия, как это было описано выше. После этого происходит разработка собственно генератора. Генератор тестов состоит из двух компонентов. Первый, называемый итератор, отвечает за последовательную генерацию модельных структур. Второй компонент, называемый меппер, отвечает за отображение каждой модельной структуры в целевой язык. Итератор должен создавать множество модельных структур в соответствии с выбранным критерием тестового покрытия. Для данной модельной структуры S меппер должен строить соответствующий тест со следующими свойствами:это основной инструмент при создании
Компиляторы[,,] - это основной инструмент при создании программного обеспечения, поэтому их надежность особенно важна. Наряду с другими методами верификации и валидации для компиляторов, тестирование попрежнему остается важным элементом в семье этих методов. Необходимость автоматизации тестирования компиляторов также представляется очевидной, поскольку реальные объемы добротных наборов тестов и сложность анализа результатов весьма велики. Подход UniTesK [] является методологий построения надежного и качественного ПО на основе использования моделей этого ПО. UniTesK использует модельный подход в следующих целях:Целевое ПО в этих проектах принадлежало к различным классам систем, предоставляющих процедурный интерфейс:
Для автоматического получения представительных множеств
Для автоматического получения представительных множеств тестов строились генераторы на основе абстрактного описания алгоритма оптимизации. При построении генераторов использовались инструменты и библиотеки, которые существенно облегчали процесс разработки моделей и компонентов генератора. Некоторые этапы создания генератора при этом были полностью автоматизированы. Преимущества использования модельного подхода состоят в следующем:Запуск тестов
Для проверки сохранения семантики программы оптимизатором каждый тест требуется откомпилировать с оптимизацией, выполнить результат компиляции и сравнить полученную трассу с эталоном. Напомним, что мы предлагаем в качестве эталона использовать трессу неоптимизированной версии соответствующего теста. Итак, процесс запуска тестов и анализа результатов состоит из следующих шагов:Тестирование софта - статьи
Аннотация.
В статье излагается метод сокращения набора тестов для регрессионного тестирования, заключающийся в построении модели поведения программы на наборе тестов и последующей фильтрации тестового набора с помощью построенной модели. Модель поведения программы строится в терминах последовательностей системных вызовов, совершенных программой во время своего выполнения.Экспериментальное исследование метода сокращения набора тестов
Целью проводимого эксперимента являлось определение и оценка параметров метода сокращения набора тестов, предложенного в данной работе, на модельных данных. При анализе методов сокращения набора тестов важны следующие параметры:Метод сокращения набора тестов
Идея предлагаемого метода сокращения набора тестов достаточно проста. Можно предположить, что при тестировании функциональности и структуры программы встречаются тесты, которые инициируют одни и те же последовательности системных вызовов. Метод основан на этом предположении и состоит в "отсеивании" тех тестов, которые не генерируют новые последовательности системных вызовов, то есть не инициируют нового поведения программы. В предлагаемом методе сокращения набора тестов используется модель поведения программы, построенная в предыдущем разделе. Общий алгоритм работы метода выглядит следующим образом (см. Рис.2):
Рис.2Алгоритм работы метода.
Здесь T - первоначальный набор тестов;T' - сокращенный набор тестов, T'
t - очередной тест из первоначального набора, t
Mt - модель поведения программы на тесте t;
MT' - множество моделей поведения программы на тестах из 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. При использовании этой оценочной функции эффективность генетического алгоритма будет не выше, чем при случайном поиске. Примеры более эффективных оценочных функций для некоторых метрик полноты тестового покрытия можно найти в [,,].
Генетические алгоритмы
Генетические алгоритмы - это метод решения задач оптимизации. В методе используются идеи, почерпнутые из эволюционной биологии: наследование признаков, мутация, естественный отбор и кроссовер []. Определяется множество кандидатов, среди которых ищется решение задачи. Кандидаты представляются в виде списков, деревьев или иных структур данных. Общая схема генетического алгоритма выглядит следующим образом:Генетический алгоритм генерации тестов
Рассмотрим следующую задачу генерации тестов: Задача 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' из
, для которых выполняются следующие условия:
.
Обозначим через 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" - работают очень плохо. Для критериев, связанных с покрытием тех или иных путей в коде программы, удается построить достаточно удобные оценочные функции, основанные на количестве непокрытых дуг в пути, который нужно покрыть. В статье построены такие функции для некоторых широко распространённых критериев полноты тестового покрытия.Тестирование софта - статьи
Конфигурационные параметры
Другой вид структуризации тестовых наборов — определение и использование некоторых конфигурационных параметров, управляющих ходом тестирования, набором подключаемых компонентов и выполняемыми проверками. Во многих случаях достаточно статически устанавливаемых конфигурационных параметров, значения которых заносятся в конфигурационные файлы или передаются запускающей тестовой набор программе в качестве аргументов. Такие параметры могут определять глубину проводимого тестирования, набор выполняемых тестов (например, используя некоторый помечающий их квалификатор), объем проводимых проверок — некоторые проверки в тестах можно помечать как опциональные и выполнять, только если выставлено соответствующее значение некоторого параметра. Большей гибкости управления тестовым набором можно добиться, использую динамически устанавливаемые конфигурационные параметры, хотя они несколько повышают сложность анализа и сопровождения тестового набора. Приведем два примера их использования.Примерами такой функциональности являются межпроцессное взаимодействие в операционных системах и зависящая от многих факторов обработка заголовков телекоммуникационных протоколов. Ошибка, связанная с полной неработоспособностью такой функции, может сделать результаты выполнения сложных тестов для нее совершенно непонятными — обычно бывает ясно, что ошибка есть, но более точная ее локализация требует значительных усилий. Во избежание подобных затрат можно предварять выполнение сложных тестов различных аспектов такой функциональности простыми тестами на работоспособность функции в целом. Сложные тесты должны выполняться только в том случае, если предшествовавшие им простые не нашли ошибок. Реализовать описанную процедуру можно с помощью динамически устанавливаемых по результатам простых тестов конфигурационных параметров. Аналогично, нагрузочные тесты имеет смысл выполнять только в том случае, если проверяемые ими элементы тестируемой системы выполняют свои основные функции правильно.
Квалификаторы
Наиболее простой способ внесения дополнительной структуры в тестовый набор основан на определении нескольких видов меток или квалификаторов и их использовании в коде тестов или в качестве декларативных описателей тестов. Декларативными квалификаторами удобно пользоваться для классификации тестов по нескольким аспектам, например, по проверяемым требованиям и затрагиваемым элементам тестируемой системы. Выделить группы тестов так, чтобы они объединяли тесты по обоим этим признакам сразу, чаще всего невозможно. Поэтому лучше преобразовать тестовый набор в своего рода базу данных о тестах, где дополнительные квалификаторы буду играть роль атрибутов теста и позволят выделять подмножества тестов по практически произвольным признакам. Помимо связей с требованиями и элементами тестируемой системы в виде квалификаторов можно представлять классификацию тестов по целям, видам проводимого тестирования, по сложности или по другим признакам. Квалификаторы-метки обычно более удобны для выделения статической информации о тестах, однако иногда такие метки в коде тестов могут использоваться и для определения ряда характеристик теста в динамике, во время его выполнения. Например, тест может быть нацелен на достижение определенной ситуации или цели тестирования, что можно указать декларативным квалификатором, но в ходе тестирования такая ситуация не всегда возникает из-за недетерминизма поведения тестируемой системы или ошибок в ней. Чтобы уметь определять, возникала или нет эта ситуация в ходе тестирования, достаточно обеспечить сброс в трассу из кода теста определенной метки в тот момент, когда это становится ясно.Модульность
Самой мощной техникой структуризации тестового набора является выделение в нем модулей, ответственных за решение разнообразных задач, возникающих во время работы теста. Простейший способ выделения таких компонентов — определение групп тестовых вариантов, ответственных за проверку определенных элементов тестируемой системы или же некоторых аспектов требований к ней. Эти группы могут образовать иерархию, в которой группы верхнего уровня далее разбиваются на подгруппы, и т. п. При такой организации тестов выделение основных групп возможно еще на ранней стадии создания тестового набора, что позволяет эффективно распределять усилия по его разработке в большой команде. Однако более полезным с точки зрения обеспечения многократного использования одного и того же кода является выделение модулей внутри самих тестовых вариантов. Наиболее четко могут быть выделены следующие виды компонентов.Адаптеры используются и в UniTESK под именем медиаторов , и при разработке тестов на TTCN для их привязки к конкретным тестируемым системам. В UML Testing Profile адаптеры не упоминаются, поскольку он определяет структуру абстрактного тестового набора, не зависящего от синтаксиса обращений к тестируемой системе.
Выгода от их использования появляется при необходимости создавать разнообразные объекты одного типа данных в разных тестах. Генераторы тестовых данных сложной структуры обычно делаются составными. Например, генератор значений комплексных чисел можно построить из двух генераторов действительных чисел — для вещественной и для мнимой частей. Генератор сложных документов удобно строить в виде системы из взаимодействующих генераторов отдельных частей таких документов — заголовков, отдельных полей и разделов, отдельных фраз и слов. В технологи UniTESK такого рода компоненты названы итераторами . В профиле UML для разработки тестов они названы селекторами данных (data selector). Селекторы могут использовать контейнеры данных (data pool), хранящие определенный набор данных, выбор из которых может производиться селектором по дополнительным правилам.
Поскольку данные сами по себе не активны, а лишь используются в вызываемых операциях и возникающих событиях, обычно ограничения целостности данных используются всеми оракулами операций и событий, в которых затрагивается соответствующий тип данных. Выделение таких модульных оракулов оправдано двумя факторами.
При этом обычно используют набор тестовых агентов, каждый из которых отслеживает поведение только одного компонента системы или небольшой их группы. Он сам может выносить вердикт о корректности событий, касающихся отслеживаемых компонентов, в том числе, используя оракулы отдельных событий. Однако к поведению системы в целом могут при этом предъявляться требования, которые ни один из таких агентов не в состоянии проверить самостоятельно. Тогда их проверка организуется в отдельном компоненте, который получает необходимую ему информацию от всех тестовых агентов. Примером такого составного оракула является сериализатор в технологии UniTESK , который проверяет правильность набора событий в соответствии с семантикой чередования, убеждаясь, что наблюдаемое поведение системы соответствует возникновению этих событий в некотором порядке (все равно в каком). Для этого он вызывает оракулы отдельных событий в разных последовательностях, пока не будет найдена корректная или не будет показано, что подходящей последовательности нет, что означает ошибку. Другим примером является арбитр, один из компонентов, определяемых в профиле UML для разработки тестов . Однако он играет только роль посредника, позволяя тестовым агентам, выносящим свои вердикты на основе доступной им информации, обмениваться данными об этих вердиктах друг с другом. Других компонентов тестов, которые выделялись бы в рамках различных методов построения тестов и различными авторами, пока не удается найти. Все остальные виды компонентов специфичны для определенных методов тестирования и чаще всего не имеют аналогов в других подходах. При определении модульной структуры тестов стоит учитывать, что вместе с появлением возможности многократно использовать одни и те же компоненты, повышается и их сложность для людей, не знающих об используемой архитектуре. Поэтому архитектура таких тестов, с указанием всех видов используемых компонентов и задач, решаемых ими, должна быть описана в документации на тестовый набор. Крайне желательно постепенно стандартизовать достаточно широкий набор видов модулей теста, чтобы сделать возможным их использование в различных инструментах. Другое препятствие к широкому использованию модульности тестов связано с усложнением анализа ошибок.Тест уже не представляет собой единый, изолированный сценарий работы, для понимания которого не нужно заглядывать в много различных документов или в код различных компонентов. Поток управления в рамках одного тестового варианта или более сложного теста становится иногда очень причудливым и сложным, разобраться в нем становится тем труднее, чем больше разных видов компонентов используется. Поэтому при использовании модульных тестов необходимы дополнительные усилия по упрощению анализа ошибок. Одним из вариантов решения этой проблемы может быть автоматическое создание более простых, классических тестовых вариантов, повторяющих ситуацию, в которой обнаружена ошибка.
Проблемы организации тестовых наборов
При тестировании всегда используется конечный набор тестов, даже в определении тестирования в SWEBOK сказано, что оно проводится в конечном наборе ситуаций. Однако не менее важен другой аспект тестирования, также зафиксированный в этом определении, — необходимость сделать выводы о качестве проверяемой системы и потребность в том, чтобы такие выводы были достоверными. Чтобы обеспечить достоверности выводов по результатам тестирования, оно должно проводиться так, чтобы все существенные аспекты поведения тестируемой системы и все факторы, способные повлиять на его корректность, были хоть как-то затронуты. Для сложного программного обеспечения таких аспектов и факторов очень много, что и приводит и к большому количеству необходимых тестов, и к сложности их самих. Чаще всего тестовые наборы организуются в виде комплектов тестовых вариантов. Один тестовый вариант представляет собой последовательность действий, состоящую из следующих частей.Оно довольно удобно для разработки тестов человеком — можно поставить определенную задачу (в данном случае представленную целью тестирования), а затем оформить ее решение в виде отдельной процедуры, которую можно использовать независимо от разработчика. Кроме того, такая организация тестов имеет следующие достоинства.
Например, проверка реакции системы на одни и те же действия обычно одинакова, генерация тестовых данных для разных операций может выполняться одними и теми же процедурами. Все это приводит к потребности обеспечения многократного использования одних и тех же решений, которые стоит оформлять в виде отдельных компонентов. Таким образом выделяются тестовые оракулы — компоненты, чья задача состоит в проверке корректности поведения тестируемой системы в ответ на воздействия определенного типа, возникающие в различных тестах. Генераторы тестовых данных тоже часто становятся отдельными компонентами, которые можно использовать в разных тестах. Возможные виды компонентов тестовых систем обсуждаются в профиле универсального языка моделирования 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 содержит:
Процесс генерации теперь будет выглядеть несколько по-другому. Сначала нужно определить зависимости между значениями полей. Граф зависимостей должен быть ациклическим, иначе будет невозможно установить порядок построения полей вершины. Когда порядок построения полей определен, строим множество значений для независимого поля, выбираем из него какое-то значение и устанавливаем его в качестве соответствующего поля вершины. Затем строим множества значений для полей, зависящих от уже установленного поля, выбираем из них значения, устанавливаем выбранные значения в качестве соответствующих полей вершины и т.д.
Таким образом, множества значений полей строятся в соответствии с уже построенным контекстом, поэтому результирующая вершина будет удовлетворять всем наложенным ограничениям. Для построения детей вершины можно использовать такой же метод генерации вершин соответствующего типа (см. рис 6; стрелками указано направление движения данных от одного генератора к другому).
Откуда могут возникать ограничения и связи между элементами дерева? Есть четыре основных источника.
Использование абстрактных моделей
В описании схемы генерации почти ничего не говорилось об использовании аспектов состояний вершин. Между тем, помимо снижения вычислительных затрат, этот механизм даёт весьма важные преимущества при решении задач снижения количества генерируемых данных, достижения критериев покрытия и целенаправленной генерации. Для начала введём понятие абстрактной модели. Абстрактная модель объекта - это фактор-объект, получаемый с помощью абстрагирования от некоторых деталей. В качестве примера можно привести граф наследования классов в языках 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. Контекст для строящейся вершины типа t - это внешний параметр генератора.Список литературы
| [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.
При построении дерева обычно не требуется полностью знать состояние вершины. Нужны некоторые аспекты построенной части дерева, которые могут влиять на построение полей данной вершины. Эти аспекты будем называть аспектами состояния вершины. В приведённом выше примере для построения новой инструкции определения переменной нам нужен только один аспект состояния - список уже определённых переменных. Заметим, что при построении очередного элемента дерева все аспекты состояний вершин, для которых существенен этот элемент, должны измениться. Итак, множество значений поля вершины может зависеть от:
Тестирование программных систем является важным
Тестирование программных систем является важным компонентом всех проектов, связанных с разработкой программного обеспечения. По мере роста масштабов и сложности программных систем растет трудоемкость тестирования. Решить задачу повышения качества и сокращения расходов на тестирование можно за счет привлечения эффективных средств автоматизации разработки тестов. Одной из наиболее распространенных задач, возникающей при тестировании сложных программных систем, является генерация тестовых данных сложной структуры. Такие задачи характерны для тестирования систем, использующих интернет-протоколы, 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Направления дальнейшего развития
В данной работе мы представили расширение технологии 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. Она ориентирована в первую очередь на оценку качества с помощью следующих методов:Инструментальная поддержка 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. Это очень простая, но в то же время и показательная схема, на которой видно, как происходит взаимодействие между инструментами (в том числе, и не описанными выше, но которые также оказывают свое влияние на процесс тестирования).

Рисунок 5. Интеграция средств Rational Инструментальные средства Rational глубоко интегрированы друг с другом. Выходная информация одного инструмента является входной для другого. На рисунке показано, какие инструменты и как используются в проекте. Ко всем перечисленным элементам следует добавить IBM Rational Unified Process, как основу описания процессов, IBM Rational SoDA, как основной инструмент формирования проектной документации для всех инструментов IBM Rational, и, самое главное - IBM Rational Project Console – средство визуализации проектных метрик.
Итерационная разработка
В соответствие с RUP жизненный цикл программного продукта (не важно, информационной системы, графического редактора или очередной версии популярной компьютерной игры. Но в целях единообразия мы дальше будем говорить, как правило, о системе) состоит из серии относительно коротких итераций.
Рисунок 1. Итерационный жизненный цикл программного продукта Итерация — это законченный цикл разработки, приводящий к выпуску конечного продукта или некоторой его сокращенной версии, которая расширяется от итерации к итерации, чтобы, в конце концов, стать законченной системой. Каждая итерация включает, как правило, задачи планирования работ, анализа, проектирования, реализации, тестирования и оценки достигнутых результатов. Однако соотношения этих задач может существенно меняться. В соответствие с соотношением различных задач в итерации они группируются в фазы. В первой фазе — Содержание — основное внимание уделяется задачам анализа. В итерациях второй фазы — Разработка — основное внимание уделяется проектированию и опробованию ключевых проектных решений. В третьей фазе — Построение — наиболее велика доля задач разработки и тестирования. А в последней фазе — Передача — решаются в наибольшей мере задачи тестирования и передачи системы Заказчику (хотя форма "передачи" Заказчику существенно различается для заказных систем и коробочных продуктов). Каждая фаза имеет свои специфические цели в жизненном цикле продукта и считается выполненной, когда эти цели достигнуты. Так целями Начала является определение назначения и границ разрабатываемой системы. Целями Разработки являются построение и проверка архитектуры системы, то есть тех типовых решений, на которых будет основываться вся последующая разработка системы. Это и технологические решения (используемые языки и средства разработки, готовые средства), и используемые шаблоны (вплоть до типовых приемов обработки ошибок), и просто код, реализующий ключевые функции системы. Целями Построения является быстрое и экономичное построение законченной системы.
К концу фазы система должна быть готова к бета тестированию (на площадке заказчика). Построение предполагает активное использование и развитие разработанной архитектуры. Передача нацелена на завершение работ над проектом, включая тестирование, окончательную наладку и передачу системы Заказчику в постоянную эксплуатацию. Все итерации, кроме, может быть, итераций фазы Начало, завершаются созданием функционирующей версии разрабатываемой системы.
Рисунок 2. Процессы, стадии и итерации в RUP Исторически предшественниками RUP являются структурные методологии, основанные на использовании так называемого каскадного подхода или водопада. При использовании каскадного подхода тестирование проводится только на завершающих стадиях разработки. При этом тестированию, как правило, подвергаются завершенные модули, а затем полностью собранная система. Основным недостатком каскадного подхода с точки зрения тестирования считается то, что тестирование начинается на завершающих стадиях проекта, и у разработчиков часто уже не остается времени на устранение обнаруженных дефектов, особенно, если они связаны с ошибками в проектировании архитектуры системы.
Метрики тестирования и качества
Как говорят, тестировать нужно чуть-чуть меньше, чем слишком много. Как найти эту грань? Ведь недостаток тестирования может вести к выпуску продукта с существенными недостатками. А "лишнее" тестирование может стоить достаточно дорого, задерживать выпуск продукта и отвлекать тестировщиков от других работ. Чтобы принять решение о прекращении тестирования, чтобы выбрать оптимальный набор тестов и для многих других целей используются метрики тестирования и качества. Они позволяют оценить покрытие кода продукта тестами, спрогнозировать число ненайденных дефектов, оценить характеристики тестируемой системы.Оглавление
Ориентация на архитектуру.
Еще одной важной и одновременно принципиальной особенностью RUP является его ориентация в первую очередь на архитектурные задачи. Разработка системы начинается с создания архитектурного базиса или каркаса. Этот каркас представляет собой исполняемую программу (исполняемую архитектуру), содержащую образцы решения всех принципиальных и/или типовых задач разрабатываемой системы. Подобный подход позволяет устранять наиболее тяжелые риски – архитектурные – на ранних стадиях разработки. Завершение разработки исполняемой архитектуры означает, что далее перед участниками проекта осталась, в основном, задача наращивания «мышечной массы» проекта по проверенным шаблонам и образцам. Кроме того, ориентация на архитектуру означает большое внимание таким вопросам как разработка и строгое следование архитектурным шаблонам, за счет чего существенно повышаются качество и скорость реализации системы.Основные артефакты, создаваемые в процессе тестирования
В соответствие с RUP в процессе тестирования создается и используется много различных документов и моделей. Ключевые документы и модели и их назначение перечислены ниже:Как показывает наша практика построения
Как показывает наша практика построения жизненного цикла разработки ПО и внедрения технологий IBM Rational, в России (последние 1-1,5 года) идет лавинообразный всплеск интереса у разрабатывающих ПО организаций к правильному построению процессов жизненного цикла разработки ПО, и особенно к процессу тестирования. Руководители и разработчики начинают понимать важность процесса тестирования, для повышения качества программных систем. Становится очевидным, что чем позже начать тестировать программную систему, тем выше риски, тем менее надежной она может получиться. Нам приятно осознавать, что тестирование из прикладного процесса с невысоким приоритетом переходит в разряд особо важных процессов, чей жизненный цикл начинается параллельно с разработкой программных систем. Всем, кто хочет поднять свой профессиональный уровень в тестировании, а также всем, кого интересуют технологии IBM Rational, предназначен данный материал. Представленный Вашему вниманию материал является нашей попыткой объединить все разрозненные материалы по тестированию воедино, а также передать частицу нашего опыта в этой области. Опираясь на методологию IBM Rational и ее программные средства, для поддержки и осуществления процесса тестирования, мы расскажем что, как и когда использовать при тестировании программных систем.Приемосдаточные испытания
Приемосдаточные испытания оформляют процесс передачи продукта от Разработчика Заказчику. В зависимости от особенностей продукта и от требований Заказчика они могут проводиться в различной форме. Например, в виде альфа- или бета-тестирования. Или в виде формальных испытаний, проводимых строго в соответствие с утвержденной программой и методиками.Самое главное в 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. В данном случае речь идет о применении регрессионных тестов. Обратите внимание на точку "пика". Это точка обозначает одновременно максимальную нагрузку, максимальное число найденных ошибок и максимальную задействованность тестировщиков, так как трудоемкость на данном шаге достаточно большая, так как сценарным языком тестирования необходимо сымитировать весь спектр функциональных возможностей тестируемого ПО. Как видно из графика, во всех последующих версиях программного продукта количество найденных ошибок уменьшается. А поскольку процесс тестирования автоматизирован, то сокращаются трудозатраты тестировщиков, соответственно, возрастает объем тестов, и, как следствие программное обеспечение тестируется все более и более полно.
Рисунок 6. Число ошибок, найденных в версиях программного обеспечения. Следующий рисунок демонстрирует рост числа тестов в ходе разработки. Отправной точкой были 50 ручных тестов, по которым тестировалось программное обеспечение. Ручной труд, как известно, не очень производителен.
Автоматизация тестирования позволила кардинально увеличить число тестов (теперь тестировать не только самые главные функции) и довести его число до 450. Разумеется, ручное выполнение такого количества тестов потребовало бы огромных ресурсов. При автоматизации тестирования удалось обойтись прежним количеством тестировщиков.
Рисунок 7. Число тестов во времени. И в заключении еще несколько характеристик объема тестирования. 50 тестов выполнялись изначально для одной версии, сейчас же проводится тестирование 4-х версий для 30 заказчиков. При этом затраты на тестирование каждой версии уменьшились. К сожалению, в рамках одной статьи мы не можем более полно рассказать о видах тестов, например, о нагрузочных тестах моделирования нагрузок на клиент-серверные системы (снова же из практики: одна из компаний отказалась от разработки своей перспективной коммуникационной системы после проведения всего комплекса нагрузочных тестов, так как система не была готова к реальной нагрузке. Компания оценила риски, сделала выводы, и основываясь на полученных знаниях по другому спроектировала и реализовала систему). Давайте подведем итоги. Использование RUP обеспечивает:
Жизненный цикл продукта и Тестирование
Все чаще в наше время используются итеративные процессы разработки ПО. Одним из примеров такого подхода является RUP. При использовании такого подхода Тестирование перестает быть процессом "на отшибе", который запускается после того, как программисты написали весь необходимый код. Тестирование оказывается вовлеченным в гущу событий буквально с самого начала работы над проектом. Работа над тестами начинается с самого начального этапа выявления требований к будущему продукту и тесно интегрируется с текущими задачами. И это предъявляет новые требования к тестировщикам. Их роль не сводится просто к выявлению ошибок как можно полнее и как можно раньше. Они должны участвовать в общем процессе выявления и устранения наиболее существенных рисков проекта. Для этого на каждую итерацию определяется цель тестирования и методы ее достижения. А в конце каждой итерации определяется, насколько эта цель достигнута, нужны ли дополнительные испытания, и не нужно ли изменить принципы и инструменты проведения тестов. В свою очередь, каждый обнаруженный дефект должен пройти через свой собственный жизненный цикл. Дефект заносится в базу дефектов. Аналитик определяет, не является ли он повтором внесенного ранее дефекта. Действительно ли он является дефектом? Руководитель утверждает исполнителя, который приступает к устранению дефекта в соответствие с назначенным дефекту приоритетом. Тестировщик повторяет выполнение теста и убеждается (или не убеждается) в устранении дефекта. Строгое соблюдение жизненного цикла дефекта позволяет существенно улучшить управление проектом, а также избежать "расползания" требований под видом исправления ошибок. И избежать ненужной работы по излишней "полировке" продукта.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования






