Предисловие В сборнике представлены статьи сотрудников Института системного программирования и некоторых других организаций, описывающие научные результаты исследований, полученные в 2003-2004 гг. В статьях обсуждаются как теоретические вопросы, так и проблемы реализации программных систем. Сборник состоит из двух частей. Первая часть включает следующие 12 статей. В статье А.В. Баранцева, В.В. Кулямина, В.А. Омельченко и О.Л. Петренко «Проблемы внедрения наукоемких технологий» систематизируются факторы, влияющие на успешность или неуспешность внедрения технологии, и предлагаются способы повышения вероятности успеха внедрения. Предлагаемые подходы могут требовать как модификации самой технологии, так и использования особых методов для работы с персоналом и руководством организаций, в которых происходит внедрение. Работа поддержана грантом Российского Фонда содействия отечественной науке. Статья В.В. Кулямина «Комбинаторика слов и построение тестовых последовательностей» посвящена возможным подходам к решению задачи построения тестовых последовательностей при наличии ограниченной информации о тестируемой системе. В статье не излагается полное решение задачи, а лишь рассматривается несколько подходов к такому решению и обсуждаются результаты, полученные в рамках этих подходов. Работа поддержана грантом Российского Фонда содействия отечественной науке. В статье С.В. Зеленова и С.А. Зеленовой «Автоматическая генерация позитивных и негативных тестов для тестирования фазы синтаксического анализа» описывается методика автоматической генерации наборов позитивных и негативных тестов для тестирования фазы синтаксического анализа. Предлагаются критерии покрытия для таких наборов, основанные на модельном подходе к тестированию, и методы генерации наборов тестов, удовлетворяющих предложенным критериям. В статье М.В. Архиповой «Генерация тестов для модулей проверки статической семантики в компиляторах» исследуется проблема построения программ-тестов для модулей проверки статической семантики в компиляторах, рассматриваются существующие способы формального описания правил статической семантики языков программирования и обсуждаются причины, мешающие использованию существующих нотаций для решения задачи генерации семантически корректных программ-тестов. Статья А.А. Сортова и А.В. Хорошилова «Функциональное тестирование Webприложений на основе технологии UniTesK» посвящена анализу дополнительных возможностей автоматизации функционального тестирования Web-приложений на основе технологии UniTesK. Рассматриваются 5
существующие подходы к автоматизации функционального тестирования Webприложений, обсуждаются их достоинства и недостатки. В статье А.Я. Калинова, А.С. Косачёва, М.А. Посыпкина и А.А. Соколова «Автоматическая генерация тестов для графического пользовательского интерфейса по UML диаграммам действий» излагается метод автоматической генерации набора тестов для графического интерфейса пользователя, моделируемого детерминированным конечным автоматом с помощью UML диаграмм действий. Метод заключается в построении обхода графа состояний системы с применением неизбыточного алгоритма обхода и компиляции построенного обхода в тестовый набор. В статье Н.В. Пакулина «Применение UniTesK к тестированию встроенных систем» обсуждаются вопросы применимости технологии тестирования UniTesK к функциональнуму тестированию программного обеспечения встроенных систем на примере сенсорных сетей под управлением TinyOS. Выделяются сходство и отличие указанного класса программного обеспечения от систем, для которых хорошо разработаны методики применения UniTesK. Статья В.В. Рубанова, А.И. Гриневича и Д.А. Марковцева «Использование особенностей ЦПОС в компиляторе языка С» содержит описание характерных особенностей ЦПОС и связанных с ними оптимизаций, которые могут быть реализованы в компиляторе языка С. Рассматриваются как сами алгоритмы оптимизаций, так и взаимное влияние различных оптимизаций друг на друга. В статье Ю.Н. Фонина и С. Грассмана «Архитектура и принципы построения операционной среды ‘мини-ОС’» представлены архитектура и принципы построения операционной среды мини-ОС, предназначенной для использования во встроенных системах. Особое внимание уделено вопросам портирования мини-ОС и минимизации усилий на адаптацию ОС под требования аппаратуры и приложений пользователя. В статье Г.В. Ключникова, Д.С. Мишина, Д.В. Москалева, А.В. Никешина и В.З. Шнитмана «Межпротокольный шлюз NAT-PT с функциями DNS-ALG и FTP-ALG для обеспечения взаимодействия между сетями IPv4 и IPv6» рассматриваются вопросы реализации шлюзов прикладного уровня DNS-ALG и FTP-ALG для межпротокольного шлюза NAT-PT, соответствующих проекту стандарта IETF RFC 2766 “Network Address Translation - Protocol Translation”, а также особенности применения этих средств для обеспечения плавного и безболезненного перехода на протокол IPv6. Работа поддерживалась грантами РФФИ. В статье К.В. Буздина «Исполнение моделей при помощи виртуальной машины» предлагается способ исполнения моделей, основанный на использовании виртуальных машин. Показаны его основные преимущества по сравнению с непосредственной интерпретацией и конечными автоматами, которые широко применяются в данной области.
6
В последней статье первой части сборника – М.В. Ксензов, «Рефакторинг архитектуры программного обеспечения: выделение слоев» – рассматриваются один из основных методов рефакторинга архитектуры ПО – выделение слоев, а также его место в контексте рефакторинга архитектуры как многошагового итеративного процесса. Во второй части сборника представлены 10 статей. В статье П.М. Довгалюка «Анализ и оптимизация циклов с помощью производящих функций» представлены усовершенствования метода распространения констант, использующего GSA-представление (Gated Single Assignment), позволяющие алгоритму находить большее количество констант, чем исходный алгоритм. Во второй статье того же автора «Усовершенствованный алгоритм распространения констант с использованием GSA-представления» рассматривается метод анализа и оптимизации циклов с помощью производящих функций, состоящий в поиске выражений для конечных значений переменных, которые вычисляются в цикле и замене цикла вычислениями по формуле. Статья В.А. Семенова, С.В. Морозова и С.А. Пороха «Стратегии объектнореляционного отображения: систематизация и анализ на основе паттернов» посвящена методам отображения прикладных объектно-ориентированных данных в реляционную модель. Проводится систематизация этих методов, а также их анализ на основе введенной системы паттернов. Задача функционально полного отображения моделей данных рассматривается на примере EXPRESS-нотации, получившей распространение в качестве стандартного средства информационного моделирования научных и промышленных данных. В статье В.А. Семенова, С.В. Морозова и О.А. Тарлапана «Инкрементальная верификация объектно-ориентированных данных на основе спецификации ограничений» рассматриваются задачи полной и инкрементальной верификации объектно-ориентированных данных. На основе теории графов строится формальный аппарат, а также описываются разработанные методы инкрементальной верификации, использующие статический анализ спецификации ограничений и позволяющие локализовать область потенциальных нарушений при изменении данных. Результаты этой и предыдущей статей получены при поддержке РФФИ (грант N 04-01-00527) и Фонда содействия отечественной науке. В статье Д.А. Лизоркина «Оптимизация вычисления обратных осей языка XML Path при его реализации функциональными методами» предлагается алгоритм, позволяющий построить вычисление выражений XPath таким образом, что наличие указателей с дочерних узлов на родительские узлы в дереве документа становится необязательным. Проводится обоснование алгоритма и рассматриваются его основные свойства. 7
Во второй статье того же автора «Язык запросов к совокупности XMLдокументов, соединенных при помощи ссылок языка XLink» предлагается язык, который позволяет приложению прозрачным образом формулировать запросы к ссылкам XLink и осуществлять переходы по определяемым этими ссылками дугам. Язык инкапсулирует сложности синтаксиса XLink от приложения и предоставляет более высокий уровень абстракции при обработке совокупности XML-документов, соединенных ссылками языка XLink. В статье М.П. Рекуц «Виды ограничений целостности в базах XML-данных» на основе анализа потребностей современных приложений, работающих с XMLСУБД, выявляются виды ограничений целостности, которые должны поддерживаться XML-СУБД, и предлагаются средства определения этих видов ограничений с учетом специфики XML-модели данных и опыта, накопленного разработчиками реляционных СУБД. Работа выполнена при поддержке грантами РФФИ. В статье Г.И. Малашонка, А.И. Аветисяна, Ю.Д. Валеева и М.С. Зуева «Параллельные алгоритмы компьютерной алгебры» рассматривается разрабатываемая в рамках среды ParJava система компьютерной алгебры. Цель разрабатываемой системы – предоставить возможность эффективного использования параллельных вычислительных систем для проведения аналитических расчетов. В статье С.С. Гайсаряна и П.Н. Яковенко «К вопросу о генерации начальных данных, обеспечивающих заданную трассу SPMD-программы» исследуется проблема автоматизированной генерации входных данных для SPMDпрограммы на основании ее исходного текста. Вторую часть сборника завершает статья А.В. Инюхина «Открытая Т–система: распределённые вычисления в Internet», в которой рассмотрены возможности технологии автоматического динамического распараллеливания, реализованные в открытой Т–системе для выполнения распределённых вычислений в среде Internet, а также представлены результаты экспериментов, иллюстрирующие перспективы подобных вычислений. Член-корреспондент РАН В.П. Иванников
8
показателей качества итогового ПО? И как можно преодолеть это сопротивление, найти способ успешно внедрять новые технологии разработки ПО? В данной работе мы попытались систематизировать факторы, влияющие на успешность или неуспешность внедрения технологии и предложить способы повышения вероятности успеха в этом предприятии. При этом предлагаемые подходы могут требовать как модификации самой технологии, так и использования особых методов для работы с персоналом и руководством организаций, где мы пытаемся ее внедрить. Но сначала выясним, что вообще мы имеем в виду под внедрением технологии.
Проблемы внедрения наукоемких технологий1 А. В. Баранцев, В. В. Кулямин, В. А. Омельченко, О. Л. Петренко {barancev, kuliamin, vitaliy, olga}@ispras.ru
2. Виды внедрений
1. Введение В настоящее время в России идет активный поиск такой структуры взаимодействия науки и общества, которая была бы максимально выгодна обоим составляющим. Любое успешное решение этой проблемы предполагает повышение отдачи от научных разработок и, в частности, возможность получать от них экономические выгоды. С точки зрения общества наука должна продемонстрировать свою экономическую эффективность, что лучше вcего сделать с помощью примеров успешного внедрения результатов научных исследований в практику. Успешного — значит приносящего внедрившим их организациям реальные выгоды, будь то прямые доходы, снижение издержек, или косвенные выгоды от повышения показателей качества продукции, укрепления положения и конкурентоспособности компании или ее выхода на новые рынки. Посмотрев на положение дел в области методов и технологий разработки программного обеспечения (ПО), мы не найдем больших оснований для оптимизма, поскольку примеров успешного применения отечественных научных разработок в этой области очень мало. Дело тут даже не в российской специфике — за рубежом ситуация практически такая же. За исключением нескольких компаний, агрессивно пытающихся ворваться на рынок или укрепить свое положение на нем за счет использования последних достижений науки, а также компаний, работающих с оборонными ведомствами, у которых повышенные требования к качеству принимаемого ПО, производители ПО весьма неохотно принимают новые методы разработки. Для того, чтобы их использование стало восприниматься как обычное использование специализированных инструментов, требуется много времени, часто — десятилетия. В чем причина этого? Почему индустрия разработки ПО, по сути, сначала отвергает все новые подходы, предлагаемые исследователями и, вроде бы, даже показывающие на примерах повышение эффективности разработки и
Книга [1] определяет внедрение (или коммерциализацию) технологии как перевод результатов фундаментальных и прикладных исследований на уровень конструирования, разработок, производства и коммерциализации новых или улучшенных продуктов, услуг или процессов. При этом часто то, что переводится, является некоторым знанием, а не собственно технологией, т.е. структурированной системой процедур и техник, использование которых решает некоторую совокупность задач в рамках какой-либо области человеческой деятельности. В данной статье мы будем рассматривать, прежде всего, проблемы внедрения именно технологий, уже приобретших достаточно четкую и структурированную форму. Кроме того, акцент будет делаться на наукоемких технологиях, что предполагает их нацеленность на решение таких задач, которые на данном этапе развития индустрии не имеют готовых простых методов решения или же решений, достаточно легко получаемых соединением некоторых уже используемых приемов — внедрение таких технологий проходит обычно гораздо более гладко, чем внедрение наукоемких технологий, использующих элементы нового знания, не успевшего стать общим достоянием. Можно выделить следующие виды внедрений: Опытные или пробные внедрения. Целью этих внедрений является апробация и демонстрация работоспособности технологии. В таком внедрении разработчики технологии обычно играют активную роль, а организация, в которой внедрение проходит, часто довольно пассивна. Масштабные внедрения. Целью таких внедрений является проверка масштабируемости технологии и возможности ее использования в крупных проектах в контексте реального производства. Разработчики технологии в рамках такого внедрения играют важную роль, но основную часть работы берет на себя персонал организации, в
1 Данная работа поддержана грантом Российского Фонда содействия отечественной науке.
9
10
процессы которой новая технология должна вписаться; иначе внедрение окажется просто невозможным. Конвейерные внедрения. Такие внедрения происходят в рамках уже отработанного специализированного процесса внедрения, вовлекающего персонал как внедряющей организации, так и той, в которой осуществляется внедрение, и четко определяющего все процедуры, входные данные и результаты каждого шага и роли вовлеченных лиц. Проводятся такие внедрения силами специализированных организаций-посредников и чаще всего не задействуют разработчиков технологии. Технологии, достигшие этого этапа своего развития, могут быть использованы уже довольно широко и обычно теряют большую часть наукоемкости, скрывая ее в рамках четко определенных, не требующих особых знаний процедур или внутри инструментов, которыми может пользоваться широкий круг специалистов в данной области. Без выработки такого процесса внедрения и отчуждения технологии от ее разработчиков, т.е. обеспечения возможности ее внедрения силами третьих организаций, технология не может выйти на широкий рынок, и чаще всего ее разработка не окупается. Основные трудности при внедрении новой технологии возникают при первых ее практических применениях, которые относятся к первому или второму типу внедрений. Именно на этих вопросах мы и сконцентрируем свое внимание. В рамках данной работы мы остановимся на техниках, которые позволяют преодолеть сопротивление внедрению первого или второго типа, проводящемуся на начальных этапах развития технологии, а вопросы выработки полномасштабного процесса внедрения рассматривать не будем, хотя многие из приводимых ниже подходов могут быть при этом использованы.
3. Факторы успешности применения технологии Поскольку нас интересует, как сделать более успешным внедрение и использование новой технологии, стоит рассмотреть различные факторы, которые могут положительно или отрицательно повлиять на ее успешность. Эти факторы можно разделить на две большие группы: характеристики самой технологии (внутренние факторы) и характеристики окружения, в которой ей предстоит работать (внешние факторы). Обе группы факторов имеют большое значение для успеха внедрения. Первая — поскольку от самой технологии во многом зависит результативность ее использования, вторая — поскольку при успешном внедрении технология должна в итоге включиться в сложный социальный контекст. В этом контексте находятся несколько взаимодействующих социальных групп и множество отдельных индивидуумов, чьи интересы будут затронуты и, соответственно, должны учитываться в процессе внедрения. 11
В следующих разделах мы попытались рассмотреть влияние обеих групп факторов с разных точек зрения, а также возможности влиять на них с целью нивелировки отрицательных эффектов. В силу сложности второй группы факторов, мы разбиваем ее на следующие подгруппы: 1. Случайные факторы в рамках одного проекта (неподходящий персонал или менеджер, сложные или, наоборот, необычайно хорошие отношения с руководством и пр.). Можно посоветовать лишь выявлять такие отрицательные факторы и стараться избавиться от них на начальном этапе проекта внедрения, а если это не получается, то действовать с учетом возникающих рисков на основе представленных ниже соображений по аналогичным постоянным факторам. 2. Постоянные факторы: a. Индивидуальные характеристики персонала — способности, знания, умения, мотивация и пр. Существует большое количество литературы по техникам мотивации персонала или методик обучения в зависимости от индивидуальных способностей и наклонностей, построения тренингов. В целом, учет этих факторов достаточно важен, но в рамках данной работы мы предпочли больше внимания уделить методам работы с организацией как с целостной социальной структурой. Поэтому здесь возможности учета этих факторов не обсуждаются; b. Характеристики организации — атмосфера организации, отношения между людьми, стиль менеджмента, организационные стандарты и принятые в организации правила поведения и технологические процессы; c. Характеристики отрасли в целом — устоявшиеся и широко распространенные стереотипы и парадигмы; d. Национально-культурные особенности. Их мы в рамках этой статьи рассматривать не будем.
4. Внутренние факторы В данном разделе мы рассмотрим подробнее влияние характеристик самой технологии на успешность ее внедрения и использования. Практически все характеристики, существенные с точки зрения использования технологии, можно объединить в понятии ее качества. Можно попытаться определить качество технологии по аналогии с качеством ПО (см. [2, 3, 4]), используя целостный подход, включающий рассмотрение всех важных аспектов. 1. Функциональность. Этот аспект определяет, какие задачи решает технология. Он включает ее пригодность к выполнению определенных работ, степень 12
2.
правильности получаемых результатов, возможность решения других, побочных задач. Эффективность. Этот аспект качества показывает, как соотносятся ресурсозатраты на внедрение и использование технологии с выгодами от ее применения. Он включает отношение прямых и косвенных выгод от использования технологии к затратам на обучение персонала, на приобретение необходимой аппаратуры и ПО, время окупаемости внедрения технологии, итоговое повышение производительности труда при решении с помощью данной технологии задач определенного вида и пр. 4.
Функцио- Эффективнальность ность
Технология
Гибкость
Удобство использования
Надеж- Модифиность цируемость
5.
6.
Окружение
Характеристики персонала Характеристики организации Характеристики отрасли Национальнокультурные особенности
Внедрение Рис. 1. Факторы, определяющие успех внедрения технологии. 3.
Удобство использования. Этот аспект показывает, насколько легко технология осваивается и применяется, каковы трудозатраты на решение определенных задач при 13
14
работе по этой технологии. Основной момент, на который следует обратить внимание при рассмотрении этого аспекта, состоит в том, что удобство использования технологии включает как удобство ее использования конечными пользователями, так и управляемость, т.е. удобство контроля и управления проектами, использующими эту технологию. Кроме того, здесь необходимо учитывать затраты на обучение различных категорий пользователей — конечных пользователей технологии и менеджеров проектов, в которых она применяется, а также, возможно, руководителей более высокого ранга, которым может потребоваться новое понимание процессов, происходящих в их организации. Гибкость. Этот аспект отражает возможность использования технологии в разных условиях, в частности, в разных проектах, организациях, культурных средах, с разным персоналом, с разными инструментами, в сочетании с различными другими технологиями и техниками, с разным масштабом задач — для проведения небольших мобильных проектов или для проведения огромных, ресурсоемких проектов с вовлечением большого количества людей. Модифицируемость. Этот аспект определяет, насколько легко технологию приспособить для решения других задач, добавить к ней новые возможности. Добротность, надежность. Этот аспект качества связан с вероятностными характеристиками технологии, показывающими распределение «плохих» ситуаций, недостатков технологии с точки зрения всех остальных, перечисленных ранее аспектов. a. Какова вероятность неуспешного применения технологии на заданном отрезке времени с заданными характеристиками проекта, персонала, менеджмента, организации в целом? b. Каков возможный разброс характеристик эффективности в заданных условиях? c. Каков возможный разброс требуемого времени на обучение, ресурсов, затрачиваемых на внедрение технологии, какова вероятность неуспеха при обучении технологии? d. Каково соотношение между затратами на сочетание технологии с изменяющимися внешними условиями, возможными выгодами от полученного результата и вероятностью неуспешного исхода? При каких ограничениях на изменения внешних факторов вероятность успеха и получаемые выгоды находятся в определенных границах?
e. Каково соотношение между затратами на изменение технологии, возможными выгодами от полученного результата и вероятностью неуспешного исхода? При каких ограничениях на возможные модификации технологии ресурсозатраты и вероятность успешного использования результата находятся в определенных границах?
2.
Даже такое весьма поверхностное рассмотрение качества технологии в целом позволяет выделить ряд типичных проблем, создающих трудности при попытках внедрить новую технологию разработки ПО. Соответственно, появляется возможность указать пути решения этих проблем. 1. При разработке технологии обычно основное внимание уделяется аспекту функциональности в ущерб другим аспектам качества, из которых для пользователей особенно важны удобство использования и эффективность. Понятно, что на первых этапах разработки технологии ее функциональности должно быть уделено основное внимание. Но как только она становится способной решать поставленные перед ней задачи, требуется серьезная проработка вопросов удобства использования и эффективности, без решения которых разработку нельзя считать завершенной, а саму технологию нельзя признать «пригодной для внедрения». Адекватный подход к созданию удобных в использовании наукоемких технологий возможен на основе синтеза технического и гуманитарного знания, привлечения к проработке вопросов удобства использования и обучения достижений современных эргономики, педагогики и наук, занимающихся изучением процессов формирования новых знаний и умений у людей. Иногда разработка технологии ведется с прицелом на решение общих проблем, и она выглядит слишком громоздкой для решения достаточно простых задач, которые, тем не менее, часто встречаются на практике. В этом случае стоит подумать о возможности использовать технологию в облегченном режиме, позволяющем справляться с такими задачами, не затрачивая лишних усилий. При наличии таких возможностей удобство использования технологии явно возрастает. Кроме того, следует уделить большое внимание управляемости технологии, особенно если она оказывает значительное влияние на показатели общего хода проекта, например, изменяет распределение трудозатрат на различных этапах, делает непригодными использующиеся метрики производительности труда, качества и степени готовности результатов проекта. Для этого надо четко осознать, какие изменения технология вносит в привычную картину проекта, и предложить для использования новые метрики, позволяющие 15
3.
16
менеджерам адекватно отслеживать положение дел в проекте и качество его результатов в новой ситуации. Необходимо также разработать эффективные методы борьбы с основными возникающими в проектах рисками, особенно с теми, возникновение которых становится более вероятным при использовании новой технологии. Оценки эффективности и гибкости новой технологии обычно очень ненадежны в глазах ее будущих пользователей. a. Во-первых, они получены в «тепличных» условиях, в проектах, которые проводятся силами высококвалифицированных сотрудников, часто являющихся авторами технологии и с трудом отделяющих саму технологию от прагматичного использования всего, что только может помочь в решении поставленных задач; b. Во-вторых, они часто основываются на очень небольшой выборке проектов. Принятым способом оценки эффективности технологии является определение ее коэффициента окупаемости инвестиций (return of investment, ROI). Стандартной методики оценки ROI не существует — для разных технологий должны учитываться разные детали и показатели проектов. Но методика оценки ROI для данной технологии должна быть разработана, иначе руководство организации, в которой проходит внедрение, не получит значимых аргументов в пользу эффективности технологии. Кроме того, следует проводить как можно больше различных проектов, нащупывая область применимости технологии и измеряя показатели ее эффективности. При этом должен быть хорошо организован сбор различных метрик, позволяющих оценить состояние проекта и качество его результатов. Кроме традиционных метрик надо собирать и упомянутые выше новые метрики, позволяющие более адекватно оценивать обстановку в новых условиях. Во-вторых, для участия в таких проектах стоит активно привлекать представителей организаций, в которых проводится внедрение. Нужно проводить пилотные проекты в рамках этих организаций, с привлечением их служащих, с тем, чтобы продемонстрировать жизнеспособность технологии в той среде, где ей предстоит работать. Руководство и персонал организации, в которой проходит внедрение, должны иметь возможность удостовериться в оценке не только эффективности, но и других характеристик технологии. Для этой цели чаще всего используются истории уже прошедших внедрений, представляющие мнение служащих организаций, где внедрение проводилось прежде. Кроме того, можно обратиться к независимым техническим специалистам в данной области, чтобы они дали свою оценку качества технологии.
В целом, разработчикам новой технологии, переходящим к попыткам ее внедрения, стоит еще раз внимательно оценить все перечисленные выше аспекты качества технологии, может быть, с привлечением независимых экспертов. Если при этом обнаруживаются серьезные дефекты и недоработки, связанные с нефункциональными характеристиками технологии, их надо устранить, модифицируя саму технологию, до попыток ее внедрения. Кроме того, если технология влияет на стиль и методы руководства проектами и организациями, еще до внедрения она должна иметь дополнительные наработки, связанные с обеспечением ее хорошей управляемости — дополнительные метрики качества продуктов и процессов, показатели состояния проектов, методы преодоления возникающих рисков и т.д. Оценки нефункциональных характеристик технологии должны быть подкреплены не только намерениями разработчиков сделать ее эффективной, удобной, гибкой и пр. Хорошо иметь набор достаточно различных примеров проектов, в которых технология действительно проявила все эти свойства. Здесь, правда, возникает некоторый порочный круг — для успешного внедрения технология уже должна иметь историю успешных внедрений. Но разорвать его можно, используя разнообразие примеров, пилотные проекты, вовлечение независимых разработчиков и представителей заинтересованных организаций в небольшие проекты, в рамках которых эта технология применяется.
5. Внешние факторы При рассмотрении внешних факторов и способов снизить их негативное влияние на внедрение новой технологии можно учитывать оба компонента пары технология-окружение — вносить изменения и в технологию, для того, чтобы она лучше подходила для данного окружения, и пробовать изменить окружение, пытаясь ослабить его сопротивление вносимым изменениям и поддержать те факторы, которые могут помочь успешному внедрению. Вопросы изменения технологии, ее подгонки под окружение мы оставим за рамками данной статьи, потому что такие действия имеют смысл только в двух случаях. В первом случае, если характеристики этого окружения имеют общий характер, речь идет о внутреннем качестве самой технологии, которое мы рассмотрели выше. Во втором случае выгоды от однократного внедрения действительно способны покрыть затраты на «подгонку» технологии под эту конкретную среду. Далее мы рассматриваем только возможности для снижения влияния негативных и увеличения влияния позитивных факторов при внедрении технологии без внесения в нее изменений.
17
5.1. Особенности организации В успехе внедрения новой технологии в некоторую организацию большую роль играют принятые в рамках организации нормы поведения, методы работы с людьми, общие ценности и убеждения ее персонала. Все это в социологии организаций называется организационной культурой. Организационная культура во многом определяет то, какие цели организация преследует при внедрении, общий подход организации к решению возникающих проблем, а также то, какие проблемы вообще рассматриваются как таковые, порядок принятия решений и мотивацию действующих лиц. В работе [5] выделено 4 типа организаций в зависимости от их организационной культуры. Иерархическая организация. Работа такой организации основана на формальных правилах и четко определенной структуре внутренних отношений. Долгосрочные планы такой организации состоят в обеспечении стабильности и плавного, постепенного роста показателей эффективности и рентабельности выполнения основных операций. Успешный руководитель организации этого типа является, прежде всего, организатором и координатором, обеспечивающим стабильное выполнение работ за счет организации четкого контроля результатов и действий подчиненных. Он единолично принимает решения, которые становятся обязательными для всех, при этом мнение подчиненных может учитываться, а может и не учитываться. Часто в подобных организациях считается недопустимым высказывать свои мнения и оценки руководителям самостоятельно, без явного указания с их стороны. При принятии решения руководитель такой организации руководствуется, прежде всего, соображениями повышения эффективности бизнес-процессов и сохранения стабильности. Рыночная организация. Деятельность такой организации ориентирована, прежде всего, на достижение успеха при решении стоящих перед ней задач, которое обеспечивает завоевание и удержание ее места на рынке. Репутация и успех организации считаются общей заботой ее служащих. Перспективные планы ее деятельности направлены на повышение конкуретноспособности и достижение измеримых целей в этом плане. Руководство таких организаций обычно жестко проводит линию на опережение соперничающих организаций в конкурентной борьбе. Оно делегирует свои полномочия временным группам и командам, чья деятельность направлена целиком на решение стоящих перед организацией задач в части, касающейся данной группы. Решения 18
принимаются только на основе заключения группы, связанной с соответствующей областью деятельности организации. При вынесении оценок и принятии решений в такой организации руководствуются соображениями повышения конкурентоспособности и обеспечения лидерства организации на рынке.
o
Инновационная организация. Основными ценностями такой организации являются творческая рабочая атмосфера и новаторский характер деятельности. Ее служащие стремятся работать на переднем рубеже в своей области, поставлять на рынок уникальные продукты и услуги, хотя при этом могут возникать серьезные риски. Руководители таких организаций считаются готовыми к риску новаторами, поддерживающими творческие устремления сотрудников. Для организации такого рода важны, прежде всего, новизна, перспективность, уникальность используемых технологий и получаемых при их помощи продуктов и услуг.
Внедрение в рыночных организациях. При внедрении новой технологии в рыночной организации важно обратить внимание ее служащих на конкурентные преимущества, которые организация получит от использования технологии. Достаточно важным фактором является известность и высокий статус технологии в техническом сообществе, поскольку они придают дополнительный вес организации, использующей ее. В качестве оценщика технологии обычно выступает группа лиц, занимающаяся близкими к этой технологии аспектами деятельности организации, может быть, эксперты и руководители соответствующих проектов. Их одобрением необходимо заручиться, поскольку решение о внедрении технологии всецело зависит от этого одобрения.
Семейная организация. Для таких организаций характерны хорошая внутренняя атмосфера, бригадная организация работ, программы вовлечения сотрудников в бизнес и корпоративные обязательства перед ними. Основной ценностью и одновременно стратегической линией такой организации является личное совершенствование каждого сотрудника, его профессиональный рост и повышение навыков работы в команде. При этом большое значение придается традициям организации, сплоченности коллектива и моральному климату. Руководитель такой организации является воспитателем и куратором деятельности своих подчиненных, которую он направляет с учетом перспектив их личного развития.
Внедрение в инновационных организациях. Внедряя новую технологию в инновационной организации, нужно делать акцент на ее новизне и передовом характере, на том, что с ее помощью организация способна будет поставлять уникальные, новаторские продукты и услуги. При этом достаточно важно убедить каждого вовлеченного в процесс внедрения сотрудника в полезности использования этой технологии, показать возможность ее творческого применения. Однако при этом может возникнуть проблема излишне творческого отношения персонала к самой технологии — сотрудники организации могут начать изменять основные процедуры технологии и произвольно включать в нее или выбрасывать из нее техники и методы решения задач, для обеспечения ее большей эффективности с их точки зрения. Такой подход чаще всего приводит к неудачам при внедрении, и поэтому важно аккуратно определить рамки творчества при работе по данной технологии.
При внедрении в организациях разных типов возникают проблемы и риски различного вида, и для их решения следует принимать во внимание тип организационной культуры данной организации и, соответственно, делать акцент на разных аспектах технологии, использовать различные методы коммуникации. В целом, можно привести следующие рекомендации по обеспечению успеха внедрения в организациях различных типов. Внедрение в иерархических организациях. При внедрении новых технологий в иерархических организациях следует иметь в виду следующие два момента: o Во-первых, обращаться с предложениями по поводу использования технологии, объяснениями ее характеристик и приносимых ею выгод следует прежде всего к руководству. Все общение с персоналом помимо руководства будет 19
неэффективным, поскольку решение о внедрении принимается руководителем единолично. При наличии такого решения практически любое сопротивление персонала будет в итоге преодолено. Во-вторых, при описании выгод от использования технологии следует делать упор на повышении эффективности работы организации при сохранении имеющейся структуры бизнеспроцессов и даже обеспечении большей стабильности этой структуры. Полезно использовать формализованные описания действий персонала (инструкции) при работе в рамках новой технологии. Никаких намеков на возможность серьезных изменений в бизнес-процессах организации в результате ее внедрения не должно возникать, иначе технология будет воспринята крайне негативно.
20
Внедрение в семейных организациях. При внедрении новой технологии в организации такого типа, прежде всего, важно показать ее руководству и отдельным сотрудникам предоставляемые ею возможности для их профессионального роста. При отсутствии таких возможностей внедрение технологии в семейную организацию будет неудачным. При организации внедрения надо ожидать, что не все сотрудники примут новую технологию. В зависимости от своих личных целей, ценностей и способностей часть их будет пользоваться технологией, а часть — нет, при этом возможности мотивировать сотрудников к ее использованию могут быть достаточно ограничены. Поэтому, при необходимости тотального использования технологии для успеха внедрения, в организациях такого типа могут возникать дополнительные трудности. Помимо использования специализированных подходов к внедрению в рамках организаций данного типа, полезно иметь представление о действии более общих факторов. Одним из них является готовность организации к нововведениям. В работе [6] предлагается следующая 6-ти-этапная модель управления инновационными процессами в рамках организации. 1. Давление и побуждение. На этом этапе возникает ощущение необходимости перемен, вызываемое как внешними, так и внутренними факторами, и руководство организации готово к их проведению. 2. Посредничество и переориентация внимания. На этом этапе привлекаются внешние консультанты, способные объективно оценить ситуацию, либо внутри компании создается инновационная группа. 3. Диагностика и осознание. Здесь осуществляется сбор информации, анализ и поиск истинных причин возникновения проблем. 4. Нахождение нового решения и принятие обязательств по его реализации. На данном этапе ищутся новые нестандартные решения, которые будут служить основой инноваций. При этом всегда существует искушение применить проверенные способы. 5. Эксперимент и выявление. На этом этапе проводятся многочисленные эксперименты с целью выявления возможных отрицательных последствий изменений. 6. Подкрепление и согласие.
21
Здесь обеспечивается мотивирование сотрудников на принятие изменений и происходит полномасштабная реализация выработанных решений. Эта модель показывает, что на разных этапах новая технология будет оцениваться по-разному. Если информация о новой технологии появляется на первом этапе инновационного процесса в организации, то решение о ее внедрении будет приниматься долго, и будет казаться, что процесс практически стоит на месте. Если же это происходит на четвертом этапе, когда необходимость нововведений всеми осознана и ищутся конкретные варианты решения проблем, то внедрение технологии пойдет гораздо быстрее.
5.2. Особенности отрасли в целом Особенности отрасли или конкретного вида деятельности в целом, принятые шаблоны работы и стереотипы являются общими характеристиками окружения технологии, преодолевать негативное действие которых чрезвычайно сложно. При позитивном же влиянии они значительно помогают переводу технологии в практическое использование. При работе с ярко выраженными парадигмами и стереотипами значительно эффективнее изменить имеющуюся технологию, приспосабливая ее для работы в этих рамках и рассматривая такие факторы как составляющие ее гибкости и удобства использования. Попытки совершения технологической революции и преодоления широко распространенных стереотипов поведения в рамках технологических и бизнеспроцессов обычно неуспешны, даже при наличии у новых подходов убедительных преимуществ. Единичные случаи успеха действий в этом направлении всегда требовали значительного времени, до десятилетий, и были связаны с готовностью индустрии к принятию нового, с вызреванием кризиса имеющихся методик и технологий. Такие случаи обычно хорошо видны по тому, насколько широкие слои разработчиков воспринимают новые идеи, начинают использовать элементы новых подходов, насколько широко эти подходы обсуждаются в специализированных технических журналах и других изданиях и продвигаются на рынок отдельными компаниями и консультантами. Тем не менее, в качестве воздействия на отрасль в целом часто бывает полезно использовать возможности по обучению студентов и молодых специалистов работе по новой технологии, в частности, включение ее элементов в обучающие курсы университетов. При этом, приходя на работу в промышленность, они становятся более восприимчивыми к новым идеям и менее привержены старым, хотя и широко распространенным методам работы, если те действительно имеют недостатки по сравнению с предлагаемыми в рамках новой технологии. Кроме обучения молодых специалистов и студентов, хорошим средством пропаганды новой технологии является перемещение сотрудников из команды, 22
разрабатывавшей технологию, на работу в промышленность, если, конечно, разрабатывающая технологию организация может позволить себе значительную текучку кадров.
6. Заключение Данная работа акцентирует внимание на аспектах целостного понятия качества технологии и на методах представления новой технологии, нацеленных на обеспечение успешного внедрения с учетом имеющейся организационной культуры. Рассмотренные вместе — эти стороны — внутренние характеристики технологии и способы ее подачи — позволяют, по мнению авторов, выработать достаточно практичную систему мер, позволяющих повысить вероятность успешного внедрения внутренне сложной и потому неоднозначно воспринимаемой пользователями технологии. Предлагаемый подход, будучи дополнен конкретными методами оценки различных аспектов качества новой технологии, позволит вовремя обнаружить недостаточно хорошие показатели эффективности, удобства использования или гибкости технологии, которые очень важны для ее пользователей, но часто не получают достаточного внимания со стороны разработчиков. В то же время, при внедрении достаточно качественной технологии, за счет учета готовности организации к инновациям вообще и организационной культуры в способе представления технологии, можно существенно облегчить восприятие технологии в данной организации и обеспечить эффективное взаимодействие с ее сотрудниками, что способствует более успешному внедрению. Кроме того, применение новой технологии в большом количестве разнообразных проектов, проводимых в разном окружении, использование пилотных проектов, обучение пользователей с учетом индивидуальных возможностей, их дальнейшее вовлечение в обсуждение достоинств и недостатков технологии поможет определить, какие же именно факторы имеют решающее значение для ее успеха: внутренние или внешние. И, соответственно, определить, как двигаться дальше — изменять технологию для ее лучшего соответствия потребностям пользователей или же менять способы ее преподнесения, внешнее представление технологии.
5. К. Cameron, R. Quinn. Diagnosing and Changing Organizational Culture Based on The Competing Values Framework. Boston, MA: Addison Wesley, 1998. Русский перевод: К. Камерон, Р. Куинн Диагностика и измерение организационной культуры. Питер, СПб, 2001. 6. P. Albert, M. Bernasconi, L. Gaynor. Incubateurs et pépinières d'entreprises — Un panorama international. L'Harmattan, 2003.
Литература 1. G. W. Matkin. Technology Transfer and the University. Macmillan Publishing Company, NY, 1990. 2. ISO/IEC 9126-1. 2001. Software engineering – Software product quality – Part 1: Quality model. Geneva, Switzerland: International Organization for Standardization. 3. В. В. Липаев. Обеспечение качества программных средств. Методы и стандарты. Синтег, Москва, 2001. 4. В. В. Кулямин, О. Л. Петренко. Место тестирования среди методов оценки качества ПО. Труды ИСП РАН, т. 4, 2003, с. 163-176
23
24
Комбинаторика слов и построение тестовых последовательностей1 В. В. Кулямин
1. Введение При тестировании систем, поведение которых определяется не только последним обращением к ним, а и предшествующей историей работы, т.е. зависит от внутреннего состояния системы, необходимо строить тесты в виде последовательностей обращений, чтобы покрыть возникающие разнообразные ситуации. Если о системе известно немного, например, только список обращений, которые можно делать, построить ее тест в полном смысле этого слова нельзя, поскольку частью теста всегда является проверка правильности работы системы в ответ на тестовые обращения. Однако можно попробовать построить входные данные теста — определить, какие операции, с какими параметрами и когда вызывать, предположив, что проверка правильности работы системы производится отдельно. Ее может выполнять специальный модуль автоматической проверки, или же это может делать человек, понимающий, когда очередная реакция системы правильна, а когда — нет. При этом возникает две задачи: построение значений параметров вызовов и построение последовательности вызовов. Непосредственное решение этих задач путем построения всех возможных комбинаций значений параметров и всех возможных последовательностей обращений сразу приводит к комбинаторному взрыву, поэтому требуются методы построения небольшого числа тестов, которые, тем не менее, были бы достаточно качественны, то есть покрывали бы максимально возможное число различных ситуаций, связанных с поведением тестируемой системы. Первая задача при отсутствии дополнительной информации о тестируемой системе обычно решается на основе разбиений возможных значений параметров на конечное число групп и использования различных комбинаций значений из разных групп. Для построения этих комбинаций можно 1 Данная работа поддержана грантом Российского Фонда содействия отечественной науке.
25
использовать покрывающие массивы (covering arrays) [1—3], которые обеспечивают минимально возможные множества комбинаций, перебирающие все возможные сочетания пар, троек или другого числа значений отдельных параметров. Другие методы основываются на эвристических алгоритмах, вычисляющих приближения к минимальным покрывающим массивам; это оправдано, поскольку построение такого массива с нужными параметрами является NP-полной задачей. Обзоры имеющихся результатов по построению покрывающих массивов см. в [1, 2]. Эта статья целиком посвящена возможным подходам к решению второй задачи — задаче построения тестовых последовательностей, поскольку она в имеющейся литературе практически не затрагивается. В статье не излагается ее полное решение с каких бы то ни было позиций, а, скорее, рассматриваются несколько подходов к такому решению, основанных на похожих идеях, и освещаются известные автору результаты, полученные в рамках этих подходов.
2. Формулировка задачи Будем представлять возможные тестовые последовательности словами в алфавите из возможных вызовов. Число возможных вызовов можно считать конечным — для этого из множества всех возможных вызовов нужно выбрать конечное множество представителей, например, выделив «наиболее интересные» комбинации значений параметров (см. выше). Такому подходу практически нет альтернатив, поскольку тестирование, будучи принципиально конечной процедурой, не может обеспечить качественную проверку работы системы, если имеется бесконечное множество существенно различных способов обратиться к ней. Далее будем рассматривать тестовые последовательности как слова в конечных алфавитах. Поскольку мы не обладаем дополнительной информацией о тестируемой системе, все вызовы (представители) для нас ничем не отличаются друг от друга. Можно обозначить их символами от 0 до (m-1) и рассматривать m-последовательности или m-слова — слова произвольной длины, состоящие только из таких символов. Предположим теперь, что, помимо известного нам числа m различных воздействий на систему, от пользователя мы можем получить только ограничение на суммарную длину теста. Стоящая перед нами задача может быть в итоге cформулирована так: как построить одно m-слово длины, не превосходящей N, имеющее «как можно более разнообразное» поведение? Возможные интерпретации «как можно более разнообразного» поведения рассматриваются ниже. Сразу отметим, что можно пытаться построить несколько m-слов, сумма длин которых не превосходит N, совместно обеспечивающих нужное разнообразие. Такая постановка задачи и возможные подходы к ее решению оставлены за рамками данной статьи. 26
3. Максимизация числа различных подслов Наверное, наиболее очевидный способ интерпретации «как можно более разнообразного поведения» слова — это наличие у него как можно большего числа различных подслов. В слове длины N имеется всего N + (N-1) + (N-2) + … + 2 + 1 = N(N+1)/2 (*) подслов (слагаемые соответствуют подсловам длины 1, 2 и т.д.). Можно попытаться максимизировать количество разных подслов, сделав все подслова какой-то длины k различными. При этом все подслова большей длины тоже автоматически будут различны, т.е. мы получим как минимум (N-k+1)(Nk+2)/2 разных подслов длины k. Значение k при этом можно выбрать так, чтобы максимизировать полученное выражение при фиксированном N, т.е. как можно меньшее. При этом имеется всего mk разных m-слов длины k, а если они все реализуются как подслова одного слова, длина этого слова должна быть не меньше mk+k-1. Предположим, что существуют такие «наиболее плотные» слова, что все их подслова длины k различны и в то же время включают в себя все возможные m-слова длины k, и они нам известны. Тогда для заданного N можно найти минимальное k, такое что N ≤ mk+k-1, т.е. m(k-1)+(k-1)-1 < N ≤ mk+k-1, и в качестве искомого слова взять начало длины N соответствующего «наиболее плотного» слова. Это гарантирует различие всех подслов длины k в нем. Для того, чтобы еще больше увеличить количество разных подслов в нашем слове, можно попытаться найти «наиболее плотное» слово для (k-1), которое продолжается в «наиболее плотное» слово для k. Взяв начало этого последнего слова, мы получим наилучший результат — в полученном слове длины N все подслова минимально возможной длины k различны, и в то же время в нем в качестве подслов содержатся все возможные слова длины (k-1) и, соответственно, меньшие — все слагаемые в формуле (*) имеют максимальные возможные значения. Осталось выяснить два момента.
Существуют ли «наиболее плотные» слова для всех m и k, и если это так, можно ли их продолжать до «наиболее плотных» слов для m и (k+1)? Есть ли достаточно эффективные алгоритмы для построения таких слов?
Какой тестовой гипотезе, т.е. какому допущению относительно свойств тестируемой системы, соответствует выбранное понимание «наиболее разнообразного» поведения слова? Для какого рода систем этот подход дает действительно оптимальное покрытие различных возможных ситуаций? Ответу на первый вопрос посвящен весь следующий раздел. В рамках данного раздела проще ответить на второй вопрос. 27
Такой подход к построению тестов базируется на предположении о том, что поведение тестируемой системы целиком определяется последними k обращениями. При этом, выбирая тестовую последовательность, содержащую как можно больше различных подпоследовательностей длины k, мы покрываем наибольшее количество различных «поведений» системы. В качестве реального примера такой системы можно привести кодовый замок, реагирующий на последние набранные k (обычно 4 или 5) цифр (автор сам пользовался такими замками, см. также статью [4], посвященную стратегии эффективного вскрытия замка в отеле Baltimore Hilton). Дополнительное условие, обеспечивающее содержание в тестовой последовательности всех возможных слов длины (k-1), по отношению к этой гипотезе является некоторой необязательной «оптимизацией».
3.1. Слова де Бройна Итак, что можно сказать про «наиболее плотные» слова для m и k, т.е. m-слова, содержащие в качестве своих подслов длины k все возможные m-слова длины k, причем ровно по одному разу каждое? Такие слова или последовательности известны под именем слов или последовательностей де Бройна (de Bruijn) шага k. Они были известны для случая m = 2 еще в 1894 г. [5], а впоследствии были независимо переоткрыты де Бройном [6] и еще несколькими авторами [7, 8, 9]. В статье [6] де Бройн поставил и решил задачу о том, сколько имеется циклов длины mk (цикл — это класс эквивалентности слов по циклическим перестановкам их символов, например, 0110, 0011 и 1001 относятся к одному циклу), содержащих все k 1
возможные m-слова длины k. Полученный им ответ (m!) m 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, 28
являющимися всеми возможными 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), выпишем слово, 29
соответствующее первому ребру пути, затем для каждого следующего ребра пути будем приписывать в конец полученного слова последний символ этого ребра. Определим для ориентированного графа 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 символов из начала в конец). 30
Эффективный (требующий ограниченного константой «внутреннего» времени на построение одного слова Линдона) алгоритм построения слов Линдона и слов де Бройна на их основе представлен в [20]. Другие алгоритмы можно найти в [21–28]. В работе [29] эмпирически сравнивается эффективность по времени алгоритмов из [20], [25] и [27], и последний алгоритм демонстрирует наиболее высокое быстродействие.
3.2. Продолжение слов де Бройна Нас, однако, интересует еще вопрос возможности продолжения слова де Бройна шага 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) искомых 31
путей соответствуют словам, полученным конкатенацией 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+1+(k+1)-1 > N, строим продолжа-ющееся слово де Бройна шага k и продолжаем его до длины N. Поскольку N ≤ 2k+1+(k+1)-2, мы сможем это сделать, и полученное слово будет иметь максимально возможное число разных подслов — для длин, не превосхо-дящих k, это следует из того, что его начало является словом де Бройна шага k, а для больших длин из того, что все такие подслова в нем различны. Доказательство этого утверждения автору неизвестно, хотя оно кажется истинным. Примеры продолжающихся 2-слов де Бройна шагов 2, 3, 4, 5, 6 приведены в Таблице 1. В этих примерах продолжающееся слово де Бройна отделено точкой от продолжения, которое дополняет его до слова, содержащего все слова длины (k+1), кроме 1k+1. k Продолжающееся 2-слово вместе с продолжением 2 00110.1000 3 0001011100.11010000 4 0000100110101111000.1110110010100000 5 000001010110010001101111101001110000.11110011000100101110110101000000 6 000000101000100101110010000110001110111111010011010101101100111100000. 1111100110010101001001110001011000010001101110101111011010000000
Таб. 1. Продолжающиеся 2-слова де Бройна. 32
Примеры можно искать как гамильтоновы пути в B(2, k+1), начинающиеся в 0k и заканчивающиеся в 10k-1. Предположительно, такой путь всегда можно выбрать так, чтобы он пересекался с каждым циклом графа B(2, k+1) (т.е. имел хотя бы одно общее с циклом ребро), за исключением петель 0k+1 и 1 k+1; тогда он дополняется до «почти эйлерова» пути, который не покрывает только ребро 1 k+1.
4. Универсальные покрывающие последовательности Теперь рассмотрим другой способ интерпретации «как можно более разнообразного» поведения 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 групп таких, что переход по любому стимулу осуществляется из одной группы в другую или в ту же (т.е. отсутствуют такие стимулы, что из некоторой группы состояний переходы по этому стимулу ведут в состояния нескольких разных групп). При этом иногда можно считать, что различия между состояниями в рамках одной группы 33
гораздо меньше, чем между состояниями различных групп, и поэтому, прежде всего, важно протестировать поведение системы относительно разных групп ее состояний. В имеющейся литературе универсальные покрывающие слова практически не упоминаются, в отличие от слов де Бройна. Некоторое количество работ посвящено аналогу универсальных покрывающих слов глубины 0 (т.е. покрывающих все состояния) для неориентированных графов под названием универсальных обходящих последовательностей (universal traversal sequences, введены Куком, S. A. Cook, в конце 70-х годов прошлого века). В центре внимания этих работ находится не собственно построение таких последовательностей, а одна из проблем теории сложности алгоритмов — как соотносятся классы сложности P-log-SPACE детерминированных алгоритмов, требующих полиномиально-логарифмической памяти, и NP-log-SPACE недетерминированных алгоритмов с такими же требованиями к памяти. Дело в том, что задача построения пути между двумя произвольными вершинами графа является примером NP-log-SPACE задачи, а универсальная обходящая последовательность дает для нее детерминированный алгоритм решения. Тем самым, верхние и нижние границы длины универсальных обходящих последовательностей задают правила преобразования сложности NP-logSPACE алгоритма для решения некоторой задачи в сложность 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) шаг делается для 34
того, чтобы покрыть один путь, в каждом состоянии начинается 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.
0
α0
1
α1
2
α2
...
k-2
αk-2
k-1
Рис. 2. Семейство "плохих" графов. В каждом графе этого семейства некоторая выделенная последовательность (k-1) символов приводит из начального состояния в (k-1)-е, а все остальные символы во всех состояниях ведут в начальное состояние. Всякая последовательность символов длины (k-1) встречается в качестве выделенной в одном из графов семейства. Если в слове из UC(m, k, 0) нет какой-то последовательности длины (k-1) в качестве подслова, то состояние (k-1) соответствующего графа не будет покрыто. 35
Для доказательства п. 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) уже 36
отличается от US(m, 2)). Если это так, то можно было бы иметь дело либо только с универсальными покрывающими словами глубины 0 для разных шагов, либо только с универсальными покрывающими словами шага 2 для разных глубин (т.е. искать все универсальные покрывающие слова только на автоматах с двумя состояниями). Последнее свойство выглядит очень сильным, и поэтому есть сомнения в том, что оно выполняется. Все (с точностью до перестановок символов) известные автору универсальные покрывающие слова минимальных длин сведены в Таблице 2 (могут существовать универсальные покрывающие слова меньшей длины с заданными параметрами — те слова, про которые точно известно, что они имеют минимальную возможную длину, помечены звездочкой, в конце остальных слов стоит точка, кроме того, знаками вопроса помечены слова предположительно минимальной возможной длины). m, k, l Слова из UC(m, k, l) 2, 2, 1 001011* 2, 2, 2 01011000100111* 01110110001001* 2, 2, 3 0101011010010000111000111100? 0101011110100100001110001100? 2, 2, 4
2, 2, 5 3, 2, 1
3, 2, 2 4, 2, 1 4, 2, 2
5, 2, 1
0101100010100100001111011100? 0101101010000100111100011100? 0101101010010011110001110000? 00100110001111101101010000010111001010011101011001000010001111011. 00100110001111101101010000010111001010111101100100010011100001101. 00100110001111101101010000010111001010111101100100010011101100001. 00100110001111101101010000010111001010111101100111000010001001101. 0000001000110011111010111001011010011011110001010100000111011000010010100100 0011010111111011001001110001011011001100010001. 0010112022121* 0010112122020* 0010120221211* 0010201220211* 0010202201211* 0010211012022* 0010211020122* 0010212201120* 0010212202011* 0010220201211* 0010221201121* 0010221201211* 0012010220211* 0012022010211* 0012022101121* 0012022121011* 0012102212011* 0012110102022* 0012201121020* 0012202010211* 0012202101120* 0012202102011* 0100201211022* 0100211012022* 0101121220200* 0102122011200* 0120100211022* 0121101002022* 0122011210200* 0122021011200* 010021122022120011101210002010212220110201202210. 010021122022120011101210002011021222020102101120. 0010213123301120301322? 0010213123303013201122? 0012322133011203102130? 0010223212031133033121302011002322003013233210122112313103000211122202032001 001230302333123320210330312121132232310102330102320133. 0010223212031133033121302011002322003013233210122112313103000211122202032001 001230302333123320210330312121132232310102330102322133. 0010223212031133033121302011002322003013233210122112313103000211122202032001 001230302333123320210330312121132232310102330102323133. 01203244134310223042140112330043410.
37
6, 2, 1 7, 2, 1 8, 2, 1
9, 2, 1
00123424551335214043105322025411303441504351201452340. 00123442531554130210452033514322401154350242050312354. 01223454616533041025605143624206311552640321350066244015234651302463154312. 0122344567573616071530420317465264327051147250633762410021355776601454023673 7242360156531340307. 0122344567573616071530420317465264327051147250633762410021355776601454023673 7242360156531340703. 0123456788765432102041375860814253680716247350518263746172857030644831527225 58408776600113387685123806481731357412436704526183238364101781402.
Таб. 2. Минимальные известные универсальные покрывающие слова.
5. Заключение Данная статья представляет два возможных подхода к построению тестовых последовательностей при отсутствии какой-либо информации, кроме числа возможных воздействий на тестируемую систему и длины последовательности, которую хотелось бы получить. Оба подхода приводят к построению слов в конечном алфавите, обладающих специфическими комбинаторными свойствами. Первый подход основывается на предположении, что поведение тестируемой системы определяется фиксированным числом последних оказанных воздействий. Последовательности, построенные на его основе, соответствуют словам де Бройна — самым коротким словам, содержащим в качестве подслов все последовательности определенной длины. Поскольку слова де Бройна имеют помимо тестирования массу других приложений, имеется достаточно много посвященных им работ и различных эффективных алгоритмов их построения. Второй подход предлагает использовать универсальные покрывающие слова, обеспечивающие покрытие путей некоторой длины во всех регулярных сильно связных детерминированных конечных автоматах с фиксированным числом состояний. Несмотря на наглядность предлагаемой идеи, она, по-видимому, до сих пор не рассматривалась, и найти работы, в которых использовалось бы понятие, эквивалентное определенным выше универсальным покрывающим словам, автору не удалось. Такие слова устроены сложнее, чем слова де Бройна, и пока не найдено ни хорошего описания их свойств, ни удобного аппарата для работы с ними, подобного графам де Бройна, ни достаточно эффективных алгоритмов их построения. Все это — задачи для продолжения исследований. Другое возможное направление развития — использование знаний пользователя о системе, которые в реальности чаще всего не нулевые, для построения более эффективных и более коротких тестовых последовательностей. Для начала можно использовать знания о том, что 38
некоторые операции не изменяют состояния системы, некоторые другие, такие, как конструкторы объектов, не зависят от него, впоследствии можно добавить чаще всего известные предусловия операций и т.д. Литература 1. A. Hartman. Software and Hardware Testing Using Combinatorial Covering Suites. Haifa Workshop on Interdisciplinary Applications and Graph Theory, Combinatorics and Algorithms, June 2002. Available at http://www.agedis.de/documents/d435_1/CombinatorialProblemsinSWTestingfinalDraft180703.pdf 2. A. Hartman, L. Raskin. Problems and Algorithms for Covering Arrays. Discrete Mathematics 284:149-156, 2004. Available at http://www.agedis.de/documents/d434_1/ AlgorithmsForCoveringArraysPublication191203.pdf 3. C. J. Colbourn. Combinatorial aspects of covering arrays. In Proc. of Combinatorics 2004, Capomulini, Italy, September 2004. To appear in Le Matematiche (Catania). Available at http://www.dmi.unict.it/combinatorics04/documenti%20pdf/colbourn.pdf 4. H. Fredricksen and J. Maiorana. The Baltimore Hilton Problem. Technology Review, v. 83, no. 7, June 1980. 5. F.-S. Marie. Solution to problem number 58. L’Intermédiaire des Mathématiciens, 1 (1894), 107–110. 6. N. G. de Bruijn. A Combinatorial Problem. Koninklijke Nederlandse Akademie van Wetenschappen 49, 758-764, 1946. 7. M. H. Martin. A problem in arrangements. Bulletin of American Mathematical Society, 40:859–864, 1934. 8. I. J. Good. Normally recurring decimals. J. London Math. Soc. 21:167–169, 1946. 9. D. Rees. Note on a paper by I. J. Good. The Journal of the London Mathematical Society, 21:169–172, 1946. 10. Д. Кнут. Исскуство программирования. Том 1. Основные алгоритмы. Вильямс, 2002. 11. H. Fredricksen. A survey of full length nonlinear shift register cycle algorithm. SIAM Review, 24(2):195–221, 1982. 12. F. J. MacWilliams and N. J. A. Sloane. Pseudo-random sequences and arrays. Proc. IEEE 64:1715–1729, 1976. 13. C. D. Savage. A survey of combinatorial Gray codes. SIAM Review, 39(4):605–629, 1997. 14. Fan Chung and J. N. Cooper. De Bruijn Cycles for Covering Codes. 2003. Available at http://arxiv.org/abs/math/0310385 15. L. Zhang, B. Curless, S. M. Seitz. Rapid shape acquisition using color structured light and multi-pass dynamic programming. In Int. Symposium on 3D Data Processing Visualization and Transmission, pages 24–36, Padova, Italy, June 2002. 16. J. Pagès and J. Salvi. A new optimised De Bruijn coding strategy for structured light patterns. 17th International Conference on Pattern Recognition, ICPR 2004, Cambridge, UK, 23–26, August 2004. 17. S. W. Golomb. Shift Register Sequences. Aegean Park Press, Laguna Hills, CA, USA, rev. ed., 1981. 18. A. Lempel. On a homomorphism of the de Bruijn graph and its applications to the design of feedback shift registers. IEEE Transactions on Computers, C-19:1204–1209, 1970.
39
19. H. Robinson. Graph Theory Techniques in Model-Based Testing. 1999 International Conference on Testing Computer Software, 1999. Available at http://www.geocities.com/harry_robinson_testing/graph_theory.htm 20. H. Fredricksen, J. Maiorana. Necklaces of beads in k colors and k-ary de Bruijn sequences. Discrete Math., 23:207–210, 1978. 21. T. Etzion, A. Lempel. Algorithms for the generation of full-length shift-register sequences. IEEE Transactions on Information Theory, 30:480–484, 1984. 22. T. Etzion. An algorithm for constructing m-ary de Bruijn sequences. Journal of Algorithms, 7:331–340, 1986. 23. R. A. Games. A generalized recursive construction for de Bruijn sequences. IEEE Transactions on Information Theory, 29:843–850, 1983. 24. C. J. A. Jansen, W. G. Franx, D. E. Boekee. An efficient algorithm for the generation of DeBruijn cycles. IEEE Transactions on Information Theory, 37:1475–1478, 1991. 25. A. Ralston. A new memoryless algorithm for de Bruijn sequences. Journal of Algorithms, 2:50–62, 1981. 26. E. Roth. Permutations arranged around a circle. The American Mathematical Monthly, 78:990–992, 1971. 27. S. Xie. Notes on de Bruijn Sequences. Discrete Mathematics, 16:157–177, 1987. 28. F. S. Annexstein. Generating de Bruijn Sequences: an Efficietn Implementation. IEEE Transactions on Computers, 46(2):198–200, 1997. 29. M. Vassallo, A. Ralston. Algorithms for de Bruijn sequences — a case study in the empirical analysis of algorithms. The Computer Journal, 35:88–90, 1992. 30. M. J. O’Brien. De Bruijn graphs and the Ehrenfeucht-Mycielski sequence. Master’s thesis, Mathematical Sciences Department, Carnegie Mellon University, 2001. 31. A. Iványi. On the d-complexity of words. Ann. Univ. Sci. Budapest. Sect. Comput. 8, 6990, 1987. 32. A. Flaxman, A. W. Harrow, G. B. Sorkin. Strings with maximally many distinct subsequences and substrings. Electronic Journal of Combinatorics 11(1), 2004. Available at http://www.combinatorics.org/Volume_11/PDF/v11i1r8.pdf 33. R. Aleliunas, R. M. Karp, R. J. Lipton, L. Lovász, C. W. Rackoff. Random walks, universal traversal sequences, and the complexity of maze problems. In Proc. of 20-th Annual Symposium on Foundations of Computer Science, San Juan, Puerto Rico, October 1979, pp. 218–223.
40
Автоматическая генерация позитивных и негативных тестов для тестирования фазы синтаксического анализа С.В. Зеленов, С.А. Зеленова Аннотация. В статье описывается методика автоматической генерации наборов позитивных и негативных тестов для тестирования фазы синтаксического анализа. Предлагаются критерии покрытия для таких наборов, основанные на модельном подходе к тестированию, и методы генерации наборов тестов, удовлетворяющих предложенным критериям. Также приводятся результаты практического применения описанной методики к тестированию синтаксических анализаторов различных языков, в том числе языков C и Java.
1. Введение Компилятор является инструментом, требования к надежности которого чрезвычайно высоки. И это неудивительно, ведь от правильности работы компилятора зависит правильность работы всех скомпилированных им программ. Из-за сложности входных данных и преобразований задача тестирования компиляторов является весьма трудоемкой и непростой. Поэтому вопрос автоматизации всех фаз тестирования (создания тестов, их прогона, оценки полученных результатов) стоит здесь особенно остро. Синтаксический анализ является частью функциональности любого компилятора. От корректности синтаксического анализа зависит корректность практически всей остальной функциональности – проверки семантических ограничений, оптимизирующих преобразований, генерации кода. Поэтому решение задачи тестирования синтаксических анализаторов является базой для решения задач тестирования всех остальных компонент компилятора. Для очень многих языков программирования существует формальное описание синтаксиса – описание грамматики языка в форме BNF, а для тех языков, для которых существуют только эталонные компиляторы (например, COBOL), делаются активные попытки построить такое описание (см. [9, 10, 14]). BNF языка является одновременно и спецификацией функциональности синтаксического анализа, таким образом, в этой области наиболее привлекательным является тестирование на основе спецификаций (см. [17]). Существование формального описания позволяет автоматизировать процесс 41
построения тестов, что существенно снижает трудозатраты, а систематичность тестирования повышает доверие к его результатам. Построением тестов по грамматике занимались многие авторы. Основополагающей работой в этой области является работа [18], в которой сформулирован следующий критерий покрытия для множества позитивных тестов: для каждого правила в данной грамматике в множестве тестов должно присутствовать предложение языка, в выводе которого используется это правило. В той же работе Пардом предложил метод построения минимального тестового набора, удовлетворяющего этому критерию. Однако указанный критерий оказался недостаточным. Ламмель в работе [9] показал, что тестовые наборы, построенные алгоритмом Пардома, не обнаруживают простейших ошибок. Ламмель также предложил более сильный критерий покрытия, состоящий в том, что покрывается каждая пара правил, одно из которых можно применить непосредственно после другого. Предлагаемые другими авторами методы являются вероятностными (см. [7, 13, 11, 12]) и не описывают критериев покрытия, и потому для них возникает вопрос остановки генерации тестов, который решается, например, с помощью введения вероятностей появления правил и уменьшения этих вероятностей при каждом новом появлении правила в выводе. В любом случае завершение работы алгоритма за конечное время является проблемой. Кроме того, произвольность остановки генерации нарушает систематичность тестирования. Все приведенные выше работы касаются генерации позитивных тестов для синтаксического анализатора (т.е. тестов, являющихся предложениями целевого языка). В настоящее время работы, предлагающие методы генерации негативных тестов для синтаксических анализаторов (т.е. тестов, не принадлежащих целевому языку), практически отсутствуют. Однако такие тесты также важны, поскольку пропуск неверной последовательности лексем на этапе синтаксического анализа может привести к аварийному завершению компиляции. В работе [8] высказано предположение, что если имеется генератор предложений языка из грамматики (генератор позитивных тестов для синтаксического анализатора), то для генерации негативных тестов для синтаксического анализатора можно использовать метод мутационного тестирования (mutation testing)1 (см. [6, 15]). Идея состоит в том, что в 1
В общих словах мутационное тестирование состоит в следующем. Из тестируемого компонента получают множество его модификаций (мутантов), каждая из которых содержит ровно одну ошибку. Говорят, что мутант убит, если на некоторых входных данных его выход отличен от выхода исходной программы. Если имеется множество тестовых входных данных для программы, то с помощью мутационного анализа (mutation analysis) можно оценить качество этих тестов. Именно, если имеется множество мутантов, то критерий покрытия говорит, что все мутанты должны быть убиты. В случае синтаксического анализатора логично в качестве мутируемого материала рассматривать грамматику языка, т.к. ошибочный анализатор фактически распознает другой язык. В этом случае грамматика-мутант будет убита, если найдется последовательность токенов, принадлежащая языку, задаваемому этой грамматикой-мутантом и не принадлежащая (продолжение на следующей стр.)
42
исходную грамматику вносятся изменения (мутации) для получения грамматик, описывающих близкие, но не эквивалентные исходному языки. Эти мутированные грамматики подаются на вход генератору тестов для получения потенциально негативных тестов. Общие проблемы данного подхода состоят в следующем:
Грамматика-мутант может оказаться эквивалентной исходной грамматике. Такие мутанты должны быть выявлены и не должны использоваться для генерации тестов. Даже если грамматика-мутант не эквивалентна исходной, полученные из нее тесты могут оказаться правильными. Выявить эти тесты можно лишь прогнав их через эталонный синтаксический анализатор, которого может и не быть (например, в случае создания нового или расширения существующего языка).
В настоящей работе описаны критерии покрытия, нацеленные на алгоритмы синтаксического анализа. Такой подход представляется оправданным, поскольку тестовые наборы строятся для тестирования синтаксических анализаторов, и эффективность тестового набора должна оцениваться исходя из характеристик, относящихся к тестируемым компонентам (т.е. синтаксическим анализаторам), таких как, например, покрытие функциональности или кода. Данная методика разработана в рамках общего модельного подхода к тестированию компиляторов (см. [2, 3, 4]). Мы рассматриваем известные алгоритмы синтаксического анализа в качестве алгоритмов, моделирующих поведение синтаксического анализатора. Как уже говорилось, в литературе практически отсутствуют работы, посвященные генерации негативных тестов. Настоящая работа призвана закрыть этот пробел. Статья состоит из введения и трех разделов. В первом разделе содержатся сведения из теории алгоритмов синтаксического анализа. Второй раздел посвящен описанию предлагаемой методики. В нем вводятся понятия позитивных и негативных тестов, описываются критерии покрытия для тестовых наборов, опирающиеся на алгоритмы синтаксического анализа, а также приводятся алгоритмы построения наборов, удовлетворяющих этим критериям покрытия. В третьем разделе описаны результаты практического применения методики.
2. Предварительные сведения В этом разделе мы приводим некоторые сведения из теории синтаксического анализа. Более подробное изложение приведенных фактов можно найти в известной книге А. Ахо, Р. Сети и Д. Ульмана (см. [5]). исходному языку (в терминах анализаторов это как раз будет означать, что анализатор-мутант распознал данное предложение, а исходный анализатор - нет, т.е. анализатор-мутант оказался убитым).
43
Грамматика формального языка задается четверкой G = (T,N,P,S), где
T – множество терминальных символов или токенов;
N – множество нетерминальных символов;
P – список правил грамматики; S – стартовый символ грамматики. Множество предложений формального языка, задаваемого грамматикой G, будем обозначать G. Дадим несколько определений. Расширением грамматики G (или просто расширенной грамматикой) называется грамматика G' = (T,N',P',S'), где S' – новый нетерминальный символ, а к множеству правил добавлено правило S' → S. Сентенциальной формой будем называть последовательность грамматических символов – нетерминалов и токенов. Далее, греческими буквами из начала алфавита (α, β, γ, δ, ...) мы будем обозначать какие-либо сентенциальные формы. Пустую сентенциальную форму будем обозначать через ε. Правосентенциальной формой называется сентенциальная форма, для которой существует правый вывод из стартового правила. Пример. Рассмотрим следующую грамматику: S → AB A → cd B → eCf C → Ae В ней сентенциальная форма cdB не имеет правого вывода, т.е. не может быть получена с помощью последовательного раскрытия самых правых нетерминалов. Примером правосентенциальной формы может служить форма AeCf. ► Основой правосентенциальной формы называется подпоследовательность ее символов, которая может быть свернута в некоторый нетерминал, такая, что сентенциальная форма, полученная из исходной после свертки, может быть свернута в стартовый символ. Пример. Пусть грамматика та же, что и в предыдущем примере. Форма AeCf, как мы уже заметили, является правосентенциальной. В ней есть две подпоследовательности символов Ae и eCf, которые могут быть свернуты в нетерминалы C и B соответственно. Однако основой является только последовательность eCf, так как сентенциальная форма CCf невыводима из стартового символа. ► Активным префиксом правосентенциальной формы называется префикс, не выходящий за границы самой правой основы этой формы. 44
Пунктом грамматики G называется правило вывода с точкой в некоторой позиции правой части. Множество всех пунктов грамматики G будем обозначать через . Пример. Правило вывода A → XYZ дает 4 пункта: A → •XYZ, A → X•YZ, A → XY•Z и A → XYZ•. ► Базисным называется пункт с точкой не у левого края правой части правила, а также пункт S' → •S в расширенной грамматике. Замыканием множества пунктов I (обозначается closure(I)) называется наименьшее множество пунктов, содержащее I в качестве подмножества такое, что для всякого пункта A → α•Bβ ∈ I и любого правила B → γ пункт B → •γ лежит в closure(I). Для пары (I,X), где I некоторое множество пунктов грамматики G, а X – символ грамматики (терминал или нетерминал), определим функцию goto(I,X) – замыкание множества всех пунктов A → αX•β таких, что A → α•Xβ ∈ I. Рассмотрим расширенную грамматику G' = (T,N',P',S'), где N' = N∪{S'}, P = P∪{S' → S}. Пусть I0 = closure({S' → •S}). Начиная с I0, строится система множеств пунктов I0,...,IN так, что для всякой пары (Ik,X), где k = 0,...,N и X – символ грамматики, существует индекс j = 0,...,N такой, что goto(Ik,X) = Ij. Эта система пунктов называется канонической системой множеств пунктов. Используя каноническую систему I0,...,IN, можно построить конечный автомат V, распознающий активные префиксы, если в качестве состояний sj взять канонические множества Ij, а переходы задать с помощью функции goto. Широко известны два класса алгоритмов синтаксического анализа: LL-анализ и LR-анализ (см. [5]). LL-анализатор с помощью диаграммы переходов или таблицы разбора строит левый вывод предложения целевого языка. Нерекурсивная реализация LL-анализатора использует стек и таблицу разбора. Изначально в стеке находится символ конца строки $ и стартовый символ грамматики. На каждом шаге рассматривается символ на вершине стека X и текущий входной символ a. Действия анализатора определяются этими двумя символами: если X = a = $, то анализатор прекращает работу и сообщает об успешном завершении разбора; если X = a ≠ $, анализатор удаляет из стека символ X и переходит к следующему символу входного потока; если X является нетерминалом, анализатор ищет такую альтернативу раскрытия символа X, для которой символ a является допустимым первым символом. После того, как требуемая альтернатива найдена, символ X в стеке заменяется обратной последовательностью символов альтернативы. Например, если искомая альтернатива X → ABC, то анализатор заменит X на вершине стека на последовательность CBA, т.е. на вершине стека 45
окажется символ A. Конфликты, возникающие в процессе поиска альтернатив, могут разрешаться, например, с помощью “заглядывания вперед”, т.е. просмотра нескольких входных символов вместо одного. Анализатор завершает работу, когда на вершине стека оказывается символ конца строки $. Рассмотрим теперь LR-анализатор, построенный на основе стека. У такого LRанализатора имеются две основные операции: перенос символа из входного потока в стек; свертка нескольких последовательных символов на вершине стека в некоторый нетерминал. Работа анализатора происходит так, что в стеке все время находится активный префикс некоторой правосентенциальной формы. При переносе символа и свертке на вершину стека кладется символ состояния sj конечного автомата V, кодирующий текущий активный префикс. LR-анализатор принимает решение о переносе или свертке, исходя из пары (символ sj, текущий токен входного потока). Анализатор завершает работу, когда в стеке оказывается стартовый символ грамматики.
3. Описание методики 3.1. Позитивные и негативные тесты для синтаксического анализатора В данной работе парсером мы называем булевскую функцию, заданную на множестве последовательностей токенов и принимающую значение “истина”, если последовательность является предложением данного формального языка, и “ложь” – иначе. Конечно, реальные парсеры могут иметь дополнительную функциональность (например, помимо булевского значения выдавать дерево разбора или идентификацию ошибки), но здесь мы такую функциональность не рассматриваем. Позитивный тест для парсера – это последовательность токенов, на которой парсер выдает вердикт “истина”, т.е. последовательность токенов, являющаяся предложением целевого языка. Негативный тест для парсера – это последовательность токенов, на которой парсер выдает вердикт “ложь”, т.е. последовательность токенов, не являющаяся предложением целевого языка. Для построения какого-нибудь позитивного теста достаточно, следуя правилам грамматики и ограничив рекурсию правил, вывести из стартового символа некоторую сентенциальную форму, состоящую из одних токенов. При построении же негативных тестов возникают два вопроса: насколько произвольна должна быть соответствующая последовательность токенов, и как добиться того, чтобы она действительно не принадлежала целевому языку. 46
Сначала ответим на второй вопрос: как получить последовательность токенов, гарантированно не принадлежащую множеству предложений целевого языка. Рассмотрим грамматику 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. Итак, мы научились получать последовательности токенов, заведомо не являющиеся предложениями целевого языка. К вопросу о произвольности негативной последовательности токенов мы вернемся в следующем параграфе. 47
3.2. Критерии покрытия Как видно из описания 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-анализатор, обрабатывая эту последовательность, 48
получит ситуацию, когда на вершине стека будет находиться символ 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 не пересекаются. Заметим, что для такой грамматики покрытие всех пар (состояние конечного автомата, переход из этого состояния в другое)
2
Существует связь между предлагаемым подходом и методом мутационного тестирования. Именно, для любой последовательности токенов, являющейся негативным тестом в описанном выше смысле (т.е. предложением языка, “испорченным” с помощью вставки/замены “нехорошего” токена) можно построить грамматику-мутант такую, что данный негативный тест будет являться предложением языка, описываемого этой грамматикой-мутантом. Одним из принципов мутационного тестирования является так называемый эффект взаимосвязи (coupling effect): при обнаружении простых ошибок будут обнаруживаться и более сложные (см. [16]). Согласно этому эффекту взаимосвязи, мутации должны быть простыми. Заметим, что предлагаемый подход вполне согласуется с этим принципом.
49
достигается при покрытии всех пунктов грамматики. Рассмотрим следующий критерий покрытия для наборов позитивных тестов: (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
3.3. Тестовые наборы В данном разделе мы опишем способы генерации тестовых наборов, удовлетворяющих некоторым из описанных выше критериев покрытия.
3.3.1. Позитивные тесты Пусть B – нетерминал, A – произвольный грамматический символ. Множество всех вхождений (p,i) символа A в грамматику, таких что p = B → αAβ, обозначим через UAB. Упорядочим и перенумеруем элементы множества UAB, j-й элемент этого множества обозначим через A(B,j). Пример. Если нетерминал B определяется тремя правилами 50
Из теоремы 2 следует, что приведенный алгоритм завершит работу построением искомой цепочки. Заметим теперь, что, имея цепочку A ≺ B1 ≺
то для символа A имеем три вхождения: A(B,1) из первого правила (B → A1DE), A(B,2) и A(B,3) из второго правила (B → CA2DA3). В третьем правиле вхождений символа A нет. ► Введем следующее обозначение: будем писать A ≺ B, если A входит в правую часть какого-либо правила, определяющего нетерминал B. Теорема 2. Для любого грамматического символа A, выводимого из стартового
≺ Bk ≺ S, можно получить из нее несколько выводов сентенциальных форм, если конкретизировать соответствующие вхождения. Каждая из этих сентенциальных форм будет иметь вид αAβ, где α, β – некоторые последовательности грамматических символов. Определим функцию first(A), возвращающую множество раскрытий символа A: Если A – токен, то first(A) = {A}. Если A – нетерминал, то
символа, существует цепочка A ≺ B1 ≺…≺ Bk ≺ S, где S – стартовый символ грамматики, а Bi – нетерминалы, такая, что все нетерминалы, входящие в эту цепочку, различны. Доказательство. То, что символ A выводим из стартового символа, означает,
Если α = A1...An – сентенциальная форма, то пусть
что для символа A существует какая-то цепочка A ≺ B1 ≺…≺ Bk ≺ S. Пусть это
Тогда, если A1 – грамматический символ, не имеющий пустого раскрытия, то first(α) = CA, а если A1 имеет пустое раскрытие, то
минимальная по длине такая цепочка. Пусть теперь в цепочке Bi ≺…≺ Bk ≺ S
нет повторяющихся нетерминалов, а в цепочке Bi-1 ≺…≺ Bk ≺ S – есть, т.е.
Bi-1 = Bl для некоторого l = i,...,k. Но тогда мы можем заменить цепочку A ≺ B1
≺…≺ Bk ≺ S на цепочку A ≺ B1 ≺…≺ Bi-2 ≺ Bl ≺…≺ Bk ≺ S, меньшую по длине, что противоречит минимальности первоначальной цепочки. ► Доказанная теорема показывает, как можно строить наборы тестов, удовлетворяющие критерию (PLL). Действительно, эта теорема дает следующий способ построения цепочки A ≺ B1 ≺…≺ Bk ≺ S: Для символа A строим множество NA нетерминалов, для которых существует определяющее правило, содержащее A в правой части. Если S ∈ NA, то искомая цепочка построена. Для каждого элемента B1 множества NA строим множества N A,B1 нетерминалов B2 ≠ B1, для которых существует определяющее правило, содержащее B1 в правой части. Если S N A,B1 , то искомая цепочка построена. ... Для каждого элемента Bs-1 множества N A, B1 ,..., BS 2 строим множества N A, B1 ,..., BS 1 нетерминалов Bs∉{B1,...,Bs-1}, для которых существует опреде-
ляющее правило, содержащее Bs-1 в правой части. Если S N A, B1 ,..., BS 2 , то
first(α) = CA ∪ first(A2...An). При этом рекурсия при построении first(A) обрывается так: если при построении first(A) необходимо это же множество, то его считают пустым. Теорема 3. Множество first(A) включает раскрытия A со всевозможными первыми токенами для A. Доказательство. Следует из построения. ► Имея выведенную из стартового символа сентенциальную форму αAβ и множество first(A), мы можем построить множество сентенциальных форм αγβ, где γ ∈ first(A). Для каждой такой сентенциальной формы выберем какуюнибудь последовательность токенов, выводимую из этой сентенциальной формы. Множество выбранных последовательностей обозначим через A. Теорема 4. Объединение множеств A для всех нетерминалов A является множеством позитивных тестов, удовлетворяющим критерию (PLL). Доказательство. Следует из построения и из теоремы 3. ► Здесь же можно указать способ построения набора позитивных тестов, удовлетворяющего критериям WPLR и PLR. Пусть B – нетерминал из грамматики G, обозначим через Dk(B) множество сентенциальных форм вида αBβ, выводимых из стартового правила, в цепочке вывода которых каждое правило встречается не более чем k раз. Пусть π = B → λ•Xμ – пункт грамматики G. Положим
искомая цепочка построена. ... 51
52
Пусть ζ = αBβ ∈ Dk(B). Для каждой сентенциальной формы αλγμβ, где λγμ ∈ first(π), выберем какую-нибудь последовательность токенов, выводимую из этой сентенциальной формы. Множество выбранных последовательностей обозначим через , . Очевидно, что всякая последовательность токенов из является предложением целевого языка, а значит – позитивным тестом для , парсера. Теорема 5. Пусть для каждого нетерминала B ∈ N грамматики G фиксирована
сентенциальная форма ζB ∈ Dk(B). Тогда множество позитивных тестов:
а операции мутации определяются как и ранее: mut1(t1...tn,0) = {t't1...tn} и mut2(t1...tn,0) = {t't2...tn}. Из теоремы 1 следует, что обе операции мутации дают негативные тесты. Перейдем к описанию способа получения множества предложений с отмеченными токенами, позволяющего строить множества тестов, удовлетворяющие критериям (NLL1) и (NLR1). Пусть π = D → B1...Bi•Bi+1...Bn – некоторый пункт грамматики G. Рассмотрим следующий алгоритм построения множества предложений с отмеченными токенами. Пусть Mπ – пустое множество.
удовлетворяет критерию (WPLR); здесь , как и раньше, обозначает множество всех пунктов грамматики G. Доказательство. Следует из теоремы 3. ► Теорема 6. Начиная с некоторого k, множество позитивных тестов
удовлетворяет критерию (PLR). Доказательство. Поскольку при стремлении k к бесконечности множество Dk(B) стремится к множеству всех сентенциальных форм вида αBβ, то, начиная с некоторого k, все возможные состояния автомата, определяющего активные префиксы, будут покрыты. Так как мы берем объединение по всем пунктам, то будут покрыты и все пары (si,X), где si – символ состояния конечного автомата, X – символ грамматики. ►
3.3.2. Негативные тесты Ранее мы уже заметили, что наиболее естественный способ получения негативного теста – мутация некоторого предложения целевого языка, т.е. замена в этом предложении некоторого участка (возможно пустого) на “неправильную” последовательность токенов. Пусть α – предложение языка, и пусть в нем отмечен токен с порядковым номером i. Мутацией 1-го рода (обозначается mut1(α,i)) мы будем называть замену предложения α = t1...tn на множество последовательностей токенов вида t1...tit'ti+1...tn, где t'∈ ti. При мутации 1-го рода в предложение вставляются “неправильные” токены. Мутацией 2-го рода (обозначается mut2(α,i)) будем называть замену предложения α = t1...tn на множество последовательностей токенов вида t1...tit'ti+2...tn, где t'∈ ti. При мутации 2-го рода в предложении один из токенов заменяется на “неправильный” токен. Кроме того, можно определить мутации 1-го и 2-го рода при i = 0. В этом случае t' принадлежит множеству недопустимых первых символов, т.е. 53
Построим сентенциальную форму αDβ S, выводимую из стартового символа (как это сделать, обсуждалось выше). Пусть γ – непустая последовательность из множества first(Bi+1...Bn). Добавим в Mπ все сентенциальные формы вида αγβ, в которых отмечен первый символ последовательности γ. Нетрудно видеть, что этот символ является токеном (это следует из построения множества first(...)). Если последовательность Bi+1...Bn может быть раскрыта в пустую последовательность токенов, то делаем шаг 3, иначе Mπ вычислено. Для каждого пункта π' такого, что π' = X → λD•μ, добавим в Mπ все сентенциальные формы из множества Mπ'. При этом рекурсия при построении Mπ обрывается так: если при построении Mπ необходимо само это множество, его считают пустым. Рассмотрим грамматику , полученную из грамматики G следующим образом. Множества токенов и нетерминалов, а также стартовый символ в грамматиках и G совпадают. Если в грамматике G присутствует правило вывода A → B1...Bn, то в грамматике присутствует правило A → Bn...B1, здесь A – нетерминал, а Bi – грамматические символы. Других правил в грамматике нет. Очевидно, что грамматика задает язык, каждое предложение которого – это некоторое предложение языка, задаваемого грамматикой G, написанное “задом наперед”. Возьмем пункт = D → Bn...Bi+1•Bi...B1 в грамматике , соответствующий пункту π в грамматике G. Вычислим M в грамматике . Пусть Nπ = {(X1...Xn, i) | (Xn...X1, i) ∈ M }. Каждую сентенциальную форму из множества Nπ можно раскрыть в одно или несколько предложений целевого языка с отмеченным токеном, если как-нибудь раскрыть все входящие в эту сентенциальную форму нетерминалы. Обозначим полученное множество помеченных предложений через π. К нему можно применить ранее определенные операции мутации 1-го и 2-го рода. 54
Теорема 7. Пусть A – нетерминал и – множество пунктов A D → B1...Bi•Bi+1...Bn таких, что Bi+1 = A. Тогда множества негативных тестов
префиксы, будут покрыты. Таким образом, будут покрыты и все пары (si,t'), где si – символ состояния конечного автомата, t' – негативный токен. ►
4. Практические результаты. Для апробации предложенной методики нами были построены следующие генераторы:
удовлетворяют критерию (NLL1). Доказательство. Действительно, пусть существует вывод S αtAβ. Нетерминал A получается на некотором шаге этого вывода, т.е.
множества позитивных Генератор GP удовлетворяющий критерию PLL.
где p = D → B1...BiABi+2...Bn. По условию, для пункта = D → Bn...Bi+2A•Bi...B1 из грамматики было построено множество M . Отсюда ясно, что, если в M существует последовательность вида λAtμ, то ситуация (A,t'), где t'∈ t, покрыта. Заметим, что исходный вывод предложения αtAβ может быть преобразован в вывод в грамматике : Если последовательность Bi...B1 не имеет пустого раскрытия, то существует такая последовательность из first(Bi...B1), что t является ее первым токеном, т.е. в множестве M имеется сентенциальная форма λAtμ, что и требовалось. Случай, когда Bi...B1 может быть раскрыта в пустую последовательность, разбирается аналогично. ► Теорема 8. Множества негативных тестов
определенные в теореме 7, удовлетворяют критерию (WPLR1). Доказательство. Аналогично доказательству теоремы 7. ► Пусть π = D → B1...Bi•Bi+1...Bn – пункт грамматики G. Заметим, что на втором этапе построения Mπ мы выбирали сентенциальную форму S αDβ, так что в действительности построенное Mπ, а значит и π, зависят от этой сентенциальной формы. Пусть Dk(D) – множество сентенциальных форм вида αDβ, в цепочке вывода которых каждое правило встречается не более k раз. Теорема 9. Начиная с некоторого k, множества негативных тестов
удовлетворяют критерию (NLR1). Доказательство. Поскольку при стремлении k к бесконечности множество Dk(D) стремится к множеству всех сентенциальных форм вида αDβ, то, начиная с некоторого k, все возможные состояния автомата, определяющего активные 55
тестов
для
парсера,
множества негативных тестов для парсера, Генератор GN удовлетворяющий критериям WNLR1 и NLL1. Оба генератора получают на вход BNF целевого языка. У генератора GP имеются параметры, позволяющие регулировать количество тестов, а у генератора GN – параметр, позволяющий указывать способ мутации. Кроме того, генератор GN генерирует документированные тесты, т.е. в каждом тесте имеется комментарий о том, в каком месте следует ожидать ошибку и какого рода эта ошибка. Таким образом, если парсер имеет дополнительную функциональность – выдачу сообщения об ошибке при ее обнаружении, то можно протестировать и эту функциональность, если сравнить ожидаемые и полученные данные об ошибке. Генератором GP были сгенерированы наборы тестов для BNF следующих языков: C – расширение ANSI C, для которого реализован известный компилятор gcc (см. [19]); mpC – расширение ANSI C, разработанное для программирования параллельных вычислений (см. [23]); Java (версия 1.4); J@va – спецификационное расширение языка Java, разработанное в ИСП РАН (см. [21]). Некоторые данные об использовавшихся грамматиках приведены в таблице: Характеристики Количество нетерминалов Количество токенов Минимальная мощность Максимальная мощность Средняя мощность t
t t
C 70 93 1 93 68
Java 135 101 19 101 79
J@va 174 140 34 140 119
Генератором GN были сгенерированы наборы тестов для BNF трех указанных языков (исключая mpC). Количественные характеристики сгенерированных тестовых наборов приводятся в таблицах: 56
GP GN
C 137307 43448
C 19 m 30 s 3 m 01 s
J@va 219221 145758
Java 19 m 51 s 7 m 14 s
J@va 43 m 08 s 19 m 25 s
Средний размер теста в байтах
Генератор GP GN
Java 137148 71943 Время генерации
Генератор GP GN
Литература
Количество тестов
Генератор
C 58 219
Java 68 220
J@va 112 266
Позитивные тесты для Java и J@va были прогнаны через парсер языка J@va, разработанный в ИСП РАН (см. [21]). Указанный парсер был разработан при помощи JavaCC (см. [20]). В результате прогона тестов было обнаружено восемь ошибок. Позитивные тесты для языка mpC обнаружили 12 ошибок в компиляторе этого языка. Позитивные тесты для C были прогнаны на широко используемом компиляторе gcc (см. [19]). При этом на 112 тестах gcc зациклился. Негативные тесты для C были прогнаны на парсере языка С, разрабатываемом в ИСП РАН (см. [22]), и обнаружили две ошибки.
5. Заключение На основе модельного подхода к тестированию были разработаны критерии покрытия для наборов позитивных и негативных тестов для парсеров. Одно из достоинств предложенного в статье метода построения негативных тестов для синтаксического анализатора – гарантия негативности построенных тестов. Это позволяет отказаться от использования каких бы то ни было эталонных синтаксических анализаторов. Были разработаны методы генерации наборов тестов, удовлетворяющих предложенным критериям. С помощью этих методов были построены генераторы позитивных и негативных тестовых наборов. Сгенерированные для разных языков тестовые наборы были использованы для тестирования ряда парсеров и компиляторов и выявили в тестируемых компонентах ошибки. Таким образом, практические результаты применения данной методики показывают адекватность предложенных критериев покрытия, а также целесообразность использования предложенной методики для тестирования промышленных компиляторов. 57
1. А. В. Демаков, С. В. Зеленов, С. А. Зеленова. Тестирование парсеров текстов на формальных языках // Программные системы и инструменты (тематический сборник факультета ВМиК МГУ). – 2001. – № 2. – p. 150-156. 2. С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко. Генерация тестов для компиляторов и других текстовых процессоров // Программирование, Москва. – 2003. – 29. – № 2. – с. 59-69. 3. С.В. Зеленов, С.А. Зеленова, А.С. Косачев, А.К. Петренко. Применение модельного подхода для автоматического тестирования оптимизирующих компиляторов // http://www.citforum.ru/SE/testing/compilers/ 4. А.К. Петренко и др. Тестирование компиляторов на основе формальной модели языка // Препринт института прикладной математики им. М.В Келдыша. 1992. №45. 5. A .Aho, R. Sethi, J. D. Ullman. Compilers. Principles, Techniques, and Tools // AddisonWesley Publishing Company, Inc. – 1985. 6. R. A. DeMillo, A. J. Offut. Constraint-Based Automatic Test Data Generation // IEEE Transactions on Software Engineering. – 1991. – 17. – № 9. – p. 900-910. 7. R. F. Guilmette. TGGS: A flexible system for generating efficient test case generators. 1999. 8. J. Harm, R. Lämmel. Two-dimensional Approximation Coverage // Informatica Journal. – 2000. – 24. – № 3. 9. R. Lämmel. Grammar testing // In Proc. of Fundamental Approaches Software Engineering. – 2001. – 2029. – p. 201-216. 10. R. Lämmel, C. Verhoef. Cracking the 500-Language Problem // IEEE Software. – 2001. – 18. – № 6. – p. 78-88. 11. P. M. Maurer. Generating test data with enhanced context-free grammars // IEEE Software. – 1990. – p. 50-55. 12. P. M. Maurer. The design and implementation of a grammar-based data generator // Software Practice and Experience. – 1992. – 22(3). – p. 223-244. 13. W. McKeeman. Differential testing for software. // Digital Technical Journal. – 1998. – 10(1). – p. 100-107. 14. M. Mernik, G. Gerlic, V. Zumer, B. R. Bryant. Can a parser be generated from examples? // Symposium on Applied Computing, Proceedings of the 2003 ACM symposium on Applied computing, Session: Programming languages and object technologies, ISBN:1-58113-624-2 – 2003. – p. 1063-1067. 15. A. J. Offut, S. D. Lee. An Empirical Evaluation of Weak Mutation // IEEE Transactions on Software Engineering. – 1994. – 20. – № 5. – p. 337-344. 16. A. J. Offut, R. H. Untch. Mutation 2000: Uniting the Orthogonal // Mutation 2000: Mutation Testing in the Twentieth and the Twenty First Centuries, San Jose, CA. – October, 2000. – p. 45-55. 17. A. K. Petrenko. Specification Based Testing: Towards Practice // LNCS. –2001. – 2244. – p. 287-300. 18. P. Purdom. A Sentence Generator For Testing Parsers // BIT. 1972. – № 2. – p. 336-375. 19. GCC. http://gcc.gnu.org/ 20. JavaCC. https://javacc.dev.java.net/ 21. J@T. http://unitesk.com/products/jat/ 22. CTesK. http://unitesk.com/products/ctesk/ 23. ”The mpC Programming Language Specification”, The Institute for System Programming of Russian Academy of Science. http://www.ispras.ru/~mpc.
58
Генерация тестов для модулей проверки статической семантики в компиляторах М.В. Архипова Аннотация. В статье исследуется проблема построения программ-тестов для модулей проверки статической семантики в компиляторах, рассматриваются существующие способы формального описания правил статической семантики языков программирования и обсуждаются причины, мешающие использованию существующих нотаций для решения задачи генерации семантически корректных программ-тестов. Кроме того, предлагается собственный подход к описанию правил статической семантики языков программирования, удобный для построения семантически управляемого генератора программ–тестов.
1. Введение Для задач тестирования компилятор удобно разбивать на модули, имеющие различные функции: лексический и синтаксический анализаторы, семантический анализатор, оптимизатор и генератор кода. Так как описанное разбиение условно, а для тестировщика компилятор представляется единой системой, преобразующей программы, написанные на входном языке, то в качестве тестов для тестирования любого модуля компилятора могут использоваться только программы, написанные на входном языке1. Тестирование различных частей компилятора выдвигает различные требования к свойствам тестовых программ, их разнообразию и способу проверки корректности поведения тестируемого модуля. Например, при тестировании лексического и синтаксического анализаторов в составе компилятора существенным является синтаксическая корректность или некорректность тестов, при этом их семантика не важна. Будем называть процесс тестирования модулей проверки статической семантики в компиляторах семантическим тестированием. Тестовые программы для семантического тестирования строятся с учетом семантических правил исходного языка. Естественным требованием для таких тестов является их синтаксическая корректность. Семантический анализатор (или модуль проверки статической семантики в компиляторах) должен пропускать корректные с точки зрения статической
семантики программы и обнаруживать некорректность для “неправильных” с точки зрения статической семантики программ, выдавая об этом сообщения, позволяющие локализовать проблему. Различают тесты “позитивные” и “негативные”. Целевая система2 должна выдавать сообщения об ошибках в ответ на “негативные” тесты и молча пропускать “позитивные”. При помощи набора “позитивных” тестов можно проверить, что все семантически правильные программы пропускаются, а при помощи набора “негативных” тестов — что все неправильные программы отвергаются. Такого рода семантические тесты являются самопроверяющимися [18], так как они не требуют особо проверять, что тестируемая система ведет себя корректно в ходе теста. Для оценки полноты тестирования необходимо определить критерий покрытия. Такой критерий разбивает всю область входных данных на конечное число подобластей. Это разбиение строится таким образом, чтобы в одну подобласть попали данные, предназначенные для обнаружения сходных ошибок. Задачи тестирования различных модулей компилятора исследовались неравномерно, и к настоящему моменту лучше всего изучен вопрос тестирования лексического и синтаксического анализаторов. Так произошло в основном благодаря появлению формы Бэкуса-Наура (BNF) [1], которая является общепризнанным способом формального описания синтаксиса языков программирования. Среди работ, посвященных решению задачи формального описания семантики языков программирования, выделим следующие: определение Алгола 60 средствами расширенного алгоритма Маркова, данное Дебаккером [4], определение Алгола 60 с помощью -исчисления, принадлежащее Ландину [5, 6, 7] (см. также Бём [8, 9]), определение Микро-Алгола с помощью рекурсивных функций, применяемых к программе и к “векторам состояний”, принадлежащее Маккарти [10] (см. также Маккарти и Пэинтер [11]); определение языка Эйлер с помощью семантических правил, применяемых во время синтаксического анализа программы, предложенное Виртом и Вебером [12], и определение языка PL/I, данное Венской лабораторией фирмы IBM и основанное на работе Маккарти и Ландина [23], а также на понятии абстрактной машины, введённом Элготом [13, 14]. Особое место занимает подход к описанию семантики языка с помощью атрибутных грамматик, введенных Кнутом [2, 3]. Существенное различие между предшествующими методами и атрибутными грамматиками Кнута состоит в том, что остальные определения представляют собой довольно сложные процессы, применяемые ко всему языку; можно сказать, что человек, прежде чем он поймёт описание языка, должен будет понять, как устроен его компилятор. Напротив, в атрибутных грамматиках каждая конструкция языка 2
1
Далее будем называть такие программы: тестами, или тестовыми программами, или программами-тестами.
59
Целевой системой будем называть программную систему, которая выступает в качестве объекта тестирования. В данной статье под целевой системой в основном будет подразумеваться модуль проверки статической семантики в компиляторе.
60
определяется только через её “непосредственное окружение”, тем самым взаимосвязи между определениями разных частей языка сводятся к минимуму. Например, определение составных операторов, операторов перехода и т.д. не влияет существенно на определение оператора печати; любое из правил можно выбросить, и получится строгое определение другого языка. Такая локализация и разделение семантических правил помогает сделать определение семантики языка более понятным и кратким. Хотя определения семантики языков программирования большинства авторов не так сложны, как определение Дебаккера, в их работах присутствуют относительно сложные зависимости между отдельными частями определения. На основании изложенного можно сделать вывод, что ни одна из ранее предложенных схем формального определения семантики не в состоянии дать такого же краткого и простого определения семантики, как атрибутные грамматики. Отметим, что семантические правила в том виде, в котором они описываются атрибутными грамматиками, не зависят от выбранного метода синтаксического анализа. Они привязаны лишь к конкретным формам синтаксиса. Семантические правила зависят от имен нетерминалов в левой и правой частях синтаксических правил. Конкретные знаки пунктуации и порядок, в котором нетерминалы располагаются в правых частях правил, несущественны с точки зрения семантических правил. Таким образом, атрибутные грамматики хорошо сочетаются с идеей Маккарти об “абстрактном синтаксисе” [10, 11]. Формализм атрибутных грамматик оказался очень удобным средством для описания семантики языков программирования. Вместе с тем выяснилось, что реализация вычислителей для атрибутных грамматик общего вида сопряжена с большими трудностями. В связи с этим рассматривались различные классы атрибутных грамматик, обладающих "хорошими" свойствами. К числу таких свойств относятся, прежде всего, простота алгоритма проверки атрибутной грамматики на зацикливание и простота алгоритма вычисления атрибутов для атрибутных грамматик данного класса. Атрибутные грамматики широко использовались для описания семантики языков программирования. Было создано несколько систем автоматизации разработки трансляторов и расширений для них, основанных на формализме атрибутных грамматик: Yacc и Lex [15], LIGA [20], Ox [21]. Опыт их использования показал, что атрибутный формализм может быть успешно применен для описания семантики языка при создании транслятора. Но при попытке применить атрибутные грамматики для решения задачи автоматической генерации семантически корректных программ возникают трудности. Атрибутные грамматики содержат в себе мало информации, которая могла бы быть использована для упорядочивания генерации семантически корректных программ-тестов.
61
2. Атрибутные грамматики и их применимость к задаче генерации семантических тестов Атрибутные грамматики в конце 60-х годов XX века ввел Кнут для описания статической семантики языков программирования [2, 3]. Напомним некоторые определения. Атрибутной грамматикой называется тройка AG = (G, A, R), где G — контекстно-свободная грамматика, A — конечное множество атрибутов, R — конечное множество семантических правил. Контекстно-свободной грамматикой называется четверка G = (N, T, P, D), где N — конечное множество нетерминальных символов, T — конечное множество терминальных символов, P — конечное множество правил вывода, D N — стартовый символ грамматики. Элементы множества V = N T называются грамматическими символами. Правило вывода — это пара вида X , где X N, а V * , т.е. слева в правиле вывода находится нетерминал, а справа — строка, состоящая из грамматических символов. Пустая строка обозначается символом . С каждым символом XV ассоциируется конечный набор атрибутов A(X). Рас-сматриваются два класса атрибутов: наследуемые I(X) и синтезируемые S(X). Для правила вывода pP, имеющего вид p: X 0 X 1 ... X n (n 0), обозначим через X i .a значения атрибутов его элементов. Здесь a A( X i ), 0 i n. Кроме того, правилу вывода p сопоставляется конечное множество семантических правил Rp. Множество Rp содержит по одному правилу для каждого синтезируемого атрибута X 0 и для каждого наследуемого атрибута X i при 1 i n. Каждое правило в Rp, таким образом, имеет вид X i .a f y1 ,... y k , k 0 , где a S( X i ) при i = 0, a I( X i ) при 1 i n; f называется семантической функцией, все yi являются атрибутами элементов правила, отличными от вычисляемых. Деревом вывода для программы L, созданным по грамматике G, называется дерево, каждая вершина которого соответствует грамматическому символу X V или ; корень соответствует стартовому символу D; и, кроме того, если вершина, соответствующая символу X, имеет детей X 1 ,..., X n , то X X 1 ... X n — правило вывода грамматики G. Атрибутированным деревом программы L называется дерево вывода программы L такое, что к каждой его вершине N, соответствующей символу X, присоединены атрибуты, соответствующие грамматическому символу X. Значение присоединенного атрибута a A(X) в вершине N принято обозначать N.a. 62
Графом атрибутной зависимости для атрибутного дерева T программы L называется ориентированный граф, вершинами которого выступают атрибуты вершин дерева T, а дуги строятся по следующему правилу: из вершины, соответствующей атрибуту N.b, существует дуга, ведущая в вершину, соответствующую атрибуту N.a, тогда и только тогда, когда значение N.a в силу введенных семантических правил зависит от значения N.b. Если значения всех атрибутов могут быть получены посредством вычисления семантических функций, то атрибутная грамматика называется правильно определенной (well defined). Кнут показал, что атрибутная грамматика является правильно определенной тогда и только тогда, когда соответствующий граф атрибутной зависимости программы L ацикличен [2]. Пример. Рассмотрим язык L = {anbncn | n > 0}, т.е. состоящий из предложений вида abc, aabbcc, aaabbbccc, и т. д. Для его формального описания используем атрибутную грамматику. Синтаксис языка L в виде BNF будет выглядеть так: S ::= A B C A ::= a | aA B ::= b | bB C ::= c | cC Дополним синтаксис семантической информацией: каждому грамматическому символу x, кроме стартового, соответствует атрибут Nx. Значение атрибута Nx есть число повторений в х соответствующего ему нетерминала. Например, атрибут Na соответствует символу A. Правило вычисления атрибута Na таково: Na(a) = 1 Na(aA) = 1 + Na(A) Атрибуты Nb и Nc соответствуют символам B и C, правила их вычисления выглядят аналогично правилу вычисления атрибута Na. Кроме того, надо ввести семантическое правило, обеспечивающее одинаковое количество всех терминальных символов в предложениях языка: Na(A) = Nb(B) = Nc(C) Атрибутная грамматика для языка L имеет следующий вид: S ::= ABC Na(A) = Nb(B) = Nc(C) A ::= a Na(A’) := 1 | aA Na(A’) := 1 + Na(A) B ::= b Nb(B’) := 1 | bB Nb(B’) := 1 + Nb(B) C ::= c Nc(C’) := 1 | cC Nc(C’) := 1 + Nc(C) Как можно построить алгоритм для генерации синтаксически и семантически корректных предложений L, основывающийся на его формальном описании в виде атрибутной грамматики? 63
Правила вычисления атрибутов не могут быть применены, пока не будет готово предложение языка. При построении предложений можно руководствоваться только продукционными BNF-правилами, других указаний для способа генерации предложений такая грамматика не содержит. На Рис. 1 в качестве примера изображено дерево синтаксического разбора синтаксически корректного, но семантически неверного предложения языка L.
Рис. 1. Дерево синтаксического разбора предложения языка L aabbc. Поэтому так устроенный алгоритм генерации требует фильтрации построенных предложений для выбора из них только семантически корректных, что эквивалентно написанию семантического анализатора. Как показывает этот пример, для генерации семантически корректных программ тяжело использовать атрибутные грамматики в классическом виде, поэтому для описания статической семантики предлагается другой формализм, который для определенности будем называть конструктивной грамматикой.
3. Конструктивная грамматика Удобной формой описания синтаксических конструкций языка является представление их в виде абстрактного синтаксиса, т.е. в виде абстрактных синтаксических деревьев (далее AST — Abstract Syntax Tree) [1]. Такое дерево может быть описано, например, с помощью TreeDL-нотации [19]. Далее для краткости будем называть абстрактные синтаксические деревья просто синтаксическими деревьями. Анализируя статическую семантику языков программирования, можно заметить, что семантические ограничения описывают связи между узлами синтаксических деревьев, например, связь между описанием переменной или функции и их использованием. Если для одного из этих узлов известны его атрибуты, то в семантически корректной программе можно вычислить атрибуты для второго узла. Например, если в описании переменной известен
64
атрибут, соответствующий имени переменной, то известен атрибут-имя для синтаксической конструкции ее использования. Для определенности будем называть узлы синтаксического дерева, связанные семантическим правилом, источником и целью. Под источником понимается узел, содержащий информацию, достаточную для определения атрибутов второго узла, который является целью. Будем говорить, что семантическое правило Sr относится к узлу T, если T является целью для Sr. Чтобы программа была семантически корректной, для каждого узла синтаксического дерева этой программы должны быть выполнены все семантические правила, которые к нему относятся. Исходя из вышесказанного, предлагается следующий новый подход к формализации статической семантики языков программирования для задач генерации программ-тестов. Конструктивной грамматикой будем называть тройку КG = (G, A, AR), где G — контекстно-свободная грамматика, описывающая синтаксис целевого языка, A — конечное множество атрибутов, использующихся для описания семантики AR — конечное множество абстрактных семантических правил, которые описывают семантику языка. Абстрактным семантическим правилом ASr AR будем называть шестерку ASr = (St, A(St), Tt, A(Tt), P, F), где St — тип узла-источника дерева абстрактного синтаксиса, A(St) — атрибуты источника, Tt — тип узла-цели дерева абстрактного синтаксиса, A(Tt) — атрибуты цели, P — множество параметров семантического правила ASr, F — семантическая функция, связывающая атрибуты цели с атрибутами источника. В отличие от атрибутных грамматик, где семантические функции описываются, как X i .a f y1 ,... yk , k 0 , где 1 i n, и j 1 j k y j — это атрибут в правиле вывода p: X 0 X 1 ... X n (n 0), в конструктивных грамматиках будем записывать семантические функции, как Tt.a = f (a1St,…, akSt), где k 0, j 1 j k a j St A( St ) — множество независимых атрибутов источника,
Tt.a A(Tt) — множество зависимых атрибутов цели, St — тип узла-источника дерева абстрактного синтаксиса, Tt — тип узла-цели дерева абстрактного синтаксиса, 65
f F — семантическая функция. Семантические правила, в отличие от их абстрактных описаний, соответствуют синтаксическим деревьям конкретных программ на целевом языке. Для семантических правил известны конкретные узлы (соответствующих типов, указанных в абстрактном описании), для которых должно выполняться это семантическое правило. Cемантическим правилом будем называть пятерку Sr = (S, A(S), T, A(T), ASr), где S — источник, узел синтаксического дерева программы, A(S) — значения атрибутов источника, T — цель, узел синтаксического дерева программы, A(T) — значения атрибуты цели, ASr — абстрактное описание семантического правила Sr. Каждое семантическое правило в конструктивной грамматике определяется двумя узлами синтаксического дерева программы. Один из этих грамматических символов — цель семантического правила и обозначается T (от английского слова target), другой — источник семантического правила и обозначается S (от английского слова source). Графом семантической зависимости для дерева Tree программы L называется ориентированный граф, вершинами которого выступают узлы-источники и узлы-цели дерева Tree для некоторых правил. Дуги в этом графе строятся так: из вершины S существует дуга в вершину T тогда и только тогда, когда для дерева Tree определено семантическое правило с источником S и целью T. Графом атрибутной зависимости для атрибутного дерева Tree программы L называется ориентированный граф, вершинами которого выступают атрибуты источников и целей множества семантических правил дерева Tree. Дуги в этом графе строятся по следующему правилу: из вершины, соответствующей атрибуту N.b, существует дуга в вершину, соответствующую атрибуту N.a, тогда и только тогда, когда атрибут N.a зависит от N.b. Граф семантической зависимости может быть получен из графа атрибутной зависимости путем склейки в графе атрибутной зависимости вершин, соответствующих атрибутам одного и того же узла. Аналогично граф атрибутной зависимости может быть получен из графа семантической зависимости путем разбиения вершин графа семантической зависимости на группы вершин, представляющих собой зависимые и независимые атрибуты, и проведения между ними соответствующих дуг. Конструктивная грамматика, в отличие от атрибутной, содержит информацию, которая может быть использована при построении синтаксических деревьев программ-тестов, что позволяет избежать полного перебора всех правил вывода. Конструктивные грамматики позволяют отказаться от построения таблиц символов, так как вся необходимая для правильного использования имен 66
R
информация в них имеется и доступна на момент определения атрибутов очередного грамматического символа.
Каждому типу узла соответствует множество R
4. Генерация тестов с использованием конструктивной грамматики целевого языка
Далее опишем действия, которые генератор повторяет для всех Ri Ttk.
Если отталкиваться от формального описания синтаксиса и статической семантики целевого языка программирования, то естественно считать критерием покрытия (для тестирования семантического анализатора) покрытие каждого описанного семантического правила во всех возможных синтаксических контекстах. Это означает, что для каждого семантического правила Sr во множестве тестов должны встречаться все возможные синтаксические конструкции, на которых действует семантическое правило Sr. Алгоритм генерации должен обеспечить достижимость этого требования. Процесс генерации семантических тестов состоит из следующих этапов: 1. Построение синтаксических конструкций, попадающих под действие семантических правил, и формирование из них синтаксических деревьев3; 2. Достраивание деревьев программ-тестов, полученных на первом этапе, до синтаксической полноты; 3. Определение семантических зависимостей «источник-цель»: для каждой цели ищем источник, если источников не хватает, то достраиваем4; 4. Определение порядка на множестве семантических функций, соответствующих рассматриваемому синтаксическому дереву5; 5. Вычисление семантических атрибутов целей. Далее следует подробное описание процесса семантически управляемой генерации позитивных тестов для тестирования семантического анализатора в компиляторе целевого языка.
4.1. Построение синтаксического дерева программы-теста
Рассмотрим
множество
{Tt k }k 0... N
типов
узлов
дерева
Tt k
Tt k i
AR Tt k
абстрактного
синтаксиса целевого языка. Каждый элемент этого множества считаем потенциальной целью (в терминах конструктивной грамматики).
3
На этом этапе деревья могут быть синтаксически неполными После первых трех этапов атрибуты абстрактных синтаксических деревьев (например, названия типов, имена переменных), остаются пустыми. Значения для них будут получены на 4 и 5 этапе алгоритма генерации. 5 Этот порядок необходим для вычисления семантически зависимых атрибутов. Независимые атрибуты для одного семантического правила могут быть зависимыми для другого семантического правила. 4
67
i 0...M Ttk
абст-рактных семантических правил, в которых он выступает в качестве цели. каждого
На этапе построения семантически интересного синтаксического дерева тестовой программы входными данными является выбранное абстрактное Tt k
семантическое правило Ri
, имеющее в качестве:
типа узла, обозначающего цель — Ttk , Tt k
типа узла, обозначающего источник — St i
.
Рассмотрим семантическое правило “все поля-члены класса должны иметь различные имена”. Синтаксические узлы, соответствующие полям-членам класса, по синтаксису не могут находиться ни в каком поддереве, кроме поддерева, корнем которого является узел, соответствующий объявлению класса. Для того, чтобы ускорить работу генератора, можно явно указать ему узел, начиная с которого имеет смысл строить цепочки к источнику и цели. Для рассмотренного семантического правила источником и целью будут узлы, обозначающие в грамматике целевого языка определения полей-членов класса, а начальным узлом для построения цепочек к источнику и цели — узел, обозначающий объявление класса. Для оптимизации алгоритма построения синтаксических деревьев и для придания большей гибкости описанию семантических правил введем новый параметр в абстрактное описание семантического правила, а именно: тип узла, который является корнем поддерева области действия семантического правила. Далее в описании семантических правил будем называть такой узел просто корнем поддерева. Информация о типе корня поддерева неявно содержится в неформальном описании каждого семантического правила. Будем строить синтаксическое дерево тестовой программы по трем вершинам узел-источник, узел-цель, узел-корень синтаксического поддерева. Естественным способом построения всех видов синтаксически неполных деревьев тестовых программ, содержащих указанные вершины, является нахождение декартова произведения следующих множеств: множества цепочек от стартового узла до корня поддерева, множества цепочек от корня поддерева до источника и множества цепочек от корня поддерева до цели (Рис. 2). В общем случае мощности таких множеств могут быть больше единицы. Тогда для выбранного абстрактного семантического правила может быть построено не одно абстрактное синтаксическое дерево, а целое семейство деревьев. 68
Рис. 2. Декартово произведение множества цепочек от стартового узла до корня поддерева, множества цепочек от корня поддерева до источника и множества цепочек от корня поддерева до цели. Каждому синтаксическому дереву в этом семействе соответствует множество абстрактных семантических правил, выполнение которых обеспечит семантическую корректность программы, которой соответствует рассматриваемое синтаксическое дерево. Это множество содержит все семантические правила, в которых в качестве целей могут выступать узлы рассматриваемого дерева (Рис. 3). На следующем этапе на основе множества абстрактных семантических правил должно быть построено множество семантических правил, связывающих конкретные узлы синтаксического дерева тестовой программы.
4.2. Построение множества семантических правил синтаксического дерева программы На предыдущем этапе процесса семантически управляемой генерации тестов были получены синтаксически полные деревья программ-тестов, сопровождаемые множеством абстрактных семантических правил.
Рис. 3. Построение синтаксических каркасов для семантически корректных тестов. Следующим этапом генерации будет построение из множества абстрактных описаний множеств семантических правил для каждого синтаксического дерева. 69
В качестве исходных данных рассмотрим множество синтаксически полных деревьев и список абстрактных семантических правил, в соответствии с которыми должны быть вычислены атрибуты узлов деревьев. Не теряя общности, рассмотрим синтаксическое дерево программы Tree и список абстрактных семантических правил SRList, выполнение которых обеспечит семантическую корректность. Для того, чтобы программа стала семантически корректной, необходимо правильно определить конкретные семантические правила. В предыдущем параграфе описывался процесс построения семейства деревьев Tt
программ для выбранного семантического правила Ri k . Будем называть такое семантическое правило основным (main). Для основного семантического правила по построению известны узлы источник и цель. Основное семантическое правило становится первым во множестве конкретных семантических правил, соответствующих дереву программы-теста. Рассмотрим правила заполнения неосновных семантических правил в соответствии с их типом. Выделяются три типа семантических правил: 1. “Один к одному” — каждой цели соответствует только один источник и этот источник не может определять другую цель. Примером может служить требование о соответствии типов выражений слева и справа от оператора суммирования; 2. “Один ко многим” — каждой цели соответствует только один источник, который может определять больше одной цели. Например, статическая семантика языка Си позволяет множественное использование переменной, которая описана только один раз; 3. “Многие ко многим” — каждой цели должны соответствовать все существующие в данном дереве источники указанного в семантическом правиле типа, каждый из которых должен определять все цели указанного в семантическом правиле типа. Например, каждая конструкция определения переменной должна задавать уникальное имя переменной6. Семантические правила типа “один к одному” и “один ко многим” задают правила вычисления атрибутов цели через атрибуты источника, поэтому источник должен обязательно существовать в дереве. Семантические правила типа “многие ко многим” не требуют обязательного наличия источника (или источников), но если он есть, то правило должно выполняться. Рассмотрим некоторое абстрактное семантическое правило Ri SRList. В независимости от его типа находим в дереве все узлы, соответствующие типу узла-цели в семантическом правиле Ri, и все узлы, соответствующие типу узлаисточника в семантическом правиле Ri. Найденные узлы дерева будем 6 Такого рода правила являются псевдоопределениями цели. На самом деле с помощью таких правил описываются требования на уникальности каких-либо значений.
70
называть кандидатами на роль цели и источника соответственно. Теперь необходимо расставить зависимости между узлами-кандидатами в соответствии с типом семантического правила: 1. Если рассматриваемое правило имеет тип “многие ко многим”, то ставим в соответствие по правилу Ri каждому узлу из множества кандидатов на роль цели каждый узел из множества кандидатов на роль источника. Отсутствие кандидатов-источников в дереве не имеет значения, в этом случае никаких зависимостей по правилу Ri не появляется. 2. Если правило отвечает типу “один ко многим”, то для каждого кандидата на роль цели подбираем источник из множества кандидатов и устанавливаем между ними зависимость по правилу Ri. Один и тот же источник может использоваться больше одного раза. Если множество кандидатов на роль источника пусто, тогда следует достроить в дереве необходимый узел, который мог бы выступить кандидатом на роль источника7. 3. Если правило имеет тип “один к одному”, то подбор кандидатов осуществляется аналогично пункту 2, с учетом того, что источник может определять только одну цель. Утверждение 1: Если позволяет синтаксис и семантические ограничения целевого языка, то для любого семантического правила может быть построен новый узел-источник. Доказательство: Действительно, по свойствам грамматики целевого языка любой узел достижим из стартового узла, и это утверждение может быть нарушено только, если: 1. В качестве корня поддерева семантического правила (то есть корня поддерева, где должны находиться источник и цель) указан узел, из которого источник и цель одновременно недостижимы; 2. Описаны избыточные семантические ограничения для источника, отбрасывающие все возможные варианты. Но оба эти случая означают, что допущены ошибки в описании правил статической семантики.
4.3. Вычисление атрибутов целей семантических зависимостей На этом этапе упорядочим все дуги графа семантических правил следующим образом. Будем называть граф семантической зависимости дерева Tree упорядоченным, если его дуги пронумерованы так, что при обходе графа атрибутной зависимости дерева Tree в соответствии с указанной нумерацией для каждого семантического правила атрибуты цели не будут посещены раньше атрибутов источника. Введенный на графе семантических зависимостей порядок определяет порядок вычисления атрибутов целей в семантических правилах. 7
Рассмотрим пример, в котором участвуют три семантических правила: 1. Ограничение на повтор имен при описании переменных в одной области видимости: переменные должны иметь различные имена в одной области видимости (узлы синтаксического дерева, соответствующие описанию переменных, обозначим Decl). 2. Ограничение на использование переменной до ее описания: переменная должна быть описана перед использованием. 3. Ограничение на использование не инициализированных переменных: переменная перед использованием должна быть проинициализирована (узлы синтаксического дерева, соответствующие использованию переменных, обозначим VarUse, инициализации переменных — Assign). Первое семантическое правило связывает узлы Decl в отношении «многие ко многим», второе — связывает Assign (источник) и VarUse (цель) в отношении «один ко многим», третье — Decl (источник) и VarUse (цель) в отношении «один ко многим». Предположим, что построено синтаксическое дерево, фрагмент которого изображен на Рис. 4.
Рис. 4. Фрагмент синтаксического дерева. Фрагменту синтаксического дерева с Рис. 4 может соответствовать фрагмент кода ... int
; float ; ... = 0; ... if () { ... } ... Namei - узлы обозначают атрибуты соответствующих узлов (Name1 - Decl1, Name2 — Decl2, Name3 — VarUse — Assign, Name4 — VarUse - ... ). На следующем рисунке (Рис. 5) жирными линиями изображен полученный граф
Это возможно как показано в утверждении 1.
71
72
семантической зависимости, дуги которого пронумерованы, следуя ранее введенным правилам нумерации.
Будем называть граф семантических зависимостей разрешимым, если система, образованная правилами вычисления атрибутов графа семантических зависимостей, имеет решение. В случае, когда получена неразрешимая система, необходимо переопределить множество семантических правил для рассматриваемого дерева программы так, чтобы система для вычисления семантических атрибутов была разрешима. Пример такого переопределения приведен на Рис. 7.
Рис. 5. Фрагмент синтаксического дерева с указанием семантических правил Правила вычисления атрибутов для синтаксического дерева с Рис. 5 образуют систему (1). Name2 Name1 Name3 Name1 (1) Name4 Name2 Name4 Name3 Очевидно, что система (1) не имеет решения. Посмотрим на рисунок 6. На нем жирными стрелками обозначены дуги графа атрибутной зависимости. Хорошо виден контур, в котором присутствует дуга, соответствующая правилу запрета эквивалентности значений атрибутов (Name1 → Name2).
Рис. 7 Фрагмент синтаксического дерева с указанием семантических правил. Семантическое правило “переменная должна быть описана перед использованием” имеет тип “один-ко-многим”, поэтому можно использовать узел Decl1 в качестве источника для определения узла VarUse (Рис. 7) вместо узла Decl2, как это было раньше (см. Рис. 5). Граф атрибутных зависимостей, соответствующий графу на Рис. 7, образует систему (2): Name2 Name1 Name3 Name1 (2) Name4 Name1 Name4 Name3 Система (2) имеет решение. Следовательно, граф семантических зависимостей разрешим, и синтаксическое дерево, на котором определен этот граф, может быть преобразовано в корректную программу в соответствии с заданными правилами статической семантики целевого языка.
5. Реализация подхода Рис. 6 Фрагмент синтаксического дерева с указанием семантических зависимостей между атрибутами. Если бы, например, в графе атрибутной зависимости не было контура, или в контуре не присутствовала дуга запрета эквивалентности значений атрибутов, или все дуги контура были бы дугами запрета эквивалентности, тогда система имела бы решение. 73
Описанная методика построения синтаксически и семантически корректных программ-тестов была реализована в инструменте Stg. Stg (Semantic Test Generator) — java-инструмент для автоматической генерации семантически корректных тестовых программ на целевом языке программирования. Stg — инструмент, адаптируемый для разных языков программирования. Это значит, что инструмент состоит из неизменяемого ядра 74
и языково-зависимых частей, большинство из которых генерируются автоматически из формального описания грамматики целевого языка. Инструмент Stg был использован для формального описания статической семантики языка Cи с использованием стандарта языка С99 [16]. В таблице 1 приво-дятся среднестатистические данные по результатам генерации тестов для языка Си. Количество сгенерированных программ Средний размер сгенерированных программ
~ 10000 ~ 30 (строк кода в одной программе) Общий объем ~ 10 Mb Время генерации ~ 60 мин Время прогона всех программ через gcc-компилятор ~ 15 мин Покрытие кода фронтенда gcc-компилятора ~ 27 % (для измере-ния сгенерированными программами использовался Clover)
Таб. 1. Результаты генерации Stg (Cи) Stg-алгоритм использует формальное описание целевого языка в качестве исходной информации для создания программ на целевом языке. Синтаксис и семантика целевого языка описываются отдельно. Для описания абстрактного синтаксиса используется специальная нотация TreeDL[19]. Описание статической семантики задается в специальной нотации SRL (Semantic Relation Language). Описанная технология генерации тестовых программ на целевом языке позволила реализовать пилотный проект для MPEG-21 [17]. MPEG-21 определяет XML-схемы, описывающие метаданные и семантические правила для их обработки. Для проведения пилотного проекта были предложены IPMP XML-схема и стандарт, неформально описывающий семантические требования [24]. STG-технология позволила формализовать описание семантических требований, XML-схема была переведена в TreeDL-представление, после чего автоматически было сгенерировано множество XML-документов, соответствующих IPMP — описанию. В заключение, отметим, что работа над автоматизацией семантически управляемой генерации с использованием конструктивных грамматик продолжается. Как показали последние эксперименты, описанная технология позволяет создавать также и “негативные” тесты. Литература 1. А.Ахо, Р.Сети, Д.Ульман. Компиляторы: принципы, технологии, инструменты // Москва–Санкт-Петербург–Киев, 2001. 2. Knuth D.E. Semantics of Context-Free Languages, Mathematical Systems Theory, 2, 2, 1968, 127-146.
75
3. Knuth D.E. Semantics of Context-Free Languages: Correction, Mathematical Systems Theory, 5, 1, 1971, 179. 4. de Bakker J.W., Formal definition of programming languages, with an approach to the definition of ALGOL 60, Math. Cent. Tracts 16. Mathematical Centrum. Amsterdam, 1967. 5. Landin P.J., “The mechanical evaluation of expressions’, Comp. J. 6 (1964), 308-320. 6. Landin P.J., A formal description of ALGOL 60, Formal Language Description for Computer Programming, pp. 266-294, Proc. IFIP working Conf. Vienna (1964), North Holland, 1966. 7. Landin P.J., A correspondence between ALGOL 60 and Church’s lambda notation, Comm. ACM 8 (1965), 89-101, 158-165. 8. Bohm C., The CUCH as a formal and description language, Formal language description languages for computer programming, pp. 266-294, Proc. IFIP working Conf. Vienna (1964), North Holland, 1966. 9. Corrado Bohm and Wolf Cross, “Introduction to the CUCH”, Automata Theory (ed/ by Caianiello E.R.), pp. 35-65, Academic press, 1966. 10. McCarthy J., A formal definition of a subset of ALGOL, Formal Language Description for Computer Programming, pp. 1-12, Proc. IFIP working Conf. Vienna (1964), North Holland, 1966. 11. McCarthy J., Painter J., Correctness of a compiler for arithmetic expressions, Proc. Sympos. Appl. Math., Vol 17, Amer. Math. Soc., Providence, R.I., 1967. 12. Wirth N., Weber H., Euler: A generalization of ALGOL, and its formal definition, Comm. ACM 9 (1966), 11-23, 89-99, 878. 13. Elgot C.C., “Machine species and their computation languages”, Formal Language Descripton Languages for Computer Programming, pp. 160-179, IFIP Working Conf., Vienna (1964), North Holland, 1966. 14. Elgot C.C., Robinson A., “Random-access, stored program machines, an approach to programming languages”, J. ACM 11 (1964), 365-399. 15. http://dinosaur.compilertools.net/ 16. ISO/IEC 9899 - Programming languages — C. http://wwwold.dkuug.dk/JTC1/SC22/WG14/www/C99RationaleV5.10.pdf 17. http://www.iso.ch/iso/en/prods-services/popstds/mpeg.html 18. Ю.В.Баскаков. Принципы построения тестовых комплектов для тестирования конформ-ности компиляторов стандартам языков программирования. – в сб. трудов под ред. В.А. Сухомлина ‘Теоретические и прикладные проблемы ИТ’ Москва, ВМиК МГУ, 2001 19. http://sourceforge.net/projects/treedl 20. Kastens, U. "Attribute Grammars as a Specification Method", Proceedings of the International Summer School on Attribute Grammars, Lecture Notes in Computer Science 545, Springer-Verlag, New York-Heidelberg-Berlin, 1991a, 16-47. 21. Kurt M. Bischoff. Ox: An attribute-grammar compiling system based on yacc, lex and c: User reference manual. User Manual, 1993. http://citeseer.ist.psu.edu/bischoff93ox.html 22. http://www.swif.uniba.it/lei/foldop/foldoc.cgi?abstract+syntax 23. PL/I-Definition Group of the Vienna Laboratory "Formal Definition of PL/I (U.L.D. No. 3)" Tech. Rep. TR 25.071, IBM Laboratory Vienna, Vienna, Austria, Dec. 1966. 24. ISO/IEC 13818-11:2004 Information technology -- Generic coding of moving pictures and associated audio information -- Part 11: IPMP on MPEG-2 systems; http://www.iso.org/iso/en/CatalogueDetailPage.CatalogueDetail?CSNUMBER=37680& COMMID=&scopelist=
76
Webприложение
Функциональное тестирование Web-приложений на основе технологии UniTesK
HTTP, HTTPS
Webбраузер
HTMLинтерфейс Пользователь
Рис. 1. Процесс работы типичного Web-приложения. А.А. Сортов, А.В. Хорошилов Аннотация. Статья посвящена анализу дополнительных возможностей автоматизации функционального тестирования Web-приложений на основе технологии UniTesK. В ней рассматриваются существующие подходы к автоматизации функционального тестирования Web-приложений, обсуждаются их достоинства и недостатки. Кроме того, анализируются возможные варианты применения технологии UniTesK для тестирования данного класса приложений, и предлагается способ дополнительной инструментальной поддержки процесса разработки функциональных тестов.
1. Введение В данной статье Web-приложениями мы будем называть любые приложения, предоставляющие Web-интерфейс. В настоящее время такие приложения получают все большее распространение: системы управления предприятиями и драйверы сетевых принтеров, интернет-магазины и коммутаторы связи – это только небольшая часть приложений, обладающих Web-интерфейсом. В отличие от обычного графического пользовательского интерфейса Webинтерфейс отображается не самим приложением, а стандартизированным посредником – Web-браузером. Web-браузер берет на себя все взаимодействие с пользователем и обращается к Web-приложению только в случае необходимости. Описание пользовательского интерфейса предоставляется браузеру в стандартном представлении, в роли которого обычно выступает HTML1 [1]. На рис. 1 представлен процесс работы типичного Web-приложения. Пользователь взаимодействует с приложением посредством Web-браузера, который при необходимости обращается с запросом к Web-приложению, чтобы выполнить ту или иную операцию. Результатом такого обращения является полное или частичное обновление интерфейса приложения, отображаемого в браузере.
При обращении к Web-приложению браузер посылает запрос по одному из протоколов доступа (HTTP, HTTPS или др.). Web-приложение обрабатывает запрос и возвращает браузеру описание обновленного интерфейса. Web-приложения в первую очередь характеризуются тем, что их пользовательский интерфейс имеет стандартизированную архитектуру, в которой: 1) для взаимодействия с пользователем используется Web-браузер; 2) взаимодействие с пользователем четко разделяется на этапы, в течение которых браузер работает с одним описанием интерфейса; 3) эти этапы разделяются однозначно выделяемыми обращениями от браузера к приложению; 4) для описания интерфейса применяется стандартное представление (HTML); 5) коммуникации между браузером и приложением осуществляются по стандартному протоколу (HTTP). Web-приложения можно рассматривать как клиент/серверные приложения, в которых функциональность реализуется как на серверной, так и на клиентской стороне. Функциональность, реализованная на клиентской стороне, как правило, сводится к проверке вводимых данных и реализации дополнительных возможностей интерфейса, что реализуется путем использования скриптовых возможностей, встроенных в HTML (использование Java-script, VBScript и т.д.)2. В этой статье мы будем рассматривать функциональное тестирование именно серверной части, оставляя рассмотрение функциональности клиентской части в качестве темы будущих исследований. В этом случае основной интерес представляют взаимодействия браузера с сервером. Эти взаимодействия хорошо формализованы, поскольку осуществляются на основе протокола
2
1
Кроме скриптовых возможностей существуют другие средства расширения функциональности, такие как технология ActiveX, Macromedia Flash и др., которые взаимодействуют с Web-приложением, используя свои собственные механизмы. В рамках данной статьи тестирование таких расширений не рассматривается.
Помимо HTML может использоваться XHTML, XML и др.
77
78
HTTP. Четкая формализация взаимодействий может служить основой для автоматизации функционального тестирования. С другой стороны, представление интерфейса в виде HTML также четко формализовано. Кроме того, в этом описании интерфейса можно выделить действия, приводящие к взаимодействию с сервером. Эти действия связаны с воздействиями на кнопки, активизацией гиперссылок и реакциями на различные события, закодированные в скриптовой части интерфейса. Таким образом, формальное описание интерфейса Web-приложений также предоставляет широкие возможности для автоматизации функционального тестирования. Но как наилучшим образом использовать потенциал, предоставляемый стандартизированной архитектурой интерфейса Web-приложения? Исследованию этого вопроса и посвящена данная работа. Статья построена следующим образом. В разделе 2 мы рассмотрим существующие подходы к автоматизации функционального тестирования Webприложений. В разделе 3 будут представлены основные сведения о технологии автоматизации тестирования на основе моделей UniTesK. Затем мы проанализируем различные варианты моделирования Web-приложений в рамках технологии UniTesK и по результатам этого анализа представим расширение базовой технологии UniTesK, специально адаптированное для функционального тестирования Web-приложений. В заключение, мы рассмотрим пути дальнейшего развития предложенного подхода.
2. Существующие подходы к функциональному тестированию Web-приложений Самым распространенным является подход, называемый Capture & Playback (другие названия – Record & Playback, Capture & Replay). Суть этого подхода заключается в том, что сценарии тестирования создаются на основе работы пользователя с тестируемым приложением. Инструмент перехватывает и записывает действия пользователя, результат каждого действия также запоминается и служит эталоном для последующих проверок. При этом в большинстве инструментов, реализующих этот подход, воздействия (например, нажатие кнопки мыши) связываются не с координатами текущего положения мыши, а с объектами HTML-интерфейса (кнопки, поля ввода и т.д.), на которые происходит воздействие, и их атрибутами. При тестировании инструмент автоматически воспроизводит ранее записанные действия и сравнивает их результаты с эталонными, точность сравнения может настраиваться. Можно также добавлять дополнительные проверки – задавать условия на свойства объектов (цвет, расположение, размер и т.д.) или на функциональность приложения (содержимое сообщения и т.д.). Все коммерческие инструменты тестирования, основанные на этом подходе, хранят записанные действия и ожидаемый результат в некотором внутреннем представлении, доступ к которому можно получить, используя или распространенный язык програм79
мирования (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). Описанные подходы основываются на построении тестов с использованием тестируемого приложения. В подходе Keyword Driven. [7, 9] предпринимается попытка сделать процесс создания тестов независимым от реализации. Суть подхода заключается в том, что действия, выполняемые в ходе тестирования, описываются в виде последовательности ключевых слов из специального словаря («нажать», «ввести», «проверить» и т.д.). Специальный компонент тестовой системы переводит эти слова в воздействия на элементы интерфейса тестируемого приложения. Таким образом, никакого программирования для создания тестов не нужно. Единственное, что нужно менять при изменении интерфейса, – это компонент, который отвечает за перевод слов из «словаря» в последовательность воздействий на приложение. Комплект тестов может 80
разрабатываться пользователями, не владеющими навыками программирования, однако для поддержания комплекта в рабочем состоянии программирование все-таки необходимо. В качестве примера инструмента, поддерживающего такой подход к разработке тестов, можно привести Certify [10] от WorkSoft, в котором поддерживается библиотека функций для работы с каждым компонентом интерфейса (окна, гиперссылки, поля ввода и т.д.) и предоставляется язык воздействий на эти элементы (InputText, VerifyValue, VerifyProperty и т.д.). Основные преимущества этого подхода заключаются в том, что он позволяет создавать тесты, не дожидаясь окончания разработки приложения, руководствуясь требованиями и дизайном интерфейса. Созданные тесты можно использовать как для автоматического выполнения, так и для ручного тестирования. Основной недостаток этого подхода – отсутствие автоматизации процесса разработки тестов. В частности, все тестовые последовательности разрабатываются вручную, что приводит к проблемам, как на стадии разработки, так и на стадии сопровождения тестового набора. Эти проблемы особенно остро проявляются при тестировании Web-приложений со сложным интерфейсом.
3. Технология UniTesK Большинство проблем, присущих рассмотренным подходам разработки тестов, решены в технологии UniTesK, разработанной в Институте системного программирования РАН. Технология хорошо себя зарекомендовала при функциональном тестировании разнообразных систем (ядро операционной системы, стеки протоколов, компиляторы). Опыт применения технологии для тестирования Web-приложений показал, что UniTesK может служить хорошей базой для тестирования такого класса приложений. В этом разделе мы остановимся на основных моментах технологии UniTesK (более детальную информацию о ней можно найти в [11, 12, 13, 14]); в последующих разделах рассмотрим особенности применения технологии для тестирования Webприложений. Технология UniTesK – это технология разработки функциональных тестов на основе моделей, которые используются для оценки корректности поведения целевой системы3 и автоматической генерации последовательностей воздействий, далее называемых тестовыми последовательностями. Оценка корректности поведения целевой системы осуществляется в рамках следующего представления об устройстве интерфейса целевой системы. Предполагается, что целевая система предоставляет набор интерфейсных функций, и все воздействия на нее осуществляются только через вызовы этих 3
Целевой системой мы будем называть программную систему, которая выступает в качестве объекта тестирования. Далее в качестве синонима для термина целевая система мы будем также использовать словосочетание тестируемая система.
81
интерфейсных функций. Параметры воздействий, передаваемые целевой системе, описываются входными параметрами интерфейсной функции. Результат воздействия (реакция системы) представляется выходными параметрами, значения которых могут зависеть от истории взаимодействий целевой системы с окружением. Информация об истории моделируется внутренним состоянием целевой системы. Внутреннее состояние влияет на выходные параметры интерфейсных функций и может изменяться в результате их работы. Следует заметить, что в рамках данной статьи для тестирования Webприложений рассматривается представление, в котором воздействия на целевую систему и получение ее реакции на это воздействие (выходные параметры интерфейсной функции) рассматриваются как атомарное действие. Под атомарностью действия понимается, что следующее воздействие можно произвести только после получения реакции на предыдущее. Технология UniTesK также позволяет представлять целевую систему и как систему с отложенными реакциями, т.е. как систему, разрешающую воздействие до получения всех реакций на предыдущее. Корректность поведения целевой системы оценивается с точки зрения его соответствия поведению некоторой «эталонной» модели, называемой спецификацией. В технологии UniTesK эталонная модель описывается неявно в виде требований к поведению каждой интерфейсной функции. При задании эталонной модели можно описывать функции и их параметры в достаточно обобщенном виде, отвлекаясь от несущественных подробностей. Основными компонентами тестовой системы являются итератор тестовых воздействий, оракул и медиатор. Задачей итератора тестовых воздействий, работающего под управлением обходчика, является построение тестовой последовательности, обеспечивающей заранее определенный критерий тестового покрытия. Задачей оракула является оценка корректности поведения целевой системы. Задача медиатора – преобразовывать тестовое воздействие в последовательность реальных воздействий на целевую систему и на основании доступной информации построить новое модельное состояние целевой системы после вызова. В качестве языка описания компонентов тестовой системы используются спецификационные расширения обычных языков программирования, таких как C# и Java. В этих расширениях реализованы три вида специальных классов, предназначенных для описания компонентов тестовой системы. Из спецификационных классов генерируются оракулы, из медиаторных – медиаторы, а из сценарных – итераторы тестовых воздействий. В спецификационных классах описываются спецификационные методы, каждый из которых соответствует некоторой интерфейсной функции и содержит формальное описание требований к поведению целевой системы при взаимодействии с ней через данную интерфейсную функцию. Сценарные классы предназначены для описания тестовых сценариев, содержащих 82
описание единичных воздействий и правил итерации их параметров. Медиаторы генерируются на основе медиаторных классов, которые связывают интерфейсные функции с воздействиями на целевую систему. Схема работы тестовой системы, разработанной по технологии UniTesK, представлена на рис. 2. Тестовая система вход Обходчик
Итератор воздействий
Оракул
Целевая система
Медиатор
выход
Трасса
Отчеты
Рис. 2. Схема работы тестовой системы, разработанной по технологии UniTesK. Основной шаг работы тестовой системы устроен следующим образом. Обходчик выбирает очередное сценарное воздействие. Сценарное воздействие содержит несколько обращений к целевой системе, представляющих собой вызов интерфейсной функции с определенным набором значений входных параметров. Вызов интерфейсной функции передается оракулу, который, в свою очередь, передает его медиатору. Медиатор преобразует вызов интерфейсной функции в последовательность действий над тестируемой системой, получает результат этих действий от тестируемой системы и преобразует его в значения выходных параметров интерфейсной функции. Медиатор также синхронизирует модель состояния тестируемой системы, используемую оракулом для оценки корректности поведения, с ее реальным состоянием. Оракул, зная значения входных и выходных параметров интерфейсной функции, а также состояние целевой системы, оценивает корректность ее 83
поведения в рамках данного взаимодействия. Если вердикт оракула является отрицательным, то это значит, что тестовая система нашла несоответствие между поведением целевой системы и требованиями к ней. По завершении выполнения одного шага управление возвращается обходчику, который выбирает следующее сценарное воздействие или принимает решение о прекращении тестирования. Все события, происходящие в процессе тестирования, находят свое отражение в трассе теста. На основе трассы генерируются различные отчеты, помогающие в анализе результатов тестирования. Процесс разработки тестов с помощью технологии UniTesK можно представить в виде следующей последовательности шагов4: (1) анализ функциональности тестируемой системы; (2) формализация требований к функциональности; (3) связывание формализованных требований с реализацией; (4) разработка сценариев тестирования; (5) исполнение тестов и анализ результатов. Рассмотрим каждый из этих шагов более подробно. В результате анализа функциональности необходимо определить интерфейс тестируемой системы. Для этого требуется выделить функции, предоставляемые системой, и для каждой такой функции определить, что выступает в качестве ее входных и выходных параметров. На этапе формализации требований для каждой интерфейсной функции, выявленной на предыдущем шаге, необходимо описать ограничения на значения выходных параметров в зависимости от значений входных параметров и истории предыдущих взаимодействий с тестируемой системой. Для этого в технологии UniTesK используется широко известный подход программных контрактов [15]. В основе этого подхода лежат инварианты данных, а также предусловия и постусловия интерфейсных операций. При связывании требований с реализацией необходимо описать, как каждая интерфейсная функция отображается на реализацию тестируемой системы. В рамках этого отображения требуется установить правила преобразования вызовов интерфейсных функций в последовательность действий над тестируемой системой, а также правила построения модели состояния тестируемой системы. Для систем с прикладным программным интерфейсом, когда взаимодействие через интерфейсную функцию соответствует вызову функции тестируемой системы, установление такого отображения может быть автоматизировано при помощи интерактивных шаблонов, предоставляемых инструментами семейства UniTesK. 4 Конечно технология UniTesK не требует строго последовательного выполнения этих шагов. Они описывают только основное направление развития разработки теста. Ничто не мешает возвращаться к предыдущим шагам для уточнения и доработки определенных элементов.
84
Тестовые сценарии строятся на основе конечно-автоматной модели целевой системы, которая используется для динамической генерации последовательностей тестовых воздействий. Сценарий определяет, что именно рассматривается как состояние автомата, и какие интерфейсные функции с какими наборами аргументов могут быть вызваны в каждом состоянии. Алгоритмы UniTesK обеспечивают вызов каждой интерфейсной функции с каждым набором ее параметров в каждом достижимом состоянии. Для описания сценария необходимо задать способ идентификации состояний и способ итерации входных воздействий в зависимости от текущего состояния. Инструменты семейства UniTesK предоставляют интерактивные шаблоны, которые позволяют упростить разработку тестовых сценариев. На заключительном этапе технологического процесса происходит выполнение созданных тестов, автоматическая генерация отчетов о результатах тестирования и анализ этих результатов. На основе анализа принимаются решения о создании запросов на исправление дефектов, обнаруженных в тестируемой системе, или о доработке самих тестов с целью повышения уровня покрытия.
4. Применение UniTesK для тестирования Webприложений Технология UniTesK применялась для тестирования Web-приложений в нескольких проектах. В ходе разработки тестов выяснилось, что большая часть усилий тратится на создание медиаторов, которые переводят вызов интерфейсных функций в последовательность воздействий на Webприложение. Анализ опыта показал, что большая часть этой работы может быть автоматизирована, опираясь на стандартизированную архитектуру пользовательского интерфейса Web-приложений. В принципе, эту особенность Web-приложений можно было бы использовать для автоматизации других шагов технологии UniTesK. В этом разделе будут рассмотрены варианты моделирования поведения Web-приложения в контексте возможной автоматизации шагов технологии UniTesK. Моделирование определяется способом выделения интерфейсных функций и способом построения модели состояния Web-приложения. Первый вариант основывается на стандартном протоколе HTTP, который служит для взаимодействия между Web-браузером и Web-приложением. Поведение Webприложения рассматривается на уровне HTTP, и этот уровень считается единственно возможным для обращения к Web-приложению. Во втором варианте за основу берется формальное описание интерфейса в виде HTML, которое используется Web-браузером для организации взаимодействия с пользователем. В этом варианте взаимодействие с Web-приложением происходит только посредством Web-браузера. И, наконец, в третьем варианте поведение Web-приложения моделируется без привязки к конкретному способу обращения, основываясь лишь на тестируемой функциональности. 85
4.1. Моделирование поведения на уровне HTTP В первом варианте модель Web-приложения представляется одной интерфейсной функцией, описывающей HTTP-запрос к Web-приложению. Параметры этой функции – это параметры запроса (например, тип запроса (GET или POST), адрес (URL), параметры заголовка и т.д.) и список данных, которые передаются Web-приложению. Выходные параметры функции формируются на основе HTTP-ответа, пришедшего от Web-приложения. Требования к функциональности формулируются в виде набора ограничений на выходные параметры в зависимости от значений входных параметров и модели состояния Web-приложения. Требования в большинстве случаев сильно различаются в зависимости от URL, поэтому спецификацию интерфейсной функции можно разделить на независимые спецификации нескольких интерфейсных функций, каждая из которых описывает поведение, характерное для конкретного семейства значений параметра, определяющего URL. Однако для больших Web-приложений такое описание требований получается громоздким и плохо структурируемым. Каждая интерфейсная функция соответствует определенному запросу с некоторыми параметрами; в процессе работы тестовой системы вызов интерфейсной функции преобразуется в посылку соответствующего HTTP-запроса серверу. HTTP-запрос строится на основе формальных правил преобразования параметров, поэтому шаг технологии UniTesK, на котором происходит связывание требований с реализацией, полностью автоматизируется. При создании тестовых сценариев для этого варианта наибольшую трудность представляет организация перебора параметров выделенных интерфейсных функций. Для каждого конкретного случая можно найти наиболее подходящий способ перебора параметров, однако это требует от тестировщика определенной квалификации и опыта. Следует отметить, что этот вариант позволяет тестировать Web-приложение на устойчивость к некорректным HTTP-запросам, так как можно имитировать ситуации, которые не должны появиться в процессе нормальной работы с Webприложением посредством браузера.
4.2. Моделирование на уровне Web-браузера Во втором варианте в качестве интерфейса системы рассматривается интерфейс, предоставляемый Web-браузером. В этом варианте интерфейсным функциям соответствуют воздействия на активные элементы интерфейса, в результате которых происходит обращение к Web-приложению. Такими элементами можно считать гиперссылки, кнопки форм и элементы интерфейса, для которых определена обработка на клиентской стороне, приводящая к обращению к серверу. Входные параметры этих функций – это данные, которые может вводить пользователь, например, при заполнении полей ввода или выборе значений из выпадающих списков и т.д. Таким образом, если 86
рассматривать HTML-форму, то нажатию на кнопку, по которой отправляются данные, будет соответствовать интерфейсная функция, параметрами которой являются значения полей этой формы. Стоит оговориться, что в этом варианте в качестве тестируемой системы рассматривается Web-приложение в целом, включая как серверную, так и клиентскую часть. Однако внимание акцентируется на тестировании серверной части, и не ставится задача покрытия функциональности клиентской части. В этом варианте считается, что выходные параметры интерфейсных функций отсутствуют, поскольку результат воздействия описывается как изменение состояния Web-приложения. Состояние Web-приложения в этом варианте разбивается на состояние интерфейса, отображаемого Web-браузером, и состояние сервера. К состоянию интерфейса можно отнести текущую отображаемую страницу и состояние элементов на ней. К состоянию сервера относится, например, состояние базы данных, с которой работает Web-приложение, или данные, описывающие сеанс работы пользователя. Интерфейсные функции доступны не во всех состояниях, так как не во всех состояниях пользовательского интерфейса присутствуют элементы интерфейса, воздействия на которые соответствуют этим интерфейсным функциям. Например, некоторые элементы интерфейса становятся доступны только после авторизации, HTML-формы с полями и кнопками располагаются не на всех страницах Web-приложения. Условия доступности описываются в предусловии и определяются состоянием Web-приложения. Требования к функциональности описываются в постусловии в виде ограничений на состояние, в которое переходит Web-приложение в результате воздействия, описываемого интерфейсной функцией. Часто одному URL соответствуют пользовательские интерфейсы, содержащие одни и те же наборы интерфейсных функций. В таких случаях эти наборы удобно объединять в группы функций и специфицировать их как методы одного спецификационного класса. В других случаях одни и те же функции могут присутствовать сразу на нескольких интерфейсах, соответствующих разным URL. В этом случае интерфейсные функции удобно объединять в группы в зависимости от функционального назначения и специфицировать отдельно. Это позволяет получить хорошо структурированные спецификации, в которых дублирование описания функциональности сведено к минимуму. По сравнению с первым, этот вариант позволяет уделить большее внимание описанию именно функциональности Web-приложения, абстрагируясь от деталей обработки HTTP-запросов и ответов, что существенно упрощает моделирование работы пользовательских интерфейсов, обладающих сложным динамическим поведением. При тестировании вызов интерфейсной функции преобразуется в воздействия на соответствующие элементы пользовательского интерфейса Web87
приложения. Для этого используются специальные средства (например, API Web-браузера), обеспечивающие доступ к внутреннему состоянию Webбраузера и позволяющие моделировать воздействия на элементы интерфейса. Эти же средства используются для получения информации о результатах воздействия, которые отражаются в состоянии пользовательского интерфейса. Такая информация необходима для проверки корректности поведения тестируемого Web-приложения. В отличие от первого варианта, для определения связи интерфейсных функций с Web-приложением требуются более сложные преобразования, так как обращение к одной интерфейсной функции может быть преобразовано в несколько последовательных воздействий на элементы пользовательского интерфейса. При ручном описании этих преобразований потребуется больше усилий; кроме того, тестировщик должен обладать знаниями и навыками работы со специальными средствами доступа к внутреннему состоянию Webбраузера. Тем не менее, в большинстве случаев этот процесс можно автоматизировать при помощи дополнительной функциональности инструментов тестирования, реализующих технологию UniTesK. При создании тестовых сценариев для этого варианта удобно опираться на предлагаемый технологией UniTesK метод построения тестовой последовательности, основанный на обходе графа переходов конечного автомата. Часто в качестве состояния автомата удобно рассматривать страницы с одним и тем же URL, а в качестве переходов между состояниями – вызовы интерфейсных функций. При построении тестового сценария основной задачей пользователя становится описание перебора параметров интерфейсных функций. При этом не нужно описывать все интересные пути обхода страниц Web-приложения; обход достижимых состояний автомата будет автоматически реализован при помощи обходчика. Описание перебора параметров интерфейсных функций (т.е. перебор данных на странице, например, перебор значений полей формы) может быть автоматизировано при помощи некоторого интерактивного режима работы пользователя с инструментом. Этот же интерактивный режим работы, в принципе, может быть использован и для создания сценариев тестирования на основе подхода Capture & Playback, если пользователь все-таки захочет вручную выделить некоторые пути обхода страниц. Итак, во втором варианте пользователю предлагается более «естественный» язык для создания теста – в терминах интерфейсных элементов Web-приложения и воздействий на них. Описания становятся более понятными, что, как следствие, снижает требования к квалификации разработчиков тестов. Недостатком этого варианта по сравнению с первым можно считать отсутствие возможности протестировать работу Web-приложения на некорректных HTTP-запросах.
4.3. Моделирование без привязки к уровню взаимодействия Третий вариант заключается в рассмотрении только функциональности Webприложения и абстрагировании от того, каким образом эта функциональность 88
предоставляется пользователю. В этом случае интерфейсным функциям соответствуют абстрактные действия, которые выделяются на основе требований к функциональности Web-приложения. Одной интерфейсной функции может соответствовать несколько последовательных обращений к серверу (например, авторизация, переход на нужную страницу, заполнение на ней полей формы и нажатие кнопки). Входные и выходные параметры функций также определяются на основе требований и могут быть не привязаны к интерфейсу Web-приложения. Результатом такого подхода являются качественные спецификации с удобным абстрактным интерфейсом, которые можно применять для тестирования на разных уровнях (на уровне EJB, посредством Web-браузера или HTTP-протокола). При этом вся работа по созданию таких спецификаций должна быть целиком возложена на разработчика тестов, поэтому требования к его квалификации в этом варианте значительно выше, чем в первых двух. Поскольку интерфейсной функции соответствует некоторое абстрактное действие, которое отвечает некоторому варианту работы с Web-приложением, в этом подходе, как и во втором, возникает проблема автоматизации преобразований вызовов интерфейсных функций в обращения к Web-приложению. Эта проблема, так же как и в предыдущем варианте, может быть решена с использованием интерактивного режима работы. Как уже было замечено, спецификацию интерфейсных воздействий можно использовать для тестирования на разных уровнях, поэтому в результате работы пользователя в интерактивном режиме можно сразу получать преобразования в воздействия разных уровней. Интерфейсное воздействие может быть переведено в серию HTTP-запросов или же в последовательность обращений к элементам интерфейса Web-приложения. При создании сценариев тестирования необходимо задавать перебор параметров интерфейсных функций и, при необходимости, указывать способ определения состояния для работы обходчика. Эти шаги для рассматриваемого варианта не обладают какой-либо спецификой и мало чем отличаются от шагов технологического процесса UniTesK при его традиционном применении. Подведем итог. Третий вариант моделирования никак не ограничивает разработчика тестов при описании поведения и формализации требований к тестируемой системе. Получившиеся в результате компоненты тестовой системы можно использовать для тестирования на разных уровнях. На шаги технологии в этом подходе практически не влияет специфика Webприложений, поэтому трудно предлагать какую-либо автоматизацию, за исключением шага связывания формализованных требований с реализацией, на котором разрабатываются медиаторы.
4.4. Сравнение возможной модели-рования
автоматизации
вариантов
Сравним рассмотренные варианты. Цель сравнения – выбрать вариант для тестирования Web-приложений, который обеспечивал бы наибольшую 89
автоматизацию процесса разработки тестов при реализации инструментальной поддержки. Вместе с тем, инструментальная поддержка должна обеспечивать высокое качество функционального тестирования на уровне UniTesK, обладая при этом простотой использования и освоения. Каждый вариант мы будем рассматривать, исходя из следующих критериев. Во-первых, рассмотрим каждый вариант с позиции уровня автоматизации каждого шага технологического процесса UniTesK (последний шаг – выполнение тестов и анализ результатов – не рассматривается, так как инструменты UniTesK уже автоматизируют этот шаг в достаточной степени). Во-вторых, рассмотрим уровень абстракции получаемой в каждом варианте модели требований, поскольку более абстрактные модели облегчают повторное использование, а также обеспечивают простоту разработки и сопровождения получаемых тестов. В-третьих, рассмотрим предъявляемые каждым вариантом требования к квалификации пользователя, что обобщает простоту использования и обучения работе с инструментом. Результаты сравнения представлены в Таблице 1. Вариант моделирования Критерий Автоматизация анализа функциональности тестируемой системы Автоматизация формализации требований Автоматизация связывания требований с реализацией Автоматизация разработки тестовых сценариев Уровень абстракции Требования к квалификации разработчика
Уровень HTTP
Уровень Webбраузера
Без привязки к уровню взаимодействия
высокий уровень
высокий уровень
отсутствует
высокий уровень средний уровень низкий
низкий уровень высокий уровень высокий уровень средний
высокие
средние
отсутствует
отсутствует низкий уровень средний уровень высокий высокие
Таб. 1. Сравнение рассмотренных вариантов возможного моделирования Web-приложений с использованием технологии UniTesK. Прокомментируем результаты, приведенные в таблице. Результатом анализа функциональности тестируемой системы является список интерфейсных функций с входными и выходными параметрами. При моделировании без привязки к уровню взаимодействия этот выбор осуществляется пользователем пол-ностью вручную, исходя из представлений о функциональности тестируемой системы. В других вариантах моделирования можно предложить формальный способ выделения интерфейсных функций на основе анализа интерфейса Web-приложения (может быть, с использованием интерактивного режима). Формализация требований трудно поддается автоматизации, как и всякий переход от неформального к формальному, но в рамках моделирования на 90
уровне Web-браузера можно предложить автоматизацию формализации некоторой части требований с использованием интерактивного режима. Проблема связывания требований с реализацией решается посредством автоматической генерации медиаторов при моделировании как на уровне Webбраузера, так и на уровне HTTP. Для моделирования без привязки к уровню взаимодействия для создания медиаторов можно предложить воспользоваться интерактивным режимом работы с инструментом, при котором для каждой интерфейсной функции определяется последовательность воздействий на элементы пользовательского интерфейса Web-приложения. При разработке тестовых сценариев можно пользоваться стандартными средствами инструментов семейства UniTesK, которые позволяют автоматизировать процесс задания основных элементов тестового сценария. Однако для моделирования на уровне Web-браузера можно предложить дополнительные средства для организации перебора параметров и идентификации различных тестовых ситуаций, например, извлекать данные для перебора из интерфейса Web-приложения или выбирать элементы интерфейса, идентифицирующие состояние. При моделировании на уровне HTTP описание функциональности и тестирование осуществляется в терминах HTTP-запросов, что заставляет пользователя разбираться с большим объемом технической информации, повышая тем самым требования к его квалификации. Моделирование на уровне Web-браузера позволяет описывать функциональность и проводить тестирование в терминах элементов пользовательского интерфейса и воздействий на них, что является естественным языком для разработчика тестов. Описание, выполненное в таких терминах, позволяет отразить требования к функциональности на приемлемом уровне абстракции, не сосредотачиваясь на деталях технологий, лежащих в основе Web-приложения. Третий вариант не ограничивает пользователя в свободе выбора уровня описания функциональности, но вместе с тем не предоставляет дополнительных возможностей по автоматизации, возлагая всю работу на пользователя, что требует от него определенных навыков и опыта. Описанные варианты моделирования были опробованы в ходе тестирования Web-приложений с использованием технологии UniTesK. Все шаги технологического процесса были реализованы с использованием инструментов UniTesK без дополнительной автоматизации, которая упоминалась в обзоре вариантов моделирования. Анализ процесса разработки показал необходимость и подтвердил выводы о возможности дополнительной автоматизации шагов технологии. Кроме того, опыт передачи тестовых наборов, разработанных с помощью технологии UniTesK, в реальное использование показал, что моделирование без привязки к уровню взаимодействия требует от разработчиков и последующих пользователей хорошего знания технологии UniTesK. В то же время, моделирование на 91
уровне Web-браузера более естественно воспринималось пользователями, тестирующими Web-приложения и не владеющими технологией UniTesK. Оценивая рассмотренные варианты с этих позиций, можно сказать, что моделирование на уровне Web-браузера является наиболее подходящим для дальнейшей автоматизации и дополнительной инструментальной поддержки. В следующем разделе мы остановимся более подробно на реализации дополнительной инструментальной поддержки этого подхода.
5. Дополнительная инструментальная поддержка Основной задачей, возлагаемой на инструментальную поддержку, является упрощение работы пользователя по созданию компонентов тестовой системы. Это достигается за счет дополнительной автоматизации шагов технологического процесса UniTesK с учетом специфики Web-приложений. Первый шаг технологического процесса UniTesK – анализ функциональности тестируемой системы – не предполагает инструментальной поддержки, однако для Web-приложений можно предложить способ выделения интерфейсных функций на основе автоматизированного анализа интерфейса Webприложения. На шаге формализации требований пользователь может описывать требования в виде условий на различные атрибуты элементов интерфейса; эти условия могут строиться с использованием поддержки инструмента. Информации, собранной при автоматизации первого и второго шагов, оказывается достаточно для автоматического связывания интерфейсных функций с Web-приложением. Для шага разработки тестовых сценариев предлагаются дополнительные возможности по описанию его компонентов в терминах интерфейса Web-приложения. Последний шаг не требует дополнительной автоматизации, так как все инструменты семейства UniTesK уже предоставляют развитые средства выполнения тестов и анализа их результатов. При использовании дополнительной инструментальной поддержки процесс разработки тестов для функционального тестирования Web-приложений изменяется, и состоит из следующих шагов: 1) создание модели Web-приложения; 2) создание тестового сценария; 3) выполнение тестов и анализ результатов. Первый шаг – создание модели Web-приложения – включает в себя определение интерфейсных функций, описание требований к ним и их связывание с Web-приложением, т.е. объединяет первые три шага технологии UniTesK. Основная задача этого шага – формализация требований к интерфейсным функциям – в отличие от второго шага технологии UniTesK может быть частично автоматизирована, а выделение интерфейсных функций и их связывание с Web-приложением происходит автоматически. Два последних шага соответствуют двум последним шагам технологии UniTesK и отличаются только уровнем автоматизации. 92
Рассмотрим более подробно перечисленные выше шаги. На первом шаге должно быть получено описание модели, состоящее из набора интерфейсных функций и описания требований к ним. Интерфейсная функция соответствует воздействию на интерфейс Web-приложения, в результате которого происходит обращение к серверу. Элементы интерфейса, влияющие на параметры этого обращения, включаются в список параметров интерфейсной функции. Результатом воздействия является обновление интерфейса, которое описывается в требованиях к интерфейсной функции. Например, для HTML-формы регистрации можно описать интерфейсную функцию, соответствующую отправке данных формы на сервер, параметрами которой являются значения полей, входящих в форму. В описание требований включается информация о значениях, для которых успешно выполняется регистрация, и описываются ограничения на состояние обновленного интерфейса. Разбиение множества всех возможных состояний интерфейса Web-приложений на классы эквивалентности представляется в модели набором страниц. Это разбиение, которое, с одной стороны, используется при описании требований, с другой стороны, является основой для определения состояния в сценарии. По умолчанию разбиение на страницы осуществляется по адресу (URL) HTMLдокумента, отображаемого в Web-браузере. Однако пользователь может переопределить разбиение произвольным образом. При традиционном способе построения Web-приложения, когда для каждого URL определяется его собственный интерфейс, разбиение по умолчанию соответствует представлению пользователя о тестировании Web-приложения – пройти все страницы и проверить всю возможную функциональность на каждой из них. Также естественным для пользователя требованием к Web-приложению является требование перехода с одной страницы на другую в результате активизации гиперссылки или нажатия на кнопку HTML-формы. Такие требования легко описываются с помощью понятия страниц. В более сложных случаях, например, для описания требования к результату работы HTMLформы поиска по некоторому критерию, требования формулируются в виде условий на атрибуты элементов интерфейса. Автоматизация построения модели поддерживается в процессе сеанса работы с тестируемым Web-приложением. Пользователь осуществляет навигацию по страницам приложения, редактируя список интерфейсных функций и их параметров, который автоматически предлагается инструментом, и добавляет описания требований, формулируя их в виде проверки некоторых условий на атрибуты элементов интерфейса. Для формулировки проверок инструмент предоставляет возможность выделения интерфейсных элементов и задания условий на их атрибуты. В результате этого шага инструмент создает из модели Web-приложения компоненты тестового набора UniTesK, обычно появляющиеся на первых трех шагах технологии – это спецификационные классы, описывающие 93
интерфейсные функции, и медиаторные классы, реализующие связь между интерфейсными функциями и тестируемой системой. В спецификационных классах для каждой интерфейсной функции создаются спецификационные методы, в которых описываются требования к поведению функций, сформулированные при работе инструмента. Для этого используются предусловия и постусловия спецификационных методов. В том случае, если средств, предоставляемых инструментом, недостаточно для описания функциональности Web-приложения, полученные спецификационные классы могут быть доработаны вручную. В медиаторных классах описывается связь между спецификацией и тестируемой системой. Для каждого спецификационного метода задается медиаторный метод, который преобразует вызов этого спецификационного метода в соответствующее воздействие на интерфейс Web-приложения. Это преобразование осуществляется следующим образом. Для каждого параметра спецификационного метода медиатор находит соответствующий ему элемент интерфейса Web-приложения и устанавливает значение его атрибутов в соответствии со значением параметра. Затем медиатор осуществляет воздействие требуемого типа на элемент интерфейса, соответствующий данной интерфейсной функции, и ожидает реакции Web-приложения. Как правило, реакция на воздействие заключается в обращении Web-браузера к Web-серверу и получении от него нового описания интерфейса. Медиатор дожидается завершения обновления состояния интерфейса и синхронизирует состояние модели. На втором шаге нужно получить описание тестов для Web-приложения. При создании тестов используется подход, предлагаемый технологией UniTesK. Согласно этому подходу тесты описываются в виде тестовых сценариев, в основе которых лежит алгоритм обхода графа переходов конечного автомата. Для каждого тестового сценария нужно выбрать подмножество интерфейсных функций, для тестирования которых предназначен данный сценарий. Для каждой выбранной функции нужно задать правила, по которым будут перебираться ее параметры. Кроме того, нужно задать правила идентификации состояний тестового сценария. Для автоматизации процесса создания тестового сценария предоставляется возможность определять итерацию для параметров выбранных интерфейсных функций на основе готовых вариантов перебора. Для этого могут использоваться библиотечные итераторы и итераторы, разработанные пользователем. Данные, которые вводились в ходе сеанса работы с инструментом на первом шаге, также могут быть включены в качестве дополнительных значений для заданной итерации. Кроме того, инструмент может предложить перебор параметров, построенный на основе анализа интерфейса Webприложения. Например, использовать для итерации значения элементов выпадающего списка или же значения, которые берутся из разных интерфейсных элементов, например, расположенных в столбце некоторой таблицы. 94
Для решения другой задачи, связанной с определением состояния тестового сценария, инструмент предоставляет средства для описания этого состояния в терминах интерфейса Web-приложения. В качестве состояния по умолчанию инструмент предлагает использовать страницу HTML-документа. Для более детального разбиения пользователю предоставляется возможность уточнить описание состояния, выбрав элементы интерфейса и указав условия на их атрибуты или на наличие этих элементов в интерфейсе. Кроме того, для описания состояния можно пользоваться описанием состояния сервера. Следует заметить, что информации, собранной на шаге построения модели Web-приложения, уже достаточно для создания тестового сценария. При создании тестового сценария по умолчанию используются все выделенные интерфейсные функции, для итерации параметров которых используются итераторы по умолчанию и значения, введенные пользователем в ходе сеанса построения модели, а в качестве состояния сценария используется страница HTML-документа. В качестве альтернативного способа создания тестов для Web-приложения можно использовать подход Capture & Playback. В процессе работы пользователя с Web-приложением инструмент записывает последовательность воздействий на интерфейс, на основе которой генерирует последовательность вызовов интерфейсных функций, соответствующую записанным воздействиям. Итак, по сравнению с базовым подходом UniTesK описанный подход обладает следующими преимуществами. Во-первых, уменьшается объем ручного труда за счет автоматизации действий, предписываемых технологией UniTesK. Вовторых, снижаются требования к квалификации пользователей технологии, так как в этом подходе основным языком взаимодействия с пользователем является не язык программирования (или его расширение), а язык элементов интерфейса и воздействий на них. Следует заметить, что этот подход сохраняет большинство преимуществ технологии UniTesK – гибкую архитектуру тестового набора, обеспечивающую возможность повторного использования компонентов, автоматическую генерацию тестовых последовательностей. Недостатком данного подхода является отсутствие возможности создания тестов до появления реализации, поскольку подход основан на использовании реализации для дополнительной автоматизации шагов технологии UniTesK. Однако наличия прототипа уже достаточно для начала процесса разработки тестов.
6. Сравнение с другими подходами В предлагаемый подход, по возможности, были включены достоинства распространенных подходов и инструментов, предназначенных для функционального тестирования Web-приложений. Этот подход, как и другие, позволяет строить тесты, оперируя терминами интерфейса и действий с ним; полученные тесты могут быть расширены с использованием итерации данных. Предоставляются средства для автоматического прогона полученных тестов, анализа их 95
результатов и генерации отчетов о покрытии и обнаруженных ошибках. Инструмент, реализующий данный подход, может быть использован для создания тестов в стиле Capture & Playback с сохранением всех достоинств этого подхода к тестированию. В реализации описанного подхода также присутствует и основное достоинство подхода Keyword Driven – хорошая архитектура тестового набора, обеспечивающая устойчивость при изменении интерфейса. В то же время предложенный подход отличается от других рядом преимуществ. В отличие от подхода Capture & Playback он позволяет автоматически генерировать тестовые последовательности, покрывающие различные тестовые ситуации. Таким образом, в этом подходе затраты на создание комплекта тестов того же качества становятся меньше. Другим достоинством данного подхода является возможность создания тестов для не полностью корректной реализации. Если в реализации присутствует ошибка, заключающаяся в том, что при некотором воздействии приложение переходит на некорректную страницу, то инструмент, реализующий Capture & Playback, ошибочно запоминает результат этого перехода как правильный. Добавление этого теста в автоматизированный тестовый набор будет возможно только после исправления ошибки – нужно, чтобы инструмент запомнил корректный результат. В описанном же подходе при описании модели можно явно указать ожидаемую страницу и ее параметры, после чего инструмент будет рассматривать любой другой результат как ошибочный и генерировать соответствующие отчеты об ошибках. Подход Keyword Driven не предоставляет возможности автоматизации разработки тестов и хорошо автоматизирует только выполнение тестов. Описанный подход автоматизирует как выполнение, так и разработку тестов. К перечисленным преимуществам можно добавить возможность автоматизированного анализа интерфейса Web-приложения, благодаря чему пользователь может выбирать интерфейсные функции из списка функций, автоматически найденных на странице. Кроме того, в дополнение к традиционным источникам данных для итерации параметров, таких как файлы различных форматов и базы данных, инструмент позволяет извлекать данные из интерфейса Web-приложения, что удобно использовать для организации динамически изменяемого перебора параметров.
7. Направления дальнейшего развития. В данной работе мы представили расширение технологии UniTesK, которое автоматизирует процесс создания тестов для тестирования функциональности Web-приложений. Тесты строятся в терминах элементов интерфейса и воздействий на них в процессе интерактивной работы пользователя с инструментом, реализующим это расширение. Инструмент взаимодействует с Webприложением, автоматизируя анализ его интерфейса в предположении, что основная функциональность Web-приложения реализована на стороне сервера. 96
В заключение рассмотрим возможные направления развития данного подхода. Одним из наиболее важных направлений является автоматизация поддержки тестового набора в актуальном состоянии при изменении интерфейса Webприложения. Эта проблема присутствует во всех подходах, в которых тесты строятся на основе уже работающей реализации. Единственное относительно успешное решение заключается в локализации компонентов теста, зависящих от деталей реализации интерфейса Web-приложения, позволяющей сократить усилия по приведению тестов в соответствие с изменившимся интерфейсом. Следующее направление – тестирование функциональности, реализованной в пользовательском интерфейсе. Напомним, что эта функциональность обычно заключается в проверке корректности входных данных и реализации дополнительных возможностей интерфейса. Для описания этой функциональности нужно использовать принципы выделения интерфейсных функций, основанные на более детальном описании взаимодействия пользователя с Web-браузером. И, наконец, можно выделить направление, связанное с расширением видов тестирования, поддерживаемых инструментом, начиная от генерации некорректных HTTP-запросов для тестирования устойчивости Webприложения и заканчивая использованием разработанных тестовых наборов для нагрузочного тестирования. Литература 1. D. Raggett, A. Le Hors, I. Jacobs. HTML 4.0 Specification URL: http://www.w3.org/TR/html40 2. http://solex.sourceforge.net 3. http://www.segue.com 4. http://www-306.ibm.com/software/awdtools/tester/robot 5. http://www.mercury.com/us/products/quality-center/functional-testing/winrunner 6. Shakil Ahmad. Advance Data Driven Techniques 7. Keith Zambelich. Totally Data-Driven Automated Testing URL: http://www.sqa-test.com/w_paper1.html 8. http://www.empirix.com 9. Carl J. Nagle. Test Automation Frameworks. URL: http://safsdev.sourceforge.net/DataDrivenTestAutomationFrameworks.htm 10. http://www.worksoft.com 11. I.B. Bourdonov, A.S. Kossatchev, V.V. Kuliamin, A.K. Petrenko. UniTesK Test Suite Architecture. Proc. of FME 2002. LNCS 2391, pp. 77-88, Springer-Verlag, 2002. 12. В.В. Кулямин, А.К. Петренко, А.С. Косачев, И.Б. Бурдонов. Подход UniTesK к разработке тестов. Программирование, 29(6):25–43, 2003. 13. http://www.unitesk.com 14. А.В. Баранцев, И.Б. Бурдонов, А.В. Демаков, С.В. Зеленов, А.С. Косачев, В.В. Кулямин, В.А. Омельченко, Н.В. Пакулин, А.К. Петренко, А.В. Хорошилов. Подход UniTesK к разработке тестов: достижения и перспективы. Труды Института системного программирования РАН, №5, 2004. http://www.citforum.ru/SE/testing/unitesk 15. Bertrand Meyer. Applying 'Design by Contract'. IEEE Computer, vol. 25, No. 10, October 1992, pp. 40-51.
97
непротиворечивости и т.п. Для задачи автоматизации тестирования эта формальная запись должна также обеспечивать возможность описания формальной связи между понятиями, используемыми в спецификации, и сущностями языка реализации программы. В наиболее распространенных подходах используются специализированные языки формальных спецификаций, но существуют определенные трудности внедрения таких подходов в индустрию. Другой подход заключается в расширении обычных языков программирования (или других, привычных для пользователей языков) средствами спецификаций. Такой подход используется в методологии UniTesK [15]. В данной статье описана попытка применения такого подхода к автоматизации тестирования графических интерфейсов.
Автоматическая генерация тестов для графического пользовательского интерфейса по UMLдиаграммам действий
2. Обзор имеющихся средств автоматического тестирования графического интерфейса
А.Я. Калинов, А.С. Косачёв, М.А. Посыпкин, А.А. Соколов Аннотация. В статье излагается метод автоматической генерации набора тестов для графического интерфейса пользователя, моделируемого детерминированным конечным автоматом с помощью UMLдиаграмм действий. Метод заключается в построении обхода графа состояний системы с применением неизбыточного алгоритма обхода [1, 2] и компиляции построенного обхода в тестовый набор.
1. Введение Практически все программные системы предусматривают интерфейс с оператором. Практически всегда этот интерфейс – графический (GUI – Graphical User’s Interface). Соответственно, актуальна и задача тестирования создаваемого графического интерфейса. Вообще говоря, задача тестирования создаваемых программ возникла практически одновременно с самими программами [1]. Известно, что эта задача очень трудоёмка как в смысле усилий по созданию достаточного количества тестов (отвечающих заданному критерию тестового покрытия), так и в смысле времени прогона всех этих тестов. Поэтому решение этой задачи стараются автоматизировать (в обоих смыслах). Для решения задачи тестирования программ с программным интерфейсом (API – Application Program Interface: вызовы методов или процедур, пересылки сообщений) известны подходы – методы и инструменты, – хорошо зарекомендовавшие себя в индустрии создания программного обеспечения. Основа этих подходов следующая: создается формальная спецификация программы, и по этой спецификации генерируются как сами тесты, так и тестовые оракулы – программы, проверяющие правильность поведения тестируемой программы. Спецификации как набор требований к создаваемой программе существовали всегда, Ключевым словом здесь является формальная спецификация. Формальная спецификация – это спецификация в форме, допускающей её формальные же преобразования и обработку компьютером. Это позволяет анализировать набор требований с точки зрения их полноты, 99
Самым распространённым способом тестирования графического интерфейса является ручное тестирование. Естественно, у такого подхода имеются серьёзные недостатки, и самый главный из них – большая трудоёмкость, которая часто становится критичной при организации регрессионного тестирования. Другим недостатком такого подхода является отсутствие гарантий выполнения каждого доступного воздействия из каждого возможного состояния системы. Таким образом, приходится полагаться на однозначность реакции системы на одинаковые воздействия из разных состояний, то есть считать эти состояния эквивалентными для данного воздействия. Другим способом организации тестирования является автоматическое тестирование [2]. Существуют два раскрытия этого термина: Ручное написание тестов и их автоматический прогон; Автоматическая генерация тестов. Из наиболее доступных инструментов для автоматизированного тестирования можно выделить следующие пять: WinRunner; QA Run; Silk Test; Visual Test; Robot. Перечисленные инструменты применяются для автоматизированного тестирования, в том числе при тестировании GUI. В своей статье [3] Рэй Робинсон приводит сравнительную таблицу (см. таб. 1) этих инструментов по способности удовлетворять заданные потребности пользователя. При этом рассматриваются различные свойства этих инструментов: Запись и проигрывание тестов; Возможности Web-тестирования; Возможности базы тестов и функции для работы с ней; 100
Доступные инструменты для автоматической генерации тестов для GUI нам не известны. Поэтому мы в первую очередь сконцентрировали свои усилия именно на автоматизации генерации тестов для GUI по формальной спецификации. Причем при разработке средств формальной спецификации GUI мы старались максимально использовать существующие, привычные для пользователей методы и инструменты.
Объектное представление; Сравнение рисунков; Обработка ошибочных ситуаций; Сравнение свойств объектов; Возможности расширения языка; И т.п. (По 5-бальной шкале, наивысшая оценка 1)
Web Testing
Database tests
Data functions
Object Mapping
Image testing
Test/Error recovery
Object Name Map
Object Identity Tool
Extensible Language
Environment support
Integration
Cost
Ease of use
Support
Object Tests
WinRunner QA Run Silk Test Visual Test Robot
3. Описание подхода
Record & Playback
2 1 1 3 1
1 2 2 3 2
1 1 1 4 1
2 2 2 3 1
1 1 1 2 1
1 1 1 2 1
2 2 1 2 2
1 2 1 4 4
2 1 2 1 1
2 2 1 2 1
1 2 2 3 2
1 1 3 2 1
3 2 3 1 2
2 2 3 3 1
1 2 2 2 2
1 1 1 2 1
Таблица 1. Кроме самых распространённых, имеется много других инструментов для автоматического прогона тестов, созданных вручную. Но эти инструменты не предоставляют возможности автоматической генерации тестов (исключая процесс записи теста). В основе всех этих инструментов лежат средства формального описания тестов (разного рода скриптовые языки). Тесты пишутся вручную. Данный подход имеет существенные недостатки. Один из них состоит, как и при полностью ручном подходе, в недостаточной систематичности. Другим серьёзным недостатком является трудность при организации регрессионного тестирования. При изменении тестируемой системы возникает необходимость переписывания (редактирования) большого количества тестов. То есть, например, система изменилась в одном месте, а тесты приходится модифицировать в несравненно большем количестве мест. Причём если некоторые такие проблемы можно обойти созданием набора вызываемых функций, которые также можно изменять в одном месте, то другие (например, изменение в логике программы) так просто не решаются. Кроме того, остаётся проблема дублирования одних и тех же действий (написание одного и того же тестового кода) при совершении одного и того же события из разных состояний. Такой метод сменил ручной прогон тестов на ручное написание самих тестов для дальнейшего автоматического прогона, но само их написание по-прежнему остаётся трудоёмким занятием. 101
Правильность функционирования системы определяется соответствием реального поведения системы эталонному поведению. Для того чтобы качественно определять это соответствие, нужно уметь формализовать эталонное поведение системы. Распространённым способом описания поведения системы является описание с помощью диаграмм UML (Unified Modeling Language) [4]. Стандарт UML предлагает использование трех видов диаграмм для описания графического интерфейса системы: Диаграммы сценариев использования (Use Case); Диаграммы конечных автоматов (State Chart); Диаграммы действий (Activity). С помощью UML/Use Case diagram можно описывать на высоком уровне наборы сценариев использования, поддерживаемых системой [5]. Данный подход имеет ряд преимуществ и недостатков по отношению к другим подходам, но существенным с точки зрения автоматизации генерации тестов является недостаточная формальная строгость описания. Широко распространено использование UML/State Chart diagram для спецификации поведения системы, и такой подход очень удобен с точки зрения генерации тестов. Но составление спецификации поведения современных систем с помощью конечных автоматов есть очень трудоёмкое занятие, так как число состояний системы очень велико. С ростом функциональности системы спецификация становится всё менее и менее наглядной. Перечисленные недостатки этих подходов к описанию поведения системы преодолеваются с помощью диаграмм действий (Activity). С одной стороны, нотация UML/Activity diagram является более строгой, чем у сценариев использования, а с другой стороны, предоставляет более широкие возможности по сравнению с диаграммами конечных автоматов. Следующие причины определяют пригодность диаграмм действий для моделирования графического интерфейса: 1. Графический интерфейс пользователя (GUI) в большинстве случаев удобно представить в виде конечного автомата, так как поведение системы зависит от состояния, в котором она находится, и воздействия пользователя (и, может быть, других внешних воздействий). 2. GUI имеет иерархичную структуру. Действительно, модальные диалоги (те, которые перехватывают управление) удобно моделировать с помощью отдельной диаграммы состояний (для верхней диаграммы это поддиаграммы), инкапсулируя тем самым их функциональность. 102
3. Диаграмма действий позволяет специфицировать систему таким образом, при котором отсутствует дублирование одинаковых событий из разных состояний. Достигается это использованием на диаграмме управляющих элементов, из которых возможны тестовые воздействия, а не самих состояний. 4. Спецификация диаграммами действий удобна для восприятия человеком. Другими словами, спецификация графического интерфейса с помощью диаграмм действий является достаточно естественным способом описания требований к графическому интерфейсу. Причём, при таком подходе сохраняется вся сила формальных спецификаций – как показывает опыт, большая часть ошибок выявляется именно на этапе составления спецификации системы. Итак, перейдём к детальному рассмотрению предлагаемого подхода. Для создания прототипа работающей версии данного подхода использовался инструмент Rational Rose [6]. Он использовался в первую очередь для спецификации графического интерфейса пользователя при помощи диаграмм действий UML. Для прогона сгенерированных по диаграмме состояний тестов использовался инструмент Rational Robot [6]. Из возможностей инструмента в работе мы использовали следующие: 1. Возможность выполнять тестовые воздействия, соответствующие переходам между состояниями в спецификации. 2. Возможность проверять соответствие свойств объектов реальной системы и эталонных свойств, содержащихся в спецификации. Из тех возможностей, которые доступны с помощью этого инструмента, используется проверка следующих свойств объектов: Наличие и состояние окон (заголовок, активность, доступность, статус); Наличие и состояние таких объектов, как PushButton, CheckBox, RadioButton, List, Tree и др. (текст, доступность, размер); Значение буфера обмена; Наличие в оперативной памяти запущенных процессов; Существование файлов. Общая схема генерации и прогона тестов выглядит следующим образом: Диаграмма состояний
Генератор тестов Тестовый набор
Прогонщик тестов
Результаты тестирования
103
Рассмотрим ее компоненты более подробно.
3.1. Диаграмма состояний Диаграмма состояний – это формальная спецификация всех состояний системы и возможных переходов между ними с помощью диаграмм действий. Но во избежание так называемого «взрыва состояний» был использован метод расширенных конечных автоматов (EFSM – Extended Finite State Machine). При этом выделяется множество так называемых управляющих состояний и множество переменных. Реальные же состояния системы описываются управляющими состояниями и наборами значений переменных. Каждому переходу приписывается (кроме обычных стимулов и реакций) предикат от значений переменных и действия по изменению значения переменных. EFSM позволяют существенно сократить размер описания, сохраняя практически всю информацию о системе. На диаграммах имеются объекты типа State(Normal). Это управляющие элементы, или управляющие состояния. Переменные были введены как раз затем, чтобы не дублировать одно и тоже действие из разных состояний, а изобразить его один раз из одного управляющего состояния. Таким образом, каждому управляющему состоянию на диаграмме соответствует множество реальных состояний системы. То есть мы можем написать: Состояние = . (где UML-State есть управляющее состояние). Соответственно, переходы между управляющими состояниями обретают предусловия (guard condition), определяющие, из каких конкретно состояний, соответствующих данному управляющему состоянию, возможен переход, и действия (action), определяющие, в какое конкретно состояние ведёт этот переход. Предусловия и действия записываются на простом языке, в котором есть следующие конструкции: 1. конструкция ::= список предусловий | список действий 2. список предусловий ::= предусловие | список предусловий, предусловие 3. список действий ::= действие | список действий, действие 4. предусловие ::= переменная = выражение 5. действие ::= переменная := выражение 6. выражение ::= переменная | константа 7. переменная ::= символ{символ} 8. константа ::= цифра{цифра} 9. символ ::= буква | цифра Такой язык был выбран из-за простоты интерпретации, что упростило написание интерпретатора. Но, как оказалось, перечисленных возможностей вполне достаточно для описания большинства возможностей графического интерфейса. Ниже приведён пример диаграммы состояний с двумя переменными и одной поддиаграммой. 104
Start State
State(Normal) mpCWorkshop
Connect to Server
Activity Connect to Server
Exit
End State
Поддиаграмма главной диаграммы, связанная с объектом типа Activity "Connect to Server": State(Normal) Start St t
Close
Connect to S Connect
End St t
[ server=1 ] / connect:=1
OK Decisio [ server=0 ] mpCWorkshop
Cancel
State(Normal)
В этом примере используются две переменных – ‘server’ и ‘connect’. Значение переменной ‘server’ определяет состояние сервера (значение «1» означает, что сервер запущен, значение «0» означает, что сервер не запущен), а переменная 'connect' определяет наличие соединения с сервером (значение «1» означает, что есть соединение с сервером, а значение «0» означает, что соединения с сервером нет). Соответственно, интерпретировать эту диаграмму следует так: 1. Переход по событию ‘Connect to Server’ из управляющего состояния ‘mpCWorkshop’ означает переход в поддиаграмму, соответствующую объекту типа Activity ‘Connect to Server’, и дальнейший переход к состоянию ‘Connect to Server’. 2. Переход по событию ‘Close’ из управляющего состояния ‘Connect to Server’ ведёт в конечное управляющее состояние на этой поддиаграмме, что означает переход из Activity ‘Connect to Server’ на главной диаграмме в управляющее состояние mpCWorkshop. 3. Переход по событию ‘Connect’ из управляющего состояния ‘Connect to Server’ ведёт в объект типа Decision, в котором происходит разветвление в зависимости от значения переменной ‘server’. 105
a) Если сервер запущен (‘server=1’), то устанавливается соединение (‘connect:=1’), и, как и в случае 2, переход в конечное управляющее состояние этой поддиаграммы ведёт в управляющее состояние mpCWorkshop главной диаграммы. b) Если сервер не запущен (‘server=1’), то переход ведёт в управляющее состояние mpCWorkshop этой поддиаграммы (это модальный диалог-предупреждение о том, что соединение не может быть установлено). Из этого управляющего состояния возможны два перехода – по событию ‘OK’ переход ведёт в управляющее состояние ‘Connect to Server’, а по событию ‘Cancel’, как и в случае 2, переход ведёт в управляющее состояние mpCWorkshop главной диаграммы. 4. Переход по событию ‘Exit’ из управляющего состояния ‘mpCWorkshop’ ведёт в конечное управляющее состояние главной диаграммы, что означает переход к начальному управляющему состоянию этой диаграммы и дальнейший переход в состояние mpCWorkshop. Итак, можно составить набор правил, определяющих переходы между состояниями: Переход между состояниями осуществляется по событию и может состоять из нескольких переходов между управляющими элементами на диаграмме, к каковым относятся объекты типа State(Normal), Start State, End State, Activity, Decision, а также переход в поддиаграмму или возвращение из неё. Событие есть непустое имя перехода. Событие есть у всех переходов, начинающихся в управляющих элементах типа State(Normal), и только у них. Переход из управляющего состояния типа State(Normal) происходит по событию. Переход из управляющего состояния типа Start State, End State, Activity, Decision происходит без события. Переход между состояниями начинается и заканчивается только в управляющих элементах типа State(Normal). Переход между состояниями атомарный. Каждый переход между управляющими элементами может иметь предусловия и действия. Наличие пустого предусловия по умолчанию означает его истинность. Пустое действие означает отсутствие изменения значений переменных. Каждый переход может иметь несколько предусловий, разделённых оператором «,». Каждый переход может иметь несколько действий, разделённых оператором «,».
106
Переходы детерминированы, то есть не может существовать двух переходов с одинаковым событием (или оба без события), начинающихся в одном управляющем состоянии и обладающих предусловиями, которые допускают одновременное выполнение. На каждой диаграмме должны существовать начальное и конечное (не обязательно одно) управляющие состояния (на главной диаграмме конечное состояние может не существовать). Поддиаграммы есть у объектов типа Activity, и только у них. Если переход из какого-либо управляющего элемента ведёт в управляющий элемент типа Activity, это означает переход к начальному управляющему элементу поддиаграммы, соответствующей элементу типа Activity, и дальнейший переход без события. Если переход из какого-либо управляющего элемента ведёт в конечный управляющий элемент какой-либо поддиаграммы, то это означает переход к управляющему элементу типа Activity, которому соответствует эта поддиаграмма, и дальнейший переход из этого элемента без события. Если переход из какого-либо управляющего элемента ведёт в управляющий элемент типа Decision, это означает продолжение перехода от этого элемента без события. Дальше диаграмма, составленная по таким правилам, подаётся на вход генератору тестов. Кроме этого, с каждым объектом на диаграмме (модель, диаграммы, управляющие элементы, переходы) связаны требования (спецификации) к данному объекту. Эти требования размещаются в поле Documentation, предусмотренное Rational Rose для каждого объекта на диаграмме. Эти требования записываются в виде инструкций на языке SQABasic, которые выполняет Rational Robot. В большинстве случаев с переходами связаны инструкции, означающие какие-то действия пользователя, а с управляющими элементами связаны инструкции, означающие требования к данному состоянию, которые может проверить Rational Robot. По сути, данный набор инструкций реализует оракул этого состояния.
3.2. Генератор тестов Генератор строит по диаграмме состояний набор тестов. Условием окончания работы генератора является выполнение тестового покрытия. В данном случае в качестве покрытия было выбрано условие прохода по всем рёбрам графа состояний, то есть выполнения каждого доступного тестового воздействия из каждого достижимого состояния. Структурно генератор тестов состоит из следующих компонентов: Интерпретатор; Итератор; Обходчик; 107
Компилятор. Интерпретатор осуществляет переход между состояниями в соответствии с введёнными ранее правилами. В случае успешного перехода он останавливается и возвращает указатель на тот управляющий элемент, в котором он остановился, в случае неудачи он останавливается и выводит сообщение о возникшей ошибке (обычно это несоответствие введённым правилам). Для замыкания диаграммы вводится дополнительное условие: если переход из какого-либо управляющего элемента ведёт в конечный управляющий элемент главной диаграммы, то это означает переход к её начальному управляющему элементу и дальнейший переход без события. Связь интерпретатора с обходчиком выполняет итератор переходов. В соответствии с выбранным алгоритмом обхода итератор возвращает событие, переход по которому ещё не был совершён из данного состояния. Если все переходы были совершены, итератор возвращает пустое событие. Обходчик есть ключевой компонент генератора тестов. Чтобы построить тестовый набор, он строит обход ориентированного графа состояний, проходя по всем дугам графа. Алгоритм его работы взят из [7, 8, 9]. Прибегнуть к специальному алгоритму обхода ориентированного графа заставило то обстоятельство, что граф состояний скрыт на диаграмме действий изображён граф, состоящий из управляющих элементов. Это значит, что заранее неизвестны все возможные состояния графа, поэтому мы и воспользовались неизбыточным алгоритмом обхода ориентированного графа. В процессе работы обходчик вызывает итератор переходов для получения событий, соответствующих не пройденным переходам, и интерпретатор событий, последовательность которых формируется в соответствии с алгоритмом. Обходчик останавливается на любом графе, вынося вердикт, совершен обход или нет. В том случае, если нет, то выводится отладочная информация о причинах, не позволивших его совершить, чтобы можно было быстро их устранить (обычно это либо несоответствие правилам, либо тупиковое состояние). Компилятор (здесь термин “компиляция” подразумевается в его “не программистском” смысле – сборка). Как уже говорилось, с каждым объектом на диаграмме (модель, диаграммы, управляющие элементы, переходы) связан некоторый набор инструкций на языке SQABasic, которые выполняет Rational Robot. В большинстве случаев с переходами связаны инструкции, означающие какие-то действия пользователя, а с управляющими элементами связаны инструкции, означающие оракул этого состояния. В процессе обхода графа состояний пройденный путь компилируется в тестовые файлы, содержащие инструкции для Rational Robot. Компиляция происходит путём записи информации, связанной с каждым пройденным объектом. Далее приведен фрагмент вызова функции записи документации. Автоматическая генерация тестов по диаграммам действий имеет следующие преимущества перед остальными подходами к тестированию графического интерфейса: 108
Спецификация автоматически интерпретируется (тем самым она проверяется и компилируется в набор тестов). Если какая-то функциональность системы изменилась, то диаграмму состояний достаточно изменить в соответствующем месте, и затем сгенерировать новый тестовый набор. Фактически, это снимает большую часть проблем, возникающих при организации регрессионного тестирования. Гарантия тестового покрытия. Эта гарантия даётся соответствующим алгоритмом обхода графа состояний. Генератор тестов есть программа (script), написанная на языке ‘Rational Rose Scripting language’(расширение к языку Summit BasicScriptLanguage). В Rational Rose есть встроенный интерпретатор инструкций, написанных на этом языке, посредством которого можно обращаться ко всем объектам модели (диаграммы состояний).
для управления отчётами о тестировании, и в случае ошибочной ситуации выдаётся достаточная информация об ошибке для её нахождения и исправления. Типичный screenshot программы Rational TestManager представлен ниже на рисунке.
3.3. Тестовый набор В процессе построения обхода генератор тестов компилирует набор тестов инструкции на языке SQABasic. Эти инструкции есть чередование тестовых воздействий и оракула свойств объектов, соответствующих данному состоянию. Для того, чтобы продолжать тестирование, когда один тест не прошёл, в генератор тестов встроена возможность выбора – генерировать один большой тест или набор атомарных тестов. Атомарный тест – тот, который не требует приведения системы в состояние, отличное от начального состояния. В связи с наличием ограничения инструмента прогона тестов на тестовую длину, в тесты после каждой законченной инструкции вставляется строка разреза. Во время прогона по этим строкам осуществляется разрез теста в случае, если его длина превышает допустимое ограничение. После прохождения части теста до строки разреза продолжается выполнение теста с первой инструкции, следующей за строкой разреза. Нарезку и сам прогон тестов осуществляет прогонщик тестов.
4. Апробация 4.1. Поставленная задача Разработка и реализация методов автоматизированной генерации набора функциональных тестов для GUI mpC Workshop [10, 11].
3.4. Прогонщик тестов В качестве прогонщика тестов мы используем Rational Robot, который выполняет сгенерированные наборы инструкций. В случае удачного выполнения всех инструкций выносится вердикт – тест прошёл. В противном случае, если на каком-то этапе выполнения теста поведение системы не соответствует требованиям, Robot прекращает его выполнение, вынося соответствующий вердикт – тест не прошёл.
3.5. Анализ и отображение результатов Когда Rational Robot закончит выполнение тестов, результаты тестирования будут отображены в наглядном виде в Rational TestManager[6]. Это программа 109
mpC Workshop – это среда для разработки приложений для параллельных вычислений на языке mpC [12, 13, 14] с функциональностью графического 110
интерфейса, типичной для такого типа систем. Выше приведён типичный screenshot mpCWorkshop. Для тестирования базовой функциональности была составлена базовая диаграмма состояний и переходов mpC Workshop. Ниже приведены фрагменты этой диаграммы (включая поддиаграммы). Главная диаграмма выглядит следующим образом:
uEsc
Connect uConnect[ connect=0 ] tServer uDisconnect[ connect=1,exec=0,build=0,compile=0 ] / connect:=0,connectbroken:=0 uClean VPM[ connect=1 ] [ exec=0 ][ build=0 ] [ compile=0 ] [ build=1 ] [ compile=1 ] [ exec=1 ] Build Interrupt
mpCWorkshop
Workspace / exec:=0 go Workspace[ projectopen=1 ]
uCompile[ projectopen=1,connect=1,exec=0,build=0,compile=0,openfilempcifproject=1 ] / compile:=1,compile:=0
go mpCWP
mpcWorkshop
mpCWP
Fin ishe d Finished
uRebuild[ projectopen=1,connect=1,exec=0,build=0,compile=0 ] / build:=1,build:=0 go mpCW B
umpcd[ connect=0 ]
uCleanTarget[ connect=1,projectopen=1,exec=0,build=0,compile=0 ] / projectbuild:=0
[ exec=1 ] mpCWB
[ exec=0 ]
mpcd
uStop Execution[ exec=1 ] / exec:=0 uExecute[ projectopen=1,connect=1,exec=0,build=0,compile=0 ] [ projectbuild=0 ]
go mpCWF mpCWF
[ projectbuild=1 ] / exec:=1 [ exec=2 ] / connect:=0,projectopen:=0,connectbroken:=0
С Activity “Connect to Server” связана следующая поддиаграмма: Такие объекты типа Activity, как “mpCWP”, “mpCWB”, “mpCWF”, “mpcd”, “Finished”, “Workspace” означают наличие под собой поддиаграмм. Ниже приведены поддиаграммы, соответствующие объектам “mpCWP” и “mpCWB”. Connect uEsc uNew...[exec=0,build=0,compile=0]
New Project
uOpen...[exec=0,build=0,compile=0]
Open Project
mpCWorkshop Project
Alert uOk
uConnect [ mpcd=0 ]
uSave[projectopen=1]
uClose Project[projectopen=1,exec=0,build=0,compile=0]/projectopen:=0
[ mpcd=1 ] / connect:=1 uCancel
Как видно из рисунков, на этих поддиаграммах тоже есть поддиаграммы. Фактически, каждый объект типа Activity скрывает под собой часть функциональности системы. Под “mpCWB” содержится описание функциональности, связанной с работой с сервером, под “mpCWP” содержится
delProject[ projectexist=1,projectopen=0,exec=0,build=0,compile=0 ] / j t i t 0
111
112
описание функциональности, связанной с работой с проектом, под “mpCWF” содержится описание функциональности, связанной с работой с файлами, и т.д. В этой базовой диаграмме используется 12 переменных с начальными значениями, приведёнными ниже: connect, 0 connectbroken, 0 mpcd, 0 exec, 2 build, 0 compile, 0 projectopen, 0 projectbuild, 0 projectexist, 0 projectpathenter, 0 projectpathcorrect, 0 openfilempcifproject, 0 Практически с каждым объектом на диаграмме связаны тестовые инструкции на языке SQABasicScript. Ниже в таблице приведены примеры этих инструкций, связанных с управляющими состояниями и переходами: Window SetContext, "Caption={mpC Worksho*}; MpCWorskState=Enabled", "Activate=1; Status=MAXIMIZED" hop go mpCWP Connect
Result=ModuleVP (Exists, "Name=mpcworkshop.exe", "ExpectedResult = PASS") InputKeys "%p" 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", "" PushButton Click, "Text=Connect;State=Enabled"
uConnect И так далее... Генератор тестов для данной диаграммы генерирует тесты за время порядка 1й минуты. Суммарный размер выходных тестов 1541024 байта (1,5 Мб). 113
На данной диаграмме изображено 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-ти минут. Скорость ввода символов и нажатия на объекты системы ограничена возможностями инструмента, но, тем не менее, она велика, и человеку практически невозможно совершать действия с такой скоростью.
4.2. Практические результаты В ходе выполнения работы был обнаружен ряд ошибок в графическом интерфейсе mpC Workshop. Наибольшее количество ошибок было найдено на этапе составления спецификации системы. Это характерная черта использованного метода составления спецификации – по реальной работе системы. То есть на момент формализации графического интерфейса системы продукт mpC Workshop был уже разработан и реализован, таким образом сам процесс составления спецификации был уже своеобразным тестированием системы. На этом этапе было найдено порядка 10-15 ошибок в системе. После того, как была составлена спецификация основной функциональности тестируемой системы, был сгенерирован тестовый набор, ей соответствующий. После его прогона была выявлена одна трудноуловимая ошибка. 114
4.3. Обработка ошибочной ситуации В результате применения предложенного подхода была поднята некоторая проблема. А именно, как продолжать прогон тестов после его останова во время ошибочной ситуации. На данный момент существует несколько вариантов её разрешения: Ошибка исправлена разработчиками в разумные сроки. Сознательно специфицируется именно ошибочное поведение системы. Данная функциональность вообще не тестируется (например, у соответствующего перехода ставится невыполнимое предусловие). Тестирование продолжается со следующего теста. При этом нужно привести систему в состояние, соответствующее начальному состоянию следующего теста.
5. Развитие подхода Предложенный подход генерации набора тестов, на наш взгляд, дальше можно развивать в следующих направлениях: Внедрение асинхронности. Диаграммы действия UML позволяют моделировать асинхронное поведение системы. Использование других инструментов для специфицирования системы и прогона тестов. Внедрение зависимости инструкций, соответствующих объекту на диаграмме, от набора значений переменных текущего состояния (либо печатать в трассу сами действия, либо подставлять значения переменных в соответствующие поля инструкций). Использование более полной проверки состояний объектов системы с помощью Verification Point – данная возможность используется сейчас слабо. Использование инструкций, связанных с диаграммой, во всех управляющих элементах этой диаграммы для сокращения их дублирования. Использование функций от набора значений переменных в управляющих элементах диаграммы для проверки состояния объектов системы. Использование не самих наиболее часто встречающихся функций, а только их вызова – для сокращения дублирования тестового кода. Предоставление тестеру, составляющему спецификацию системы, некоторого набора шаблонов, позволяющих быстро сгенерировать соответствующую диаграмму вместе с соответствующими тестовыми инструкциями.
Литература 1. С.Канер, Д.Фолк, Е.К.Нгуен, Тестирование программного обеспечения. 2. Э.Дастин, Д.Рэшка, Д.Пол, Автоматизированное тестирование программного обеспечения. 3. Ray Robinson, AUTOMATION TEST TOOLS, Date Created: 1st March 2001, Last Updated: 11th Sept 2001. http://tester.com.ua/test_types/docs/automation_tools.doc 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
6. Заключение Предложенная в настоящей работе методика автоматизации генерации тестов для GUI была разработана и апробирована с августа 2003 г. по май 2004 г. в ходе выполнения проекта по тестированию mpC Workshop. Исследование мето-дов автоматизированной генерации тестов для GUI сейчас активно развивается. 115
116
особенностям TinyOS; тем самым доказывается применимость UniTesK к тестированию указанного класса ПО. Вывод подкрепляется результатами пилотного проекта по тестированию ПО под управлением TinyOS средствами CTesK [3] – реализации UniTesK для языка С. Статья построена следующим образом. Раздел 2 содержит краткое введение в TinyOS. В разделе 3 дано общее описание метода разработки тестовых наборов UniTesK. Раздел 4 посвящен применению UniTesK к тестированию встроенных систем. В разделе 5 приведены результаты пилотного проекта по тестированию компонента Attr. В разделе 6 обсуждается вопрос применимости формальных спецификаций UniTesK к аналитической верификации встроенного ПО. Раздел 7 – Заключение.
Применение UniTesK к тестированию встроенных систем Н.В. Пакулин ([email protected]) Аннотация. В статье обсуждаются вопросы применимости технологии тестирования UniTesK к функциональному тестированию программного обеспечения встроенных систем на примере сенсорных сетей под управлением TinyOS. В работе выделены сходство и отличие указанного класса ПО от систем, для которых хорошо разработаны методики применения UniTesK. Представлены результаты опытного проекта по исследованию применимости CTesK к тестированию ПО под управлением TinyOS.
1. Введение В статье рассматриваются вопросы тестирования встроенных систем с очень ограниченными ресурсами, прежде всего с небольшим объёмом оперативной памяти. Практически все доступные ресурсы используются для поддержания работы приложения на устройстве. Ограниченные ресурсы делают невозможным развёртывание тестовой системы на устройстве, поэтому существующие технологии тестирования необходимо адаптировать для тестирования встроенного программного обеспечения. В данной работе вопросы тестирования встроенного программного обеспечения рассматриваются на примере тестирования приложений для сенсорных сетей под управлением TinyOS [1]. TinyOS используется для программирования устройств, снабжённых датчиками (температуры, влажности, освещённости и т.п.) и маломощным коротковолновым приёмопередатчиком для периодической передачи показаний датчиков. TinyOS служит хорошим примером современных приложений встроенных систем с очень ограниченными ресурсами. Объём оперативной памяти, доступной типичному устройству TinyOS, составляет 4-16 Кбайт. В статье обсуждаются возможности применения технологии тестирования UniTesK 2 к разработке тестовых наборов для программного обеспечения сенсорных сетей под управлением TinyOS. Указанный класс программного обеспечения имеет много общего с теми системами, для которых применимость UniTesK хорошо исследована – программными интерфейсами, реализациями телекоммуникационных протоколов. В статье показано, как перенести опыт тестирования программных интерфейсов и реализаций протоколов на задачу тестирования TinyOS, и как адаптировать UniTesK к 117
2. Краткий обзор TinyOS TinyOS — это операционная система, разработанная для сетевых встроенных приложений. Программная модель TinyOS адаптирована для приложений, основанных на событиях, и отличается очень небольшим объёмом занимаемой памяти (базовые функции ОС занимают порядка 400 байт, включая данные и код). В качестве важнейших особенностей TinyOS можно выделить: компонентную архитектуру; простую модель параллельности, основанную на событиях; расщеплённые операции; специализированный язык разработки. Компоненты TinyOS и приложения разрабатываются на языке nesC [4], [5] – специализированном расширении языка С, которое поддерживает указанные особенности TinyOS.
2.1. Компонентная архитектура TinyOS Компонентная архитектура TinyOS предоставляет набор повторно используемых компонентов. Компоненты собираются в приложение посредством спецификаций связывания интерфейсов; спецификации связывания не зависят от реализации компонентов. В TinyOS различают два вида компонентов: модули и конфигурации. Модули содержат состояние и операции. Конфигурации представляют собой наборы модулей и конфигураций, связанные в соответствии со спецификацией связывания. Приложение можно рассматривать как наибольшую конфигурацию, развёрнутую на устройстве. Благодаря разделению функций TinyOS на отдельные компоненты разработчик приложения может использовать только те компоненты, которые нужны ему для решения задачи на заданной аппаратуре, тем самым минимизируя затраты ресурсов на устройстве. 118
Большая часть компонентов, поставляемых с TinyOS, представляет собой программные модули. Интерфейсы доступа к аппаратуре также имеют вид компонентов и с точки зрения разработчика приложения неотличимы от обычных программных модулей. Концепция компонентов отражена в языке nesC.
2.2. Параллельность, события и задачи В TinyOS различаются два контекста исполнения — события и задачи. Задачи представляют собой способ отложенного выполнения вычислений. События соответствуют асинхронным операциям, преимущественно прерываниям аппаратуры. Выполнение задачи не может быть прервано выполнением другой задачи (но может быть прервано событием). Выполнение задач управляется планировщиком. Компоненты могут ставить задачи в очередь, планировщик запускает выполнение задач в моменты времени, когда не обрабатываются события. Обработка событий может прерываться обработчиками других событий, но не может прерываться переключением на исполнение задач. Для обеспечения достаточной степени реактивности приложения продолжительные вычисления, которые могут возникнуть при обработке событий, рекомендуется ставить в очередь задач.
2.2.1. Расщеплённые операции В силу того, что задачи выполняются без переключения на другие задачи, в TinyOS рекомендуют разделять операции на две — запрос и завершение. Так реализован доступ к большинству аппаратных функций. Запрос возвращает управление почти мгновенно, завершение операции сигнализируется событием1. Примером расщеплённой операции может служить отправка пакета в сеть: компонент запускает передачу сообщения по радиосети, по завершении передачи компонент получает событие sendDone. Проблема конкуренции за ресурс, как правило, решается путем явного запрещения параллельных запросов. В примере с отправкой сообщений, в том случае, если радиопротокол не поддерживает параллельную отправку сообщений (например, в разных частотных диапазонах), запросы на отправку сообщений будут отклоняться до окончания текущей передачи2. Простая модель параллельности в TinyOS позволяет реализовать параллельные вычисления с небольшими накладными расходами, если сравнивать с многопотоковой параллельностью, в которой драгоценная память тратится на стек заблокированных нитей, ожидающих доступ к занятому устройству. Тем 1
Ряд быстрых операций, например, переключение светодиодов, выполняется по запросу целиком, без события завершения. 2 Разумеется, могут быть альтернативные реализации протоколов, которые поддерживают очереди исходящих сообщений.
119
не менее, в TinyOS также возможны различные ошибки, характерные для параллельного исполнения, включая тупиковые ситуации, конфликты при доступе к памяти.
2.2.2. Атомарность В nesC рассматриваются два контекста исполнения — синхронный и асинхронный. Синхронный контекст соответствует выполнению задач TinyOS, асинхронный – обработке событий в TinyOS. Так как выполнение задач в TinyOS не прерывается другими задачами, то синхронный код считается атомарным по отношению к синхронному коду. Для задания атомарных участков в асинхронном коде есть специальная синтаксическая структура atomic.
2.3. Язык nesC Язык nesC отражает основные концепции TinyOS. Язык основан на концепции компонентов и естественным образом реализует модель событий и задач. Язык nesC реализован как расширение языка C. В него добавлены синтаксические конструкции для описания компонентов TinyOS, событий и доступа к переменным состояния. В языке nesC допустимы только статические конструкции, отсутствуют динамическое выделение памяти и указатели на функции. Компонентная модель nesC и параметризация компонентов покрывают большинство ситуаций, для которых в традиционном С требуется использование динамической памяти и указателей на функции.
Interface User
Provider command return
event return
Рис. 1. Интерфейс в nesC. Приложение nesC собирается из компонентов. В языке nesC различаются интерфейсы и реализация компонентов. Компоненты предоставляют и
120
используют интерфейсы. Единственный способ обратиться к компоненту — вызвать интерфейсный метод компонента. Интерфейс nesC состоит из спецификаций методов. Методы делятся на две группы: команды и события. Команды соответствуют вызовам компонента со стороны окружения, события обозначают вызовы окружения из компонента. Это проиллюстрировано на рисунке 1. Различие между предоставлением интерфейса и использованием интерфейса заключается в том, что поставщик интерфейса реализует команды, а пользователь интерфейса реализует события. interface ADC { command result_t getData(); event result_t dataReady(uint16_t data); }
Рис. 2. Пример интерфейсного типа. В языке nesC предусмотрена концепция интерфейсного типа. Интерфейсный тип позволяет компактно записывать одинаковые интерфейсы. Интерфейс на рисунке 2 является примером представления расщеплённой операции в языке nesC. Этот интерфейс предоставляет компоненты, которые осуществляют сбор данных с датчиков. Команда getData инициирует опрос датчика и возвращает управление. Готовые данные будут доставлены как параметр data события dataReady. Двунаправленные интерфейсы удобны для описания обработчиков аппаратных прерываний. Для сравнения, в языках с однонаправленными процедурными интерфейсами требуется либо постоянно опрашивать аппаратуру, либо разделять интерфейс на два — операции с аппаратурой и соответствующие прерывания.
2.3.1. Реализация компонентов в языке nesC В языке nesC поддерживаются два вида компонентов — модули и конфигурации. Модули содержат программный код и реализуют один или несколько интерфейсов. Конфигурации используются для связывания других компонентов в одно целое, соединяя компоненты в соответствии с правилами связывания. Каждое приложение nesC описывается некоторой конфигурацией верхнего уровня, которая связывает все используемые компоненты. Язык nesC предоставляет богатый набор правил связывания. Можно связывать:
таких наборов, TinyDB [6], позволяет организовать доступ к показаниям сенсоров на устройствах в сети сенсоров в виде операций чтения/записи в некоторой виртуальной базе данных.
3. Краткий обзор UniTesK Данный раздел содержит беглое введение в UniTesK. Более подробное описание можно найти в [2]. С 1994 года в ИСП РАН активно разрабатываются методы и инструменты тестирования программного обеспечения на основе формальных методов. В 1994-1999 годах по контрактам с Nortel Networks в ИСП РАН был разработан и активно использовался метод тестирования KVEST [7]. Этот метод отличался от обычных методов тестирования индустриального программного обеспечения тем, что в KVEST использовались формальные спецификации в форме пред- и постусловий для построения тестовых оракулов, а также модель конечных автоматов для построения тестовых воздействий. Применение KVEST показало, что формальные методы можно с большим успехом применять при тестировании индустриального программного обеспечения. Опыт применения KVEST также показал, что использование академических языков формальных спецификаций и специальных языков описания тестов препятствует встраиванию инструментов, основанных на этих языках, в процесс разработки ПО. Чем ближе язык спецификаций к языку, на котором ведется разработка, тем проще разработчикам писать спецификации и тесты [8]. UniTesK разрабатывался на основе опыта, полученного при разработке и применении KVEST. Рассмотрим основные особенности UniTesK: Разделение построения тестовых воздействий и проверки правильности поведения целевой системы. Тестовые воздействия строятся в тестовых сценариях, а проверка правильности поведения целевой системы производится в тестовых оракулах; Автоматизированное построение тестовых воздействий; Представление функциональных требований к целевой системе в виде формальных спецификаций; Для записи формальных спецификаций используется язык, «близкий» к языку, на котором разработана целевая система; Автоматическая генерация тестовых оракулов из спецификаций;
одну или несколько команд с одной или несколькими командами, одно или несколько событий с одним или несколькими событиями;
Оракулы и реализация связаны посредством тонкой прослойки медиаторов;
один или несколько интерфейсов с одним или несколькими интерфейсами одного и того же типа. Для TinyOS уже разработаны несколько наборов компонентов, предназначенных для создания специализированных приложений. Один из 121
Язык описания тестовых воздействий «близок» к языку, на котором разработана целевая система; Автоматически генерируются критерии качества покрытия требований; 122
Автоматически производится оценка качества покрытия требований при прогоне тестов.
3.1. Оракулы и спецификации Оракулы – это процедуры, которые проверяют выполнение ограничений, наложенных на поведение целевой системы. В UniTesK оракулы полностью автоматически генерируются из описаний ограничений. В UniTesK ограничения описываются преимущественно в форме имплицитных спецификаций стимулов и реакций. Кроме того, часть ограничений представляется в виде ограничений на значения типов (инварианты типов) и ограничений на значения глобальных переменных (инварианты глобальных переменных).
систему. Тестовый сценарий в процессе работы обходит все состояния и в каждом состоянии оказывает все перечисленные воздействия. На рисунке 3 изображен пример тестового сценария, в котором насчитывается три состояния (S1, S2 и S3) и семь воздействий (I1_1, I1_2, I2_1, I2_2, I3_1, I3_2, I3_3). При тестировании тестовый сценарий побывает в каждом состоянии и пройдет по каждой дуге. I1_1
I1_2 S2 I2_1 I2_2 I3_1
S1
3.1.1. Стимулы и реакции В UniTesK различаются два вида операций, на которые накладываются ограничения, – стимулы и реакции. Стимул – это воздействие, которое совершается окружением над системой. Реакция – это воздействие со стороны системы на окружение.
I3_3
S3 I3_2 Рис. 3. Пример автомата с тремя состояниями и семью переходами.
3.1.2. Спецификация Имплицитные спецификации стимулов и реакций состоят из пред- и постусловий. Предусловие для стимула содержит требования к тому, в каком состоянии и с какими параметрами можно оказывать воздействие на целевую систему. Предусловие для реакции задаёт ограничения на состояния, в которых система может демонстрировать реакции. После воздействия целевая система может продемонстрировать реакции и/или перейти в другое состояние. Постусловие для стимула определяет, допустимо ли изменение состояния, а постусловия для реакций определяют допустимость продемонстрированного поведения целевой системы. Состояние целевой системы моделируется набором структур данных, называемых абстрактным состоянием. В пред- и постусловиях используется информация, содержащаяся в абстрактном состоянии, для вынесения вердикта.
3.2. Тестовые сценарии Тестовый сценарий в UniTesK определяет последовательность воздействий, которые оказываются на целевую систему. В качестве теоретической основы для построения тестового сценария в UniTesK выбрана модель конечных автоматов. Тестовый сценарий обладает собственным состоянием, которое, как правило, вычисляется на основе состояния целевой системы. В каждом состоянии задаются воздействия, которые в данном состоянии можно оказать на целевую 123
К сожалению, для большинства целевых систем невозможно получить описание тестового сценария из спецификаций полностью автоматически, без помощи человека. Можно указать следующие причины: Число состояний целевой системы, как правило, очень велико; Число воздействий, которые можно оказать на целевую систему в каждом состоянии, как правило, тоже очень велико. Вместе с тем, число групп разных состояний, как правило, вполне обозримо. Но невозможно автоматически определить критерий различения состояний, здесь нужна помощь человека – разработчика тестов. Число различных воздействий на целевую систему также много меньше общего числа воздействий. Из формальных спецификаций можно автоматически получить критерий различения воздействий, но автоматически построить воздействия можно только в простейших случаях. В UniTesK реализован компромиссный подход к разработке тестовых сценариев. Разработчик тестового сценария пишет процедуру различения состояний целевой системы, тем самым определяя классы эквивалентности состояний целевой системы. Каждый класс эквивалентности определяет одно состояние автомата тестового сценария. Разработчик тестового сценария также задает процедуру, которая строит всевозможные тестовые воздействия, то есть переходы автомата тестового сце124
нария. Эти воздействия фильтруются в соответствии с одним из сгенерированных критериев покрытия. Фильтры отсеивают избыточные воздействия, то есть воздействия, не улучшающие уже достигнутого покрытия. Фильтрация воздействий существенно упрощает написание процедур перебора воздействий. По предоставленным описаниям динамически строится граф состояний автома-та тестового сценария. При обходе графа автоматически отслеживается покры-тие требований, описанных в спецификациях для генерации отчета о покрытии.
3.3. Медиаторы Под медиаторами в UniTesK понимается промежуточный слой между оракулами и реализацией. Необходимость введения медиаторов вызвана тем, что в UniTesK воздействия на целевую систему и реакции целевой системы описываются в терминах модели, содержащейся в спецификациях. Перед тем, как оказать воздействие на целевую систему, необходимо перевести параметры воздействия из модельного представления в представление реализации, а после того, как воздействие оказано, необходимо перевести реакции целевой системы в модельное представление. Также необходимо отобразить изменения состояния целевой системы в абстрактном состоянии. В UniTesK медиаторы осуществляют необходимые преобразования, оказывают воздействия на целевую систему и собирают реакции целевой системы.
3.4. CTesK CTesK – это реализация UniTesK для языка С. CTesK поддерживает разработку спецификаций на спецификационном расширении языка С – SeC (Specification extension of C language). SeC – это ANSI C, к которому был добавлен ряд конструкций, характерных для академических языков формальных спецификаций. В частности, в SeC реализованы пред- и постусловия, инварианты типов данных и глобальных переменных, описатели доступа (access descriptors). Из спецификаций на языке SeC генерируется код на языке С, который затем компилируется обыкновенным компилятором С (в нашем случае MS VC 6.0). В CTesK реализована архитектура тестового набора UniTesK – тестовые сценарии, оракулы, медиаторы. Реализована поддержка тестирования систем с отложенными реакциями.
4. Применение UniTesK к тестированию встроенного ПО Применение UniTesK для тестирования встроенного ПО имеет много общего с применением UniTesK для тестирования сетевых протоколов: Стимулы и отложенные реакции: в протоколах имеются входящие и исходящие пакеты, во встроенном ПО – процедурные вызовы и прерывания/события; 125
Асинхронные операции: пакеты в реализации протокола обрабатываются, как правило, асинхронно; во встроенных системах большинство прерываний происходит также асинхронно; Разделение операций на пары "запрос"/"ответ"; Непроцедурные стимулы и реакции; Наличие "слоёв" в реализации, разделение объектов тестирования по "уровням"; Необходимость в организации удалённого тестирования. Помимо сходства имеются определённые различия: Непроцедурные стимулы встроенного ПО определяются прерываниями аппаратуры, поэтому непроцедурные тестовые воздействия сложнее организовать, чем при тестировании протоколов; Непроцедурные реакции связаны с воздействиями на аппаратуру; такие реакции сложнее зарегистрировать, чем реакции протокольных стеков; Более тесная связь между асинхронными стимулами: в реализациях протоколов пакеты обрабатываются практически независимо, нехарактерны ситуации, когда параллельная обработка двух и более пакетов влияет на результат обработки отдельного пакета; для встроенного ПО, напротив, асинхронность играет значительную роль, и эта особенность существенно влияет на разработку спецификаций и тестов; Встроенное ПО работает в условиях очень ограниченных ресурсов, поэтому размещение тестовой системы на одном устройстве с объектом тестирования невозможно; коммуникационные возможности устройств, как правило, невелики, поэтому организация транспорта стимулов и реакций может оказаться нетривиальной задачей; Поведение объекта тестирования в симуляторе и на реальном устройстве могут различаться; это необходимо учитывать при анализе результатов тестирования в симуляторе. Исследование применимости методологии UniTesK к тестированию встроенного программного обеспечения проводилось на примере TinyOS — системы для разработки встроенного программного обеспечения для беспроводных сетей сенсоров. Исследование показало, что UniTesK пригоден для функционального тестирования встроенного программного обеспечения.
4.1. Функциональное тестирование компонентов TinyOS С тестированием компонентов можно связать несколько задач: Тестирование обычного программного модуля; Тестирование программного модуля, связанного с аппаратурой; Тестирование конфигурации. Учитывая особенности программирования в TinyOS, можно выделить следующие задачи: Тестирование синхронного кода; 126
Тестирование асинхронного кода; Тестирование тонкой прослойки над аппаратурой; Тестирование расщеплённых операций. Тестирование программного модуля на языке nesC имеет много общего с тестированием прикладных интерфейсов на других языках, например, С или Java. Основное отличие заключается в тестировании асинхронного кода.
некоторого датчика через промежуточный менеджер атрибутов, который абстрагирует детали обращения к конкретным атрибутам. Результат, возвращаемый в ответ на запрос, зависит от результатов "переговоров" менеджера атрибутов и собственно атрибута. Query
Attribute
getAttr
4.1.1. Разработка формальной спецификации Формальная спецификация разрабатывается средствами CTesK. В CTesK имеются два вида спецификационных функций: стимулы и реакции. В языке nesC есть два вида интерфейсных функций — команды и события. Интерфейсные функции nesC могут быть как стимулами, так и реакциями. Действительно, команда в nesC является стимулом для поставщика интерфейса и реакцией для пользователя интерфейса. Событие в nesC является реакцией для поставщика интерфейса и стимулом для пользователя интерфейса. Следующее отличие SeC от nesC заключается в том, что реакции в SeC однонаправлены, соответствуют передаче данных от источника получателю. Интерфейсные функции могут иметь возвращаемые значения и обновляемые параметры (updates parameters), то есть представляют двунаправленный обмен данными. Такая ситуация проиллюстрирована на рисунке 4. ComponentUnderTest
AttrManager
getValue status status
Рис. 5. Взаимодействие на нескольких интерфейсах Специфика применения UniTesK к TinyOS связана и с асинхронным кодом. Вызов некоторой интерфейсной функции может быть прерван асинхронным вызовом, причём второй вызов может повлиять на результат первого вызова.
Environment
Environment
Dispatcher
Environment
Operation Стимул
Реакция
Operation return return
Реакция
Стимул
Рис. 6. Асинхронность в TinyOS
Рис. 4. Пара реакция/стимул. Ещё одна особенность, которая отличает тестирование компонентов TinyOS от традиционных областей применения UniTesK, заключается во взаимодействии по нескольким интерфейсам. В частности, для выполнения операции на одном интерфейсе, компонент может произвести операции на одном или нескольких других интерфейсах. На рисунке 5 представлен пример: опрос значения 127
Для того, чтобы адекватно промоделировать средствами UniTesK интерфейсные функции компонентов nesC, мы предлагаем разделить вызовы команд и событий nesC на две составляющие: вызов и возврат управления. При этом поток данных от окружения к целевому компоненту специфицируется как стимул, а обратный поток данных – как реакция. Данное решение позволяет единообразно моделировать расщеплённые операции и операции, которые возвращают управление немедленно.
128
Большинство компонентов TinyOS не связано с аппаратурой, поэтому все стимулы и реакции таких компонентов состоят из вызовов интерфейсных методов. По другому обстоят дела с компонентами, которые непосредственно взаимодействуют с аппаратурой. Для таких компонентов имеются непроцедурные стимулы — прерывания аппаратуры, и непроцедурные реакции — операции с "железом". С точки зрения разработки формальных спецификаций компоненты, взаимодействующие с аппаратурой, ничем особенным не отличаются. Непроцедурные стимулы можно моделировать спецификационными функциями-стимулами, а для описания воздействия на аппаратуру есть две стратегии: Ввести спецификационные функции-реакции, которые соответствуют обращению программного обеспечения к определённым аппаратным функциям; Включить модель состояния аппаратуры и представить изменения состояния аппаратуры в постусловии. Первая стратегия применима в тех ситуациях, когда возможно перехватывать обращения к аппаратуре. Вторая стратегия является уместной, когда есть возможность наблюдать изменения состояния аппаратуры. Конфигурации собираются из нескольких компонентов — модулей и более мелких конфигураций. Конфигурации не содержат собственного кода, все внешние интерфейсы конфигурации реализуются в компонентах, составляющих конфигурацию. Стимулы и реакции конфигурации составляются из стимулов и реакций внешних интерфейсов конфигурации. Если в состав конфигурации входят компоненты, взаимодействующие с аппаратурой, то к стимулам и реакциям конфигурации относятся также и операции с аппаратурой. С точки зрения методики разработка спецификаций для модулей и конфигураций не различаются.
тестируемый компонент различные значения изменяемых параметров и возвращаемых значений. Поскольку для тестирования представляет интерес проверка поведения целевого компонента при различных ответах окружения, в тестовом сценарии необходимо уметь перебирать ответы окружения. Наиболее естественно для этой задачи подходит использование в сценарных методах UniTesK итераторов и “stable”-переменных. На рисунке 7 схематично изображён сценарный метод, реализующий предложенный приём. Методы разработки тестовых сценариев для компонентов, взаимодействующих с аппаратурой, не отличаются от уже рассмотренных приёмов. Тестовый сцена-рий перебирает параметры запроса и значения, которые должна получить реа-лизация от аппаратуры. Вопрос доставки значений аппаратуры выходит за рам-ки разработки тестового сценария, и будет рассмотрен в разделе про медиаторы.
4.1.2. Тестовые сценарии для компонентов
При тестировании асинхронного кода необходимо создавать тестовые ситуации, в которых выполнение целевой функции прерывается, причём для систематического тестирования необходимо, чтобы можно было управлять тем, в каком месте исполнение будет прервано. Для реализации управляемого прерывания исполнения целевой процедуры мы предлагаем следующее: 1. Инструментировать код целевого метода: при инструментировании в код внедряются заглушки; когда исполнение доходит до заглушки, исполнение целевой функции может быть прервано. 2. В тестовом сценарии задаётся, в каких заглушках целевая функция будет прервана при прогоне теста, и какие функции прервут исполнение.
Особенности разработки тестовых сценариев UniTesK для TinyOS вытекают из уже упоминавшихся особенностей TinyOS и nesC: Разделение исполнения на синхронное и асинхронное; Взаимодействие по нескольким интерфейсам; Наличие расщеплённых операций; Наличие взаимодействия с аппаратурой. Если целевой компонент не вызывает другие компоненты для обработки запроса, то разработка тестового сценария или сценарного метода для такой операции ничем не отличается от тестирования обычного метода в UniTesK. Если целевой компонент для выполнения запроса обращается к окружению, то для тестирования необходимо подготовить соответствующие заглушки, имитирующие ответы от окружения. Задача заглушек — передавать в 129
bool scenario test_with_stubs() { iterate( /* перебор аргументов запроса */ ){ iterate( /* перебор ответов окружения */ ) { /* передаём тестовому агенту ответы окружения */ setup_env( ... ); /* Вызываем целевую операцию */ call_target_operation( /* аргументы */ );
} } return true;
Рис. 7. Пример перебора параметров запроса и ответов окружения.
4.1.3. Тестирование асинхронного кода
Во время написания статьи в симуляторе TinyOS прерывание исполнения не поддерживалось. Предложенный подход к симуляции асинхронного исполнения функций может позволить протестировать поведение функции при прерываниях даже в условиях фактически непрерываемого исполнения. 130
Как правило, асинхронное поведение в TinyOS наблюдается в функциях, которые вызываются из обработчиков прерываний. Для достоверного тестирования асинхронного поведения на аппаратуре необходимо удалить источники прерываний, в цепочке обработки которых встречается целевая функция. Вместо аппаратных прерываний необходимо организовать программные прерывания, что можно сделать аналогично симулятору — через инструментирование кода. В этом случае тестовый сценарий определяет, в каких точках исполнения целевой функции необходимо прерывать исполнение, и настраивает заглушки. Оценка затрат ресурсов на тестирование асинхронного поведения при помощи инструментирования кода: Дополнительный код: число вставок * размер вставки + размер диспетчера (здесь диспетчер означает компонент, который эмулирует асинхронное поведение); размер вставки — несколько инструкций, размер диспетчера до 1 Кб;
Мы оцениваем затраты на транспорт между устройством и тестовой системой величинами около 2-3 Кб кода и 1 Кб данных на устройстве. Эта оценка основана на оценке ресурсов реализации компонента Messenger для симулятора. Затраты стека для передачи и приёма данных можно оценить примерно в 10 вызовов. Эти оценки показывают, что UniTesK применим для тестирования компонентов на устройстве. При выполнении проекта по исследованию применимости UniTesK к тестированию TinyOS тестирование на реальных устройствах не проводилось. Тестовая система Итераторы Оракулы Медиаторы
Расходы на данные: число вставок * размер данных вставки; размер данных для отдельной вставки можно оценить как несколько байт на отдельную вставку. Как показывают оценки, предложенный подход к тестированию асинхронного поведения возможно реализовать на типовых устройствах для TinyOS. Предложенный подход к тестированию асинхронного выполнения ПО пока не применялся на практике.
4.1.4. Медиаторы для тестирования компонентов TinyOS Размеры характерных устройств не позволяют разместить на этих устройствах полученный тестовый набор. По этой причине необходимо проводить удалённое тестирование целевых компонентов. 4.1.4.1. Тестирование компонентов, не взаимодействующих с аппаратурой Необходимо окружить целевой компонент заглушками. Назначение заглушек — передавать тестовые воздействия на целевой компонент и регистрировать реакции целевого компонента. Связь с заглушками осуществляется с использованием некоторого транспортного механизма. Демультиплексирование данных осуществляется специальным компонентом, который мы назвали Messenger. Было реализовано удалённое тестирование синхронного кода в симуляторе TinyOS. В качестве транспортного механизма использовался протокол TCP/IP. Для удалённого тестирования на устройстве необходимо основывать транспортный механизм на тех коммуникационных средствах, которые предоставляет устройство. Типовые устройства для TinyOS содержат параллельный порт (COM-порт), а в поставку TinyOS входят компоненты, реализующие приём и передачу данных по параллельному порту. 131
TinyOS ( устройство или симулятор )
CUT
Транспорт
Тестовое окружение Диспетчер
CUT
Целевой компонент, Component Under
Рис. 8. Удалённое тестирование компонентов TinyOS. 4.1.4.2. Тестирование взаимодействия с аппаратурой Такое тестирование средствами UniTesK требует разработки специализированных медиаторов, которые позволяют решить следующие задачи: Подача тестовых воздействий через аппаратуру или симулятор аппаратуры; Регистрация операций с аппаратурой; Определение состояния аппаратуры. Остальные компоненты тестового набора могут разрабатываться в рамках методологии UniTesK без особых изменений. Тестирование компонентов, связанных с аппаратурой, не проводилось.
5. Результаты применения UniTesK к тестированию компонентов TinyOS В 2003-2004 годах совместно с компанией Luxoft проводился проект по исследованию применимости UniTesK к тестированию компонентов TinyOS и сетей устройств под управлением TinyOS. В проекте преследовались следующие цели: 1) Разработать методику применения UniTesK к тестированию приложений TinyOS; 2) Показать применимость подхода UniTesK к верификации сенсорных сетей. 132
В качестве объекта тестирования был выбран компонент Attr из подсистемы управления атрибутами в TinyDB. Мы провели тестирование реализации шести методов, принадлежащих двум интерфейсам компонента. Функциональные требования извлекались из документации и исходных текстов. Из-за скудости документации нам пришлось использовать исходные тексты реализации. Всего было извлечено порядка 40 функциональных требований.
5.1. Использованный процесс Метод: набор инструментов CTesK. Данный проект отличался от прочих проектов с использованием UniTesK тем, что (1) среди методов интерфейса имеются асинхронные; (2) обработка операции на одном интерфейсе может привести к исполнению операции на другом интерфейсе. Разработка: Разработка проводилась в среде Linux с использованием набора инструментов CTesK, средств разработки TinyOS и обычных средств разработки Unix (Emacs, make). Устройство тестового стенда и средства тестирования: тестируемый компонент был развёрнут в симулятор TinyOS для Linux-PC. Тестирование проводилось в удалённом режиме. Были разработаны специализированные компоненты TinyOS, обеспечивающие связь между тестовой системой и тестируемым компонентом.
6. Применимость UniTesK для тестирования встроенных приложений Проект показал, что метод UniTesK применим для тестирования встроенных приложений. Сильные стороны UniTesK в контексте тестирования встроенного программного обеспечения состоят в следующем: Формализация требований к ПО на языке, близком языку разработки; это упрощает анализ требований и может ускорить разработку системы; Гибкие средства описания недетерминизма в формальной модели; Относительно простые средства описания асинхронных операций; Удобные средства для описания взаимодействия с аппаратурой — операций над состоянием аппаратуры и аппаратных прерываний; Гибкие средства размещения тестового стенда. Слабые стороны UniTesK в контексте тестирования встроенного ПО: Сложность организации транспорта тестовых воздействий (с другой стороны, эти трудности должны быть присущи всем средствам тестирования встроенного ПО); Сложность тестирования асинхронных операций (но это в принципе не может быть простой задачей);
5.2. Затраты
В случае асинхронных операций требуется много рутинной работы для манипулирования состоянием модели; ряд таких операций можно было бы автоматизировать, если ввести в язык средства, характерные для темпоральных логик (но этот вопрос выходит за рамки данной работы);
Продолжительность проекта: 2 месяца. Трудозатраты: 2 человеко-месяца.
5.3. Результаты проекта Протестированы 6 интерфейсных методов. Размеры компонентов тестового набора: Компонент тестового набора Размер, в строках кода Спецификации 550 Тестовые сценарии
200
Медиаторы
500 Таб. 1. Компоненты тестовой системы.
Общее число тестовых сценариев: 5. Общее число тестовых случаев: порядка 50. Число обнаруженных ошибок: 1. При выполнении проекта удалось достичь поставленных целей. Была показана применимость UniTesK к тестированию встроенных систем. Были разработаны методы применения UniTesK к тестированию встроенных систем. 133
Сложность формальных спецификаций UniTesK для статического анализа встроенного программного обеспечения (аналитическая верификация, model-checking и т.п.).
6.1. Спецификации UniTesK и статический анализ Распространённый подход к верификации встроенного программного обеспечения основан на статическом анализе системы. К статическому анализу мы относим процедуры исследования свойств ПО по априорным описаниям или моделям. Примерами статического анализа систем могут служить аналитическая верификация и model-checking. В методах статического анализа систем широко используются формальные спецификации для исследования свойств программных систем (таких, как отсутствие тупиковых ситуаций, обязательное завершение исполнения, временные характеристики и т.п.). Интересно сопоставить формальные спецификации UniTesK и формальные спецификации для статических методов. Можно задать следующие вопросы: 134
Применимы ли формальные спецификаций UniTesK для проведения априорных рассуждений о свойствах описываемых программ? Применимы ли формальные спецификации, которые используются в такого рода исследованиях, для тестирования? Как показывает практика, спецификации для анализа и спецификации для тестирования существенно различаются по своим свойствам. Спецификации для тестирования предоставляют разработчику гибкие, но трудно верифицируемые средства. Спецификации для анализа требуют применения строго ограниченных средств, которые стесняют разработчика спецификаций или трудно применимы для автоматического построения оракула. К примеру, в спецификациях CTesK разрешается использование указателей, циклов и произвольных выражений языка С. Как нам представляется, наши спецификации можно (в принципе) использовать для анализа и доказательства теорем о свойствах описываемой системы, но для этого надо, как минимум, запретить указатели, циклы с переменными границами (ограничители времени исполнения) и ограничить выражения, которые могут встречаться в пред- и постусловиях. Мы считаем, что при выполнении указанных требований язык CTesK станет в один ряд с такими языками, как VDM, RaiseSL, а для них имеются системы аналитической верификации. Правда, при этом пропадёт основное преимущество языка CTesK — приближенность к конечному пользователю. На применимость методов статического анализа влияет ещё один аспект — сложность модели(спецификации). Как показывает наша практика, модель, достаточно детальная для построения оракула, оказывается слишком сложной для автоматического анализа. Анализ возможен только для относительно простых моделей, которые могут дать лишь общий оракул, недостаточный для целей тестирования. Вывод: спецификации, которые используются в UniTesK, могут, в принципе, использоваться для статического анализа встроенного ПО. На практике этому препятствует гибкость выразительных средств, которые используют разработчики спецификаций, и сложность моделей, необходимых для построения оракулов.
Механизм медиаторов предоставляет гибкие средства для отделения сценариев и формальной модели от реализации, позволяет строить различные схемы развёртывания тестового стенда. Спецификации, которые используются в UniTesK, могут, в принципе, использоваться для статического анализа встроенного ПО. На практике этому препятствует гибкость выразительных средств, которые используют разработчики спецификаций, и сложность моделей, необходимых для построения оракулов. Литература 1. J. Hill. A Software Architecture Supporting Networked Sensors. Masters thesis, December 2000. 2. I. Bourdonov, A. Kossatchev, V. Kuliamin, A. Petrenko. UniTesK Test Suite Architecture. Proceedings of FME 2002. LNCS 2391, pp. 77-88, Springer-Verlag, 2002. 3. Веб-сайт CTesK. http://www.unitesk.com/products/ctesk/ 4. D. Gay, P. Levis, R. von Behren, Matt Welsh, E. Brewer, D. Culler. The nesC Language: A Holistic Approach to Networked Embedded Systems. Proceedings of Programming Language Design and Implementation (PLDI) 2003, June 2003. 5. D. Gay, P. Levis, David Culler, E. Brewer. nesC 1.1 Language Reference Manual. http://nescc.sourceforge.net/papers/nesc-ref.pdf 6. S. Madden, J. Hellerstein, W. Hong. TinyDB: In-Network Query Processing in TinyOS. http:// ftp://download.intel.com/research/library/IR-TR-2002-47-120520020948_109.pdf 7. I. Bourdonov, A. Kossatchev, A. Petrenko, D. Galter. KVEST: Automated Generation of Test Suites from Formal Specifications. FM’99: Formal Methods. LNCS, volume 1708, Springer-Verlag, 1999, pp. 608–621. 8. И. Б. Бурдонов, А. В. Демаков, А. С. Косачев, А. В. Максимов, А. К. Петренко. Формальные спецификации в технологиях обратной инженерии и верификации программ. Труды Института системного программирования, 1999 г., том 1, стр. 3547.
7. Заключение UniTesK удобен для функционального тестирования встроенного ПО: Формальные спецификации UniTesK удобны для формализации требований к встроенному программному обеспечению; Тестовые сценарии позволяют компактно записывать сложные тестовые последовательности;
135
136
учитывать при построении компилятора. Во второй главе мы рассматриваем обобщённую схему работы компилятора. Третья глава содержит описание характерных алгоритмов оптимизаций, учитывающих приведённые в первой главе особенности ЦПОС. Четвертая глава рассматривает общие вопросы построения back-end’а компилятора и методы совмещения фаз. В заключении делаются выводы и описываются перспективы продолжения работы.
Использование особенностей ЦПОС в компиляторе языка "С''
2. Рассматриваемые особенности ЦПОС
В.В. Рубанов, А.И. Гриневич, Д.А. Марковцев Аннотация. Данный обзор содержит описание характерных особенностей ЦПОС и связанных с ними оптимизаций, которые могут быть реализованы в компиляторе языка '"С". Рассматриваются как сами алгоритмы оптимизаций, так и взаимное влияние различных оптимизаций друг на друга.
1. Введение Целью данной работы является аналитический обзор методов построения оптимизирующих компиляторов для цифровых процессоров обработки сигнала (ЦПОС). Рассматриваются основные особенности ЦПОС, оказывающие влияние на задачу генерации эффективного кода. При этом мы попытались провести сравнительный анализ имеющихся решений и проанализировать перспективы создания новых оптимизирующих преобразований. Материал статьи базируется на изучении публикаций об оптимизациях в компиляторах для рассматриваемого класса процессоров, а также на опыте авторов, полученном при реализации компиляторов для двух ЦПОС. В последние годы за счет развития технологий и удешевления производства появляется огромное количество типов специализированных процессоров и их модификаций. К таким узкоспециализированным процессорам, в частности, относят и ЦПОС. При этом существует огромное количество готовых прикладных программ на языке С, что делает построение эффективных компиляторов для специализированных процессоров важной задачей, существенно влияющей на качество и время выхода на рынок новых чипов и решений на их основе. Появление утвержденного ISO стандарта расширений языка C для ЦПОС (Embedded C) подчеркивает актуальность задачи построения эффективных компиляторов для данного класса процессоров. Особо отметим один из источников базового материала для нашей статьи работу [20], содержащую обобщённый обзор наиболее актуального материала на 2000 год. Наша работа дополняет эти сведения описанием алгоритмов частичного дублирования данных, программной конвейеризации и рассмотрением новых подходов к задаче сопряжения фаз генерации кода. Статья состоит из введения, четырех глав и заключения. В первой главе приводится описание ключевых особенностей ЦПОС, которые нужно 137
В данной главе приводится краткая сводка характерных особенностей ЦПОС, которые необходимо учитывать для эффективной генерации кода компилятором.
2.1. Расширенная гарвардская архитектура Процессоры общего назначения обычно ориентированы на быстрое выполнение инструкций типа регистр-регистр и имеют ограниченную пропускную способность памяти. Однако большинство алгоритмов ЦПОС требуют как раз интенсивного использования операций с памятью, поэтому для эффективной поддержки цифровой обработки сигналов память системы должна иметь высокую пропускную способность. ЦПОС достигают требуемой пропускной способности памяти за счет использования нескольких устройств памяти с независимыми шинами доступа; например, некоторые ЦПОС могут осуществлять до шести выборок из памяти параллельно (см. [1]). При этом характерный объем памяти в системах ЦОС существенно меньше, чем в системах общего назначения, что позволяет использовать дорогую синхронную память с простой иерархией. Рассмотрим фильтр с конечной импульсной характеристикой (КИХ-фильтр, или FIR-filter), вычислительная схема которого показана на рисунке 1.
Рис. 1. Основными действиями при работе фильтра являются: 1. Выборка инструкций из программной памяти. 2. Выборка двух операндов из памяти данных. 3. Умножение. 4. Накопление. 138
5. Сдвиг в линии задержки. Все современные ЦПОС могут выдавать результат данной последовательности каждый такт. Это достигается за счет конвейерной организации специализированных действий (умножение, накопление, параллельная выборка из банков памяти). Таким образом, для ЦПОС характерна расширенная гарвардская архитектура: Раздельные памяти для программ и данных; Несколько банков памяти данных (например, памяти X и Y) с параллельным доступом.
2.2. Режимы адресации Использование параллельных банков памяти увеличивает пропускную способность памяти, однако для чтения нескольких ячеек памяти одной командой процессора необходимо знать адреса всех этих ячеек. Использование абсолютных адресов (прямая адресация) приводит к увеличению длины командного слова, что неприемлемо. Универсальным решением является использование косвенных регистровых режимов адресации. Такие режимы адресации являются основными для ЦПОС, так как они позволяют использовать несколько адресов в инструкции длиной в одно короткое слово. Для этой цели в ЦПОС обычно существует набор специальных адресных регистров. Как правило, банк адресных регистров небольшой, поэтому для их кодирования требуется всего несколько бит. Кроме того, ЦПОС поддерживают аппаратную модификацию адресных регистров при косвенной адресации, что позволяет минимизировать число команд, необходимых для загрузки этих регистров. Одним из видов такой модификации является автоувеличение/уменьшение содержимого адресного регистра (++/--). При этом, шаг увеличения/уменьшения может определяться как константой, так и динамическим значением специальных шаговых регистров. Еще одним видом аппаратной модификации адресных регистров является циклическая адресация, когда автоувеличение адресного регистра приводит к зацикливанию его значения в пределах области, задаваемой парой специальных регистров (TOP/BOTTOM или START/LENGTH). Характерным типом адресации служит также косвенная адресация со смещением, однако диапазон смещения невелик ввиду ограничений на длину командного слова. В качестве примера рассмотрим разумную реализацию упоминавшегося выше КИХ-фильтра. В такой реализации один адресный регистр используется в качестве указателя на конец линии задержки, а другой регистр в качестве указателя на последний коэффициент фильтра. Тогда сдвиг в линии задержки (перемещение указателей к следующей паре «коэффициент, данные») будет осуществляться за счет автоуменьшения этих адресных регистров.
139
Таким образом, можно выделить следующие основные особенности ЦПОС, связанные с адресацией: Малое количество адресных регистров; Активное использование аппаратной модификации адресных регистров, в частности автоувеличения/уменьшения при косвенной адресации; Малый диапазон смещения при косвенной адресации со смещением.
2.3. Система команд Для процессоров ЦОС характерно наличие множества специализированных функциональных устройств для быстрого выполнения определенных операций. Наиболее распространенными операциями являются умножение, умножение с накоплением, сложение с округлением, а также операции над числами с фиксированной точкой с поддержкой арифметики насыщения (saturation). Многие процессоры используют отдельные функциональные модули для поддержки конкретных алгоритмов, например быстрого преобразования Фурье или декодера Витерби. При этом многие операции могут выполняться параллельно, то есть существуют комбинированные команды, выполняющие несколько операций параллельно (ограниченный параллелизм на уровне команд). Однако, в отличие от VLIW, возможные комбинации параллельно выполняющихся операций ограничены и нерегулярны. Ввиду относительно короткого командного слова, удается кодировать только некоторые комбинации, при этом система команд получается неортогональной. Под ортогональностью системы команд понимается возможность использования в командах любого регистра, доступного в регистровом файле. В рассматриваемом же классе ЦПОС операнды имеют уникальные для каждой команды ограничения на подмножество допустимых регистров, вплоть до того, что в некоторых командах разрешается использовать в качестве операнда только фиксированный регистр (из-за того, что необходимо уместить все необходимые операнды комбинированной команды в одно программное слово). В частности, при использовании косвенной адресации для разных команд допустимыми могут быть различные подмножества адресных регистров. Среди характерных особенностей системы команд стоит отдельно упомянуть поддержку аппаратных циклов. В отличие от традиционной организации циклов с помощью команд условного ветвления, аппаратные циклы позволяют свести затраты на организацию цикла к нулю. Однако при этом существует ряд особенностей, таких как ограниченная вложенность циклов и ограничения на использование команд внутри тела цикла. Компилятор обязан учитывать все такие особенности при генерации аппаратных циклов. Таким образом, мы выделяем следующие особенности системы команд рассматриваемого класса ЦПОС, которые необходимо учитывать в компиляторе: Наличие отдельных функциональных устройств для вычисления специфических операций; 140
Неортогональность системы команд; Поддержка аппаратных циклов.
3. Этапы компиляции В этой главе мы приводим обобщённое представление о процессе компиляции. Обобщённая диаграмма работы компилятора выглядит следующим образом: Исходная программа
Генерация кода
Front-End
Ассемблерный код
Внутреннее представление
Машиннонезависимые оптимизации
Оптимизированное внутреннее представление
Машинно-зависимые оптимизации
Оптимизированный ассемблерный код
В этом процессе оптимизации производятся дважды: Машиннонезависимые производятся над внутренним представлением, полученным от front-end; Машиннозависимые оптимизации производятся над предварительным ассемблерным кодом, полученным от генератора кода. Хотелось бы отметить, что, несмотря на своё название, машиннонезависимые оптимизации нельзя считать полностью оторванными от конкретной архитектуры. Многие из них разрабатывались с учётом общих представлений о свойствах некоторого класса машин (как правило, это машина с большим количеством регистров, фон-неймановской архитектуры, с относительно большой по размерам и медленной памятью). В современных компиляторах они, как правило, нацелены на достижение предельных скоростных характеристик программы, в то время как для встраиваемых систем к критическим параметрам также относится и размер получаемого кода. Некоторые оптимизирующие преобразования (такие, например, как раскрутка циклов) могут ухудшить программу ввиду резкого разрастания размеров и утери информации, полезной для последующих стадий оптимизации. Ещё одна особенность компилятора для встраиваемой системы вытекает из свойства большинства таких систем: ограниченность памяти и отсутствие динамических библиотек. Программа компилируется единожды целиком и в 141
последствии не может изменяться по частям. Это порождает следующие преимущества при создании компилятора: 1. Исходные программы достаточно компактны, следовательно, время, необходимое на их компиляцию, меньше. Поэтому можно использовать более тяжеловесные алгоритмы и эвристики, чем те, что применяются в компиляторах общего назначения. 2. Есть возможность получить информацию обо всей программе целиком, не ограничиваясь только рамками собираемого модуля или библиотеки. Это свойство создаёт предпосылки для применения межпроцедурного анализа и глобальных оптимизаций. Практически каждый компилятор производит определённый набор машиннонезависимых оптимизаций. Вот примеры таких оптимизаций: Исключение общих подвыражений: если внутреннее представление генерируется последовательно для каждого подвыражения, оно обычно содержит большое количество избыточных вычислений. Вычисление избыточно в данной точке программы, если оно уже было выполнено ранее. Такие избыточности могут быть исключены путём сохранения вычисленного значения на регистре и последующего использования этого значения вместо повторного вычисления. Удаление мёртвого кода: любое вычисление, производящее результат, который в дальнейшем более не будет использован, может быть удалено без последствий для семантики программы. Вынесение инвариантов циклов: вычисления в цикле, результаты которых не зависят от других вычислений цикла, могут быть вынесены за пределы цикла как инварианты с целью увеличения скорости. Вычисление константных подвыражений: вычисления, которые гарантированно дают константу, могут быть произведены уже в процессе компиляции. Несмотря на то, что эти преобразования называются машиннонезависимыми, иногда их стоит использовать с осторожностью. Вычисление общих подвыражений, к примеру, сокращая количество вычисляемых выражений, увеличивает потребность в регистрах. Для процессора с большим количеством функциональных устройств и небольшим количеством регистров повторение вычисления может оказаться более эффективным. Аналогичным образом вычисление константных подвыражений может отразиться на семантике программы, когда типы машин, производящих компиляцию и выполняющих программу, различны. Все описанные ниже алгоритмы, за исключением межпроцедурного анализа, работают на этапе машиннозависимых оптимизаций. Здесь следует особо отметить, что их эффективность зачастую напрямую зависит от наличия той или иной дополнительной информации об исходной программе, а именно: 1. «Программная конвейеризация» и сворачивание в «аппаратные циклы» напрямую зависят от информации о циклах исходной программы и их 142
свойствах (цикл for, цикл while, фиксированное или нет число шагов, ветвление внутри цикла, степень вложенности). Таким образом, необходимо, во-первых, исключить машиннонезависимые преобразования, разрушающие структуру цикла, и, во-вторых, обеспечить передачу информации об этих циклах через кодогенератор. 2. Алгоритмы «SOA» и «GOA» (см. ниже) используют информацию о локальных переменных программы. Требуется информация о том, что данное обращение к памяти суть обращение к переменной и эта переменная локальная. 3. Алгоритмы раскладки локальных переменных по банкам памяти требуют информации об обращениях к локальным переменным, плюс важно знать, используется ли где-либо адрес каждой из локальных переменных (т.е. возможно ли обращение по указателю). 4. Алгоритм «Array Index Allocation» и «частичное дублирование данных» должен иметь на входе информацию о массивах и обращениях к ним. Таким образом, нельзя полностью абстрагироваться от начальных стадий работы компилятора, рассматривая их как производящий ассемблерный код «чёрный ящик». Предварительные стадии компиляции должны учитывать особенности класса целевой машины, чтобы создавать предпосылки к максимальной эффективности алгоритмов машиннозависимых оптимизаций.
4. Алгоритмы оптимизаций 4.1. Распределение регистров Практически в каждом ЦПОС имеются так называемые специальные регистры. Их наличие обязательно должно учитываться при распределении регистров, что усложняет эту задачу. В качестве простейшего примера возьмём архитектуру тракта данных процессора Texas Instruments С25: Data bus
MEM
TR
multiplier
ALU
С точки зрения разработчика компилятора, регистры специального назначения скорее неудобство, поскольку задача распределения регистров не может быть полностью разделена с задачей выбора инструкций. Генерация кода без учёта конкретной архитектуры регистров может привести к необоснованному разрастанию кода в результате возникновения дополнительных пересылок между регистрами и памятью с целью поместить входные данные в подходящие регистры. Таким образом, концепция гомогенного неограниченного набора псевдорегистров вместе со стандартными подходами (например, раскраска графов) оказывается неэффективной в случае ЦПОС. Более того, необходимо учитывать ограниченность количества регистров специального назначения. В большинстве случаев эти регистры (или регистровые файлы) вмещают одно или два значения. Таким образом, необходимо позаботиться, чтобы регистр не был перезаписан, пока его значение не используется в последний раз. Иначе возникнут дополнительные, дорогостоящие пересылки в память и обратно. В некоторых же случаях операция сохранения в памяти усложнена, как, например, на приведённом выше рисунке. Сохранение регистра TR возможно только через регистр PR (который, в свою очередь, может содержать полезное значение), а загрузка PR обратно из памяти ещё потребует умножения на 1. Эффективность распределения регистров напрямую зависит как от результата выбора инструкций, так и от алгоритма распределения регистров. Причём желательно, чтобы алгоритм выбора инструкций учитывал особенности архитектуры, т.е. не был полностью независимым. Наиболее очевидное решение здесь видится в композиции этих фаз компиляции. Эффективные алгоритмы композиции фаз рассматриваются ниже.
4.2. Программная конвейеризация Software Pipelining (loop pipelining) – конвейеризация циклов. Это преобразование, призванное использовать способность процессора к параллельному вычислению для операций, находящихся на различных итерациях цикла. Здесь мы расскажем об алгоритме итеративной модульной планировки (Iterative Modulo Scheduling), впервые описанном в [17]. В качестве примера рассмотрим вот такой цикл: for i O1: O2: O3: O4:
PR ACCU
TR – первый операнд для мультипликатора PR – результат умножения ACCU – регистр сумматора обратным путём к ALU
a[i]=i*i b[i]=a[i]*b[i-1] c[i]=b[i]/n d[i]=b[i]%n
Для удобства цикл представлен с помощью графа зависимости по данным (DDG) следующего вида: 143
144
uses(r ) количество использований ресурса r за одну итерацию цикла;
1
units (r ) число функциональных модулей типа r ,
<0,1> 2
<1,1>
<0,1> <0,1> 3
4
В кружках стоят номера инструкций, а каждое ребро между инструкциями i и j помечено парой чисел . Ребро означает:
где c естественный цикл на графе, являющемся транзитивным замыканием исходного графа зависимости по данным, G множество всех таких циклов, а R множество ресурсов. Нижняя оценка величины основного интервала может быть получена двумя способами. Во-первых, можно оценить максимальную задержку по зависимостям по данным для каждого из естественных циклов на графе: RecMII max cG
j зависит от значения, вычисленного i инструкцией p итераций цикла назад; как минимум d тактов должно пройти после выполнения соответствующего экземпляра i, чтобы можно было выполнить j. После программной конвейеризации данный цикл может иметь следующий вид: Итерации Пролог Время (такты)
Ядро (n-3 раз) Эпилог
1 2 3,4
1 2 3,4
цикла c ; distance(c)
ic
ResMII max rR
ic
.
Сам алгоритм выглядит следующим образом:
2 3,4
procedure modulo_schedule(budget_ratio) compute MII; II:=MII; budget:=budget_ratio*количество инструкций; while не найдено подходящего расписания do iterative_schedule(II, budget); II:=II+1;
где budget_ratio – количество возвратов в переборе перед тем, как переходить к следующему значению II. Непосредственно сам перебор с возвратом происходит в процедуре iterative_schedule: procedure iterative_schedule(II, budget); compute height-based priorities; while есть неспланированные операции и budget > 0 do op:=операция с наивысшим приоритетом; min:=наиболее раннее время для операции op; max:=min+II-1; t:=find_slot(op,min,max); выставить операцию op в момент t переместить все операции, имеющие конфликт с op, обратно в список неспланированных команд; budget:=budget-1;
d i суммарная задержка по зависимостям данных для
uses (r ) units (r )
MII max( RecMII , ResMII ) .
1
Описываемый здесь алгоритм итеративный. Он пытается построить план (schedule) для значения основного интервала II, в случае неудачи увеличивая II на 1. Таким образом, очевидно, что скорость работы алгоритма непосредственно зависит от того, насколько удачно было выбрано начальное значение основного интервала. На выборе этого значения остановимся подробнее. Для начала построим процедуру оценки минимального значения основного интервала II, обозначив её как MII.
Во-вторых, задержку можно оценить, исходя из предельной загруженности функциональных модулей по каждому из ресурсов:
Разумно взять наибольшую из полученных выше оценок за начальную величину основного интервала:
Как видно из таблицы, после преобразования цикл разбивается на три стадии: пролог, эпилог и ядро. В данном случае время между началом последовательных итераций ядра – один такт. Вообще задержка между началом последовательных итераций называется основным интервалом (initiation interval, размер ядра получаемого цикла) и обычно обозначается как II.
delay (c)
delay (c) . distance(c)
p i суммарная дистанция в итерациях для цикла c ;
145
146
На каждом шаге выбирается инструкция с максимальным приоритетом. Возможны различные подходы к вычислению приоритета инструкции H (i) , например: H (i ) 0 , если i не имеет последователей, иначе H (i ) max ksucc (i ) H (k ) latency (i, k ) II * distance(i, k ) .
Непосредственно временной слот, в который будет помещена конкретная инструкция j, ограничен снизу уже расставленными предшественниками k: time( j ) time(k ) latency (k , j ) II * distance(k , j ) .
Процедура find_slot находит минимальный временной слот между min и max, такой, что операция op может быть размещена без конфликтов по ресурсам. Отметим, что выбранный слот может вызывать конфликты по ресурсам. Таким образом, размещение некоторых более ранних операций можно откатить и повторить заново. procedure find_slot(op, min, max); for t:=min to max do if op не имеет конфликтов в моменте времени t return t; if op ещё не встречалась or min > предыдущее спланированное время для op return min; else return 1 + предыдущее спланированное время для op;
Для проверки конфликтов по ресурсам используется таблица резервирования. Высота этой таблицы равна II. Если ресурс r используется в момент времени t, то в таблице помечается ячейка [r, t mod II], поэтому эту таблицу ещё называют модульной таблицей резервирования (modulo reservation table, MRT). Приведённый здесь алгоритм лишь один из представителей целого класса. Он может учитывать множество дополнительных особенностей используемой архитектуры, такой как предикатное исполнение. Более подробно обзор различных подходов к программной конвейеризации циклов может быть найден в [16].
4.3. Использование характерных особенностей ЦПОС В этом разделе приведены преобразования, несложные для реализации, но представляющие интерес, поскольку опираются на внутренние особенности ЦПОС. Раскладка локальных переменных по банкам памяти. В основе данного оптимизирующего преобразования лежит тот факт, что гарвардская архитектура предоставляет возможность параллельного доступа к двум 147
ячейкам, при условии, что они находятся в различных памятях. Таким образом, появляется возможность ускорить доступ к данным при одновременном сокращении размера программного кода [11]. Раскладка данных в свою очередь приводит к трём взаимосвязанным проблемам: 1. Необходимо установить связи между различными элементами данных в контексте раскладки по разным памятям. Это включает в себя определение пар элементов данных, потенциально способных находиться в различных банках памяти. 2. Необходимо определить функцию стоимости для каждой связи. 3. Необходимо задать правила таким образом, чтобы удовлетворить как можно большему количеству разделяющих связей, тем самым достигнув наибольшей общей производительности. Частичное дублирование данных [11] оказывается полезным для оптимизации работы с участками кода, подобными приведённому ниже: for(n=1; n
Подобный код используется, в частности, при вычислении функции автокоррелляции, что нередко встречается в алгоритмах для ЦПОС. Особенность этого фрагмента в том, что происходит одновременное обращение к двум элементам одного и того же массива. Данный фрагмент можно существенно ускорить за счёт дублирования значений signal[n] в двух банках памяти. Найти массивы-кандидаты на дублирование несложно – области жизни двух обращений к одному и тому же массиву должны пересекаться. Дублирование данных не всегда приводит к повышению производительности. В частности, если в последствии не удастся запаковать оба обращения в одну инструкцию. В этом случае дублирование данных только ухудшает программу. Кроме того, дублирование может усложнить работу с прерываниями. Если записи различных двух копий одних и тех же данных окажутся разнесёнными по различным инструкциям, теоретически возникает возможность аппаратного прерывания между ними, а значит, данные в двух копиях могут оказаться в несогласованном состоянии. Использование аппаратной поддержки циклов. В ЦПОС присутствует поддержка аппаратных циклов. Генерация кода для этого случая, в принципе, тривиальна: если имеется информация об исходном цикле, необходимо проверить, что этот цикл удовлетворяет некоторым условиям: 1. Внутри цикла нет ветвлений и вызовов процедур; 2. Границы цикла известны и фиксированы; 3. Тело цикла не зависит напрямую от переменной индукции; 4. Тело цикла не содержит запрещённых инструкций. 148
В данном случае важно, что компилятор сохраняет информацию о циклах до довольно поздней стадии: стадии, когда уже сгенерирован код. Например, в SPAM это достигается за счёт того факта, что SUIF, используемый в качестве front-end, позволяет «протаскивать» информацию об исходных циклах через весь слой машиннонезависимых оптимизаций и генерации кода.
4.4. Сжатие кода Сжатие кода (Code Compaction) – стадия работы компилятора, направленная на формирование микроинструкций из команд, которые могут быть выполнены параллельно. При этом планирование команд должно проводиться с учетом зависимостей по данным и ограничений на использование ресурсов. Учет зависимостей по данным гарантирует, что никакая команда не будет выполнена до тех пор, пока все команды, от которых она зависит, не будут выполнены полностью. Ограничения на ресурсы гарантируют, что построенное расписание не затребует ресурсов (функциональных модулей) больше, чем позволяет архитектура. Целью сжатия кода является получение минимального количества микроинструкций. Известно, что задача сжатия кода NP-полная. Поэтому был разработан целый ряд эвристических алгоритмов планирования. Перечислим такие, как firstcome-first-served, critical path и list scheduling. Каждая из этих эвристик приводит к близким к оптимуму результатам в большинстве случаев, отличаясь в свою очередь скоростью и простотой. Детальное описание этих алгоритмов (сложности O(n2)) может быть найдено в [3]. В этом обзоре мы остановимся подробнее на алгоритме list scheduling. Приоритетное списочное планирование (list scheduling) относится к разряду алгоритмов, решающих задачу локального сжатия кода, т.е. планирования команд внутри базового блока. Стоит отметить, что в этом случае зависимости по данным представляются в виде ориентированного ациклического направленного графа (DAG – direct acyclic graph). Алгоритм планирует команды с нулевого момента времени, начиная с входной команды базового блока. В каждый момент времени t поддерживается список готовых команд (ReadyList), т.е. команд, чьи предшественники уже были спланированы и произведут результат в выходном регистре к моменту времени t. List scheduling является жадной эвристикой и всегда планирует в текущий момент времени готовую инструкцию в случае отсутствия конфликтов по ресурсам. Требования по ресурсам для спланированных команд поддерживаются в глобальной таблице резервирования (GRT – global reservation table). Если есть несколько готовых команд, то выбирается команда с наивысшим приоритетом. Приоритеты назначаются в соответствии с различными эвристиками. Различные виды алгоритма list scheduling отличаются способами задания приоритетов для команд. Отметим, что назначение приоритета может быть как статическим (приоритет назначается один раз и не меняется больше в течение планирования), так и динамическим (меняется в течение 149
планирования) и таким образом требовать пересчета приоритетов готовых команд после каждой спланированной команды. Мы рассмотрим вариант алгоритма со статическими приоритетами. Когда все команды из ReadyList, не приводящие к конфликту по ресурсам, спланированы, то момент времени увеличивается на единицу, и ReadyList пополняется новыми готовыми командами. Затем ReadyList сортируется в порядке убывания приоритета. Процесс планирования продолжается до тех пор, пока все команды не будут спланированы. В общем виде алгоритм list scheduling можно записать следующим образом: Вход: DAG Выход: Instruction Schedule AssignPriority (DAG); /* каждой команде присваивается приоритет в соответствии с выбранной эвристикой */ ReadyList = source node in the DAG; timestep = 0; while (DAG содержит неспланированные команды) do { Сортировать ReadyList в порядке убывания приоритета; while (не испробованы все команды из ReadyList) do { Выбрать следующую команду i из ReadyList; Проверить на конфликт по ресурсам; if (команду можно спланировать) { update GRT (i, timestep); Удалить команду из ReadyList; } } timestep++; Добавить команды, которые стали готовыми, в ReadyList }
Рассмотрим эвристики, используемые для назначения приоритета готовым командам. Обычно используется эвристика, основанная на максимальном расстоянии от узла до фиктивного узла стока. Максимальное расстояние узла стока до себя принимается за ноль. Максимальное расстояние узла u определяется как: MaxDistance(u) = max(MaxDistance(vi) + weight(u, vi))
где максимум берется по всем vi – потомкам узла u. MaxDistance вычисляется с помощью обратного прохода графа зависимости и является статическим приоритетом. Приоритет отдается узлам с наибольшим MaxDistance. 150
Часто более высокий приоритет отдается командам, которые имеют наименьшее значение Estart. Для входного узла графа Estart принимается равным нулю. Значение Estart команды v определяется как:
где столбец s = {s0, s1, … , sn-1} и столбец N = {0, 1, …, T-1}. Тот факт, что каждая команда может быть спланирована в точности один раз, выражается условием:
Estart(v) = max(Estart(ui) + weight(ui, v))
(3)
где максимум берется по всем ui – предкам v. Похожим образом можно отдать приоритет командам с наименьшим значением параметра Lstart, который определяется как: Lstart(u) = min(Lstart(vi) - weight(u, vi))
где минимум берется по всем vi – потомкам узла u. Значение Lstart для узла стока принимается равным Estart. Разность между Lstart(u) и Estart(u) называется резервом времени или мобильностью и также может быть использована для назначения приоритета. Команды с меньшим резервом времени получают больший приоритет. Команды, лежащие на критическом пути, могут иметь нулевой резерв времени и поэтому иметь больший приоритет над командами, лежащими вне критического пути. Мы уже упоминали, что задача сжатия кода является NP-полной. Для нахождения оптимального расписания команд можно воспользоваться методом целочисленного линейного программирования. Сформулируем задачу для случая планирования базового блока при наличии ограничений на ресурсы. Предполагается простая модель ресурсов с полностью конвейеризированными функциональными модулями. Рассмотрим архитектуру с функциональными модулями различного типа (например, АЛУ, устройство записи/чтения, умножитель/делитель), где задержка выполнения команд может быть разной в различных модулях. Также будем считать, что существует Rr экземпляров для типа r функционального модуля. Пусть si – момент времени, для которого спланирована команда i, и d(i, j) – вес ребра (i, j). Чтобы не нарушить зависимостей по данным для каждого ребра (i, j), в DAG должно выполняться: (1)
sj ≥ si + d(i,
j)
.
Рассмотрим матрицу K размера n x T, где n – количество команд или узлов в DAG, и T оценка для наихудшего времени выполнения всего расписания. Обычно T – это сумма времен выполнения всех команд, входящих в базовый блок. Отметим, что T – константа и может быть получено из DAG. Элемент матрицы К[i,t] равен единице, если команда i спланирована для момента времени t, и нолю в противном случае. Время планирования si для команды i можно выразить с помощью К в следующем виде: si = ki,0 * 0 + ki,1 * 1 + … + ki,T-1.
Для всех si это может быть выражено в матричной форме: (2)
s = K * N.
151
∑ ki,t
= 1
, i.
Наконец, ограничения на ресурсы задаются неравенством: (4)
∑ ki,t ≤ Rr
, t, r, i F(r),
где F(r) представляет собой набор команд, которые могут выполняться на функциональном модуле типа r. Целевой функцией является минимизация длины расписания, что можно выразить как: minimize (max (si + d(i,j))).
Чтобы выразить это в линейной форме, введем: (5)
z ≥ si + d(i,
j).
Таким образом, решение задачи состоит в минимизации z при условиях (1) – (5). Стоит отметить, что важным фактором для реализации любого из алгоритмов сжатия кода является описание имеющихся в наличии параллельных функциональных модулей и подходящих для этого инструкций с учётом нерегулярности кодирования.
4.5. Оптимизации адресного кода Данный набор оптимизирующих преобразований основывается на способности блока генерации адресов (AGU – address generation unit) при использовании индексного регистра для адресации производить его изменение (увеличение или уменьшение на фиксированный шаг). Данный набор преобразований в такой комбинации стал уже «джентльменским набором» оптимизаций для встраиваемых систем. Их описание можно встретить как один из пунктов в [12, 20, 6] или отдельно в [14, 13]. Попытаемся на простых примерах пояснить смысл каждого из этих преобразований. SOA (Simple Offset Assignment) Допустим, имеется последовательность доступов к переменным (a, b, c, d – имена переменных): bcadbcad Обозначим через & операцию вычисления адреса, A – операцию доступа, I – индексный регистр, ++ или -- операцию постизменения индексного регистра. В общем случае каждое обращение к переменной имеет вид: 152
I = &var A(I) т.е. для каждого обращения необходимо произвести две операции. Допустим теперь, что эти переменные в памяти были расположены в лексикографическом порядке, т.е: abcd Для работы с рассмотренной выше последовательностью обращений будет необходимо произвести следующие операции: I=&b A(I++) A(I) I=&a A(I) I=d A(I) I=&b A(I++) A(I) I=&a A(I) I=d A(I) В итоге, для работы с данной последовательностью обращений при имеющемся расположении переменных в памяти нам необходимо затратить 14 операций. Допустим теперь, что мы разложили упомянутые выше переменные следующим образом: bcad Теперь последовательность операций будет выглядеть так: I(&b) A(I++) A(I++) A(I++) A(I) I(&b) A(I++) A(I++) A(I++) A(I) Всего мы затратили 10 операций. Таким образом, правильное распределение в памяти может дать существенную экономию на операциях загрузки адреса при использовании возможностей автоувеличения или автоуменьшения. 153
Для вычисления оптимального расположения переменных в памяти используются различные эвристики (эвристики для решения задачи SOA). GOA (General Offset Assignment) Задача GOA аналогична SOA, с той разницей, что мы учитываем наличие более чем одного индексного регистра, что иногда позволяет получить более экономный код. Array Index Allocation И, наконец, алгоритм Array Index Allocation позволяет оптимизировать циклические доступы к массиву. К примеру, данный цикл: for(int i=0;i<10;i++) { a[i]; a[i+1]; }
Может быть приведен к следующему виду: ptr = a; for(int i=0;i<10;i++) { ptr++; ptr; }
Таким образом, мы перешли от операции косвенной адресации a[i+1] к индексному регистру с автоувеличением ptr++, сэкономив на операции косвенной адресации. Для того, чтобы эта оптимизация была возможна, необходимо «протаскивать» информацию о доступах к массиву и о циклах. Вообще говоря, в некоторых архитектурах бывает возможен доступ с использованием абсолютной адресации или смещения, но и то и другое, как правило, требует каких-либо дополнительных затрат, например, дополнительного командного слова для кодирования смещения и, соответственно, задержки в выполнении. Кроме того, такие операции в дальнейшем не могут быть подвергнуты сжатию кода, поэтому обращение с использованием индексных регистров с автоувеличением/автоуменьшением оказывается предпочтительнее с точки зрения конечного качества кода. Подробнее о реализации приведённых здесь преобразований в SPAM см.[6].
5. Взаимное влияние оптимизаций Ввиду большой вычислительной сложности задачи компиляции, компилятор, как правило, разбивается на ряд различных фаз. Этот очевидный сам по себе подход подразумевает, что фазы выполняются в определённом порядке. Существует нетривиальная проблема определения наиболее оптимального порядка следования фаз. Многие фазы могут влиять на потенциальную 154
эффективность выполнения последующих фаз. Машиннонезависимая оптимизация «подстановка констант», к примеру, обычно повышает эффективность «вычисления константных выражений», что может привести к появлению последующих констант для подстановки. Таким образом, ясно, что эти две оптимизации должны производиться итеративно. Что касается back-end’а, то задача определения порядка фаз здесь усложняется, поскольку некоторые фазы сокращают применимость последующих фаз. Более того, зависимость между фазами обычно циклична. Мы проиллюстрируем эту проблему следующими примерами: Выбор инструкций и распределение регистров: алгоритм выбора инструкций преобразует элементы внутреннего представления в машинные команды. Обычно эта фаза также определяет, какие значения будут оставаться на виртуальных регистрах. Более того, при наличии более одного регистрового файла алгоритм выбора инструкций также должен определять типы регистров для каждой операции, поскольку, как правило, имеется оптимальный вариант с точки зрения стоимости инструкции. Тем не менее, только при распределении регистров можно понять, был ли тот или иной выбор удачным, если принять во внимание стоимость сохранения временного значения в памяти. Распределение регистров и планирование команд: распределение регистров сопоставляет виртуальным регистрам физические. Обычно при этом один физический регистр используется для нескольких виртуальных, что приводит к дополнительным антизависимостям, мешающим планировщику. С другой стороны, последовательное планирование команд явно влияет на области жизни виртуальных регистров, тем самым непосредственно влияя на результаты распределения регистров. Планирование команд и оптимизации адресного кода: оптимизации адресного кода зависят от конкретной последовательности доступов к памяти, получаемой после планировщика. Положение переменных в памяти и количество операций автоувеличения зависят от последовательности доступа, и возможно, что альтернативная последовательность команд будет более эффективна с точки зрения стоимости адресных вычислений. Поскольку оптимизации адресного кода приводят к новым инструкциям, они могут повлиять в дальнейшем на эффективность сжатия. Результат приведённых здесь зависимостей таков, что практически любая последовательность фаз может приводить к неэффективному коду для определённых входных программ. Проблема порядка следования фаз ещё острее для процессоров с нерегулярной архитектурой, таких как большинство ЦПОС. Для преодоления этих проблем обычно прибегают к сопряжению фаз (phase coupling), что приводит к тесному обмену информацией между сопряжёнными фазами. Простейший способ сопряжения фаз – итеративное выполнение нескольких фаз с возможностью изменения потенциально неправильных решений, основанных на пометках, сделанных последующими фазами. Другие подходы 155
пытаются оценить влияние каждой фазы на последующие фазы на ранней стадии. Наибольшая степень сопряжения фаз достигается, когда фазы фактически соединяются в одну, хотя такие алгоритмы тяжелы для разработки и, как правило, очень трудоёмки. Существует целый ряд подходов к композиции фаз. Например, в [22] покрытие деревьев совмещается с распределением регистров и планированием для Texas Instruments DSP. Ещё один подход, основанный на алгоритме simulated annealing, по сути похожем на генетические алгоритмы, описан в [23]. Здесь мы остановимся на общем подходе, основанном на CLP (constraint logic programming) – системе решения определённого класса задач. К этому классу задач можно свести и задачи генерации кода для нерегулярных архитектур, в частности для ЦПОС [24], т.е. для случаев, когда имеется большое количество различных ограничений, которые нельзя выразить в общем виде. Рассмотрим пример такого описания. Пусть имеется инструкция вида: X1:=X2+X3
где X1, X2 и X3 представляют различные наборы регистров, причём не все комбинации регистров, представляющих каждый из операндов, возможны, а имеются дополнительные ограничения. К примеру, пусть X1={a1,a2}, X2={b2, b2} и X3={c1,c2}. При этом обычно присутствуют ограничения вида: «если X2=b1 и X3=с1, то X1 может быть только a2», или же «X1=a1 только при X3=c2» и т.п. Эта ситуация может быть ещё более усложнена, если допустимые комбинации регистров зависят и от выполняемой параллельно инструкции. Искомые величины (X2) будем в дальнейшем называть переменными, а множество их возможных значений ({b1, b2}) – доменами. Решением задачи CSP называется набор значений для переменных X1..X3, удовлетворяющих всем ограничениям. Например, в [25] система команд процессора описывается следующим списком: (Op, R, [O1,…,On], ERI, Cons)
где Op обозначает имеющуюся в процессоре операцию, R – множество ресурсов для хранения результата (регистров или ячеек памяти), Oi – множество ресурсов для размещения входных данных, ERI – набор дополнительных ресурсов, используемых данной машинной операцией (допустим, шины или функциональные узлы), Cons – набор ограничений на значения Op, R, Oi и ERI. Для решения задачи CLP в [28] использовался язык ECLiPSe [26], являющийся расширением языка PROLOG. При этом поиск решения задачи сводится к поиску некоторой маркировки, т.е. выбора для каждой исходной переменной одного из значений из связанного с ней домена. Например, переменная X1 из приведённого выше примера может быть промаркирована значением a1 из её домена {a1, a2}. ECLiPSe содержит библиотеку алгоритмов для поиска маркировки для случая конечных доменов значений. Более того, в ECLiPSe входит 156
набор обобщённых оптимизирующих процедур, получающих на вход стратегию маркировки l(V) на наборе переменных V вместе с целевой функцией cost(V) и вычисляющих оптимальную маркировку: minimize(l(V), const(V)). Данный подход позволяет достигать впечатляющих результатов (разница по сравнению с ручным ассемблерным кодом всего порядка 20%). Из недостатков следует отметить заметные затраты ресурсов и времени (порядка минут) на компиляцию тестовых программ из набора DSPStone 27.
6. Заключение В данной работе мы рассмотрели основные особенности процессоров ЦПОС и методы оптимизации, основанные на использовании этих особенностей. В частности, особенности адресной арифметики дают возможность использования методов оптимизации распределения смещений (GOA и SOA). Алгоритмы сжатия кода позволяют использовать свойство ограниченного параллелизма команд в условиях нерегулярного кодирования. Такие оптимизации, как частичное дублирование данных, позволяют учитывать специфику расширенной гарвардской архитектуры. Кроме того, общие свойства программ для встраиваемых систем (небольшие размеры, отсутствие динамических библиотек и мягкие требования ко времени компиляции) позволяют изучать и использовать ресурсоемкие алгоритмы оптимизации, в частности, сопряжение фаз генерации кода. При построении компиляторов для ЦПОС архитектур применение рассмотренных методов является важнейшим элементом достижения эффективности генерируемого кода. В дальнейшем мы планируем использовать подход сопряжения фаз с помощью решения задачи CLP при разработке компилятора на базе платформы GCC для разрабатываемого заказчиком закрытого ЦПОС. Из возможных трудностей стоит отметить, что, несмотря на универсальность метода CLP [28], существующий алгоритм ограничивается одним базовым блоком и в некоторых аспектах для принятия решений используются эвристики. Это говорит о незавершённости разработки данного подхода. Как признают авторы CLP [28], эвристики должны быть в дальнейшем сведены к общей процедуре поиска оптимальной маркировки. Кроме того, наш целевой процессор имеет существенно большее количество регистров общего назначения, чем тот, который использовали авторы данного подхода, что может привести к затруднению задачи выбора оптимальной маркировки при распределении регистров. По всей видимости, потребуется внесение дополнительных ограничений для устранения этой проблемы. Литература 1. E. Lee. Programmable DSP Architectures: Part I. IEEE ASSP Magazine, pp. 4-19, October 1988. 2. E. Lee. Programmable DSP Architectures: Part II. IEEE ASSP Magazine, pp. 4-14, January 1989.
157
3. S. Davidson, D. Landskov, B.D. Shriver, P.W.Mallett. Some Experiments in Local Microcode Compaction for Horizontal Machines. IEEE Trans. on Computers, vol. 30, no. 7, 1981, pp. 460-477. 4. Khalid Ismail. DSP Architecture Implications on Compilation. 5. Richard M. Stallman. Using and Porting the GNU Compiler Collection. 6. Ashok Sudarsanam. Code Optimization Libraries For Retargetable Compilation For Embedded Digital Signal Processors. 7. C. Liem. Retargetable Compilers for Embedded Core Processors. Kluwer Academic Publishers, 1997. 8. Алла Солонина, Дмитрий Улахович, Лев Яковлев. Алгоритмы и процессоры цифровой обработки сигналов. БХВ-Петербург, 2001. 9. Vasanth Bala, Norman Rubin. Efficient Instruction Scheduling Using Finite State Automata. 1995. 10. P. Briggs. Register Allocation via Graph Coloring. PhD Thesis, RICE University, 1992. 11. M. Saghir, P. Chow, C. Lee. Exploiting Dual Data-Memory Banks in Digital Signal Processors. In Proceedings of the ACM SIGARCH Conference on Architectural Support for Programming Languages and Operating Systems, 1996. 12. Rainer Leupers. Retargetable Code Generation for Digital Signal Processors. Kluwer Academic Publishers, 1997. 13. S. Liao, S. Devadas, K. Keutzer, S. Tjiang, and A. Wang. Storage Assignment to Decrease Code Size. In Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation, pp. 186-195, June 1995. 14. R. Leupers, P. Marwedel. Algorithms for Address Assignment in DSP Code Generation. In Proceedings of the International Conference on Computer-Aided Design, 1996. 15. Шумаков С.М. Обзор методов оптимизации кода для процессоров с поддержкой параллелизма на уровне команд. сентябрь 2003, http://www.citforum.ru/programming/digest/20030430/ 16. V. Allan, R. Jones, R. Lee, S. Allan. Software Pipelining. 17. B. Ramakrishna Rau. Iterative Modulo Scheduling: An Algorighm For Software Pipelining Loops. 18. A. Aiken, A. Nicolau, S. Novack. Resource-Constrained Software Pipelining. 19. T. Proebsting, C. Fraser. Detecting Pipeline Structural Hazards Quickly. 1994. 20. Rainer Leupers. Code Optimization Techniques for Embedded Processors. Kluwer Academic Publishers, 2000. 21. R. Ramakrishna Rau. Iterative Modulo Scheduling: An Algorithm For Software Pipelining Loops. 22. G. Araujo, S. Malik. Optimal Code Generation for Embedded Memory NonHomogeneous Register Architectures. 1995. 23. R. Leupers. Register Allocation for Common Subexpressions in DSP Data Paths. 2000. 24. R. Leupers, P. Marwedell. Retargetable Compiler Technology for Embedded Systems. 25. R. Leupers, S. Bashford. Graph based Code Selection Techniques for Embedded Processors. 26. M. Wallace, S. Novello, J. Schimpf. ECLiPSe: A Platform for Constraint Logic Programming. 27. V. Zivonovich, J.M. Velarde, C. Schläger, H. Meyr. DSPStone – A DSP-oriented Benchmarking Methodology. 1994. 28. S. Bashford, R.Leupers. Phase-Coupled Mapping of Data Flow Graphs to Irregular Data Paths. 1999.
158
Архитектура и принципы построения операционной среды «мини-ОС» Ю.Н. Фонин, С. Грассман Аннотация. В статье представлены архитектура и принципы построения операционной среды «мини-ОС», предназначенной для использования в так называемых встроенных системах. Особое внимание в статье уделено вопросам портирования мини-ОС и вопросам минимизации усилий на адаптацию ОС под требования аппаратуры и приложений пользователя.
1. Введение Для обеспечения функционирования сложных систем цифровой обработки сигналов (ЦОС), таких как системы, реализующие протоколы беспроводной передачи данных в режиме реального времени, требуются специальные, адаптированные под конкретные приложения, аппаратные конфигурации. Такие конфигурации могут включать в себя несколько процессоров, а также специальные аппаратные ускорители для быстрого выполнения тех или иных функций. Для портирования алгоритмов на такие аппаратные системы необходим набор сервисов, поддерживающих коммуникации и синхронизацию между различными потоками в приложении. Обычно такие сервисы предоставляются операционной средой. Для портирования стандартной операционной системы реального времени требуется от 3 до 20 человеко-месяцев, в зависимости от сложности системы. С другой стороны, для реализации задач ЦОС требуется только некоторое подмножество модулей ОС, и, соответственно, для портирования этих модулей нужно меньше времени, чем для портирования полноценной ОС. В ходе работы были исследованы различные операционные среды от больших, многофункциональных систем, таких как Linux и Symbian, до небольших, обладающих минимальным набором функций, таких как VsWorks, RTEMS и EUROS [1, 2, 4]. Особенностью всех этих систем является то, что они основываются на неразделяемом ядре с базовым набором модулей, таких как планировщик задач, менеджер управления прерываниями, менеджер драйверов ввода-вывода, набор примитивов синхронизации, системный таймер и т.д. При этом ни один из перечисленных модулей не может быть исключен из системы, даже если он не используется непосредственно приложением. По этой причине для портирования такой системы требуется заново реализовать все платформо159
зависимые части ОС, что существенно увеличивает время портирования системы. В настоящее время существуют несколько способов ускорения процесса портирования операционной системы. Например, в системе Choice [5] вся платформеннозависимая часть ОС логически выделяется в так называемое «наноядро». При таком подходе портирование ОС на новую платформу сводится к реализации наноядра. Другой метод [6] заключается в документировании методологии портирования посредством паттернов. Для создания и формализации шаблонов разработан специальный «язык паттернов». Такой подход позволяет формализовать процесс портирования ОС. Нами была разработана операционная система «мини-ОС», отличительными особенностями которой является модульность и конфигурируемость. Любой из базовых модулей может быть исключен из системы без необходимости модификации остальной части ОС. Данное свойство мини-ОС позволяет значительно ускорить процесс портирования системы на новую платформу, а также обеспечивает возможность оптимизации ОС под конкретный набор приложений. Статья организована следующим образом. В разделе 2 обсуждаются концепция построения системы мини-ОС и основные требования, выдвигавшиеся к системе. Раздел 3 описывает структуру системы, её основные модули. В разделе 4 рассматриваются платформеннозависимые части ОС в соответствии с использующими их модулями. В разделе 5 приводится пример портирования системы на многопроцессорную систему ARM_MUSIC.
2. Концепция разработки мини-ОС При разработке минимальной операционной системы (далее мини-ОС) к ней выдвигались следующие требования: 1) Модульность – мини-ОС представляет собой базовый набор ортогональных модулей, реализованных таким образом, что конфигурация из любого набора модулей может быть скомпилирована без внесения изменений в код системы. 2) Портируемость – время на адаптацию системы к новой архитектуре должно быть минимальным. Количество платформеннозависимых частей мини-ОС должно быть минимально, и их наличие должно обуславливаться только невозможностью реализации в платформеннонезависимом виде. 3) Программный интерфейс мини-ОС должен быть совместим с интерфейсом ОС Windows. 4) Мини-ОС должна поддерживать выполнение приложений на многопроцессорной системе с общей памятью, т.е. должна обеспечиваться возможность синхронизации между потоками, работающими на разных процессорах. 160
Первое требование было удовлетворено за счет разработки различных реализаций одних и тех же модулей в зависимости от конфигурации ядра. В процессе компиляции мини-ОС выбирается та реализация, которая соответствует конфигурации системы. Такая гибкость была достигнута за счет использования директив условной компиляции в языке Си. Для уменьшения времени портирования мини-ОС использовались следующие методы: Разделение платформеннозависимых и платформеннонезависимых частей ОС. Реализация платформеннозависимых частей в виде макросов. Использование инлайн-ассемблера. Формализация функциональности платформеннозависимых частей ядра. Поддержка многопроцессорной системы с общей памятью. Совместимость с ОС Windows достигнута за счет использования интерфейсов функций, а также имен типов данных Windows при декларации объектов ОС, таких как поток, синхронизационный примитив и т.д. Операционная система мини-ОС, построенная в соответствии с описанными выше принципами, позволяет реализовать следующий сценарий разработки и портирования приложений: Разработка и отладка многопоточного приложения выполняется под управлением ОС Windows с использованием стандартного интерфейса для управления потоками и объектами синхронизации и стандартного набора функции для организации ввода-вывода данных. Далее производится портирование операционной среды на аппаратную платформу, адаптированную под данное приложение. Тестирование мини-ОС выполняется с помощью специального набора тестовых приложений. Производится компиляция приложения совместно с мини-ОС под целевую платформу и выполняется оценка производительности полученной системы. Наконец, осуществляется оптимизация приложения и операционной системы. При необходимости производится модификация аппаратной платформы и соответствующих платформеннозависимых частей ОС. Подобный подход позволяет, во-первых, отделить процесс разработки приложения от портирования на специальную аппаратную систему. Вовторых, сама операционная система может быть оптимизирована в соответствии с требованиями приложения.
3. Структура мини-ОС Ядро мини-ОС состоит из следующих модулей: Модуль управления потоками (Task manager) – включает в себя планировщик потоков, а также набор функций для управления 161
состоянием потока. Планировщик задач реализован в виде функции, осуществляющей выбор активного потока и переключение контекста. Модуль динамического распределения памяти (Memory manager) – содержит функции для динамического распределения памяти. Пользователю предоставляются две функции: alloc() для выделения памяти данного размера и free() для освобождения выделенной памяти. Модуль синхронизационных примитивов (Synchronization primitives) – включает в себя функции для создания синхронизационных объектов и управления синхронизационными примитивами: семафоры, мьютексы, бинарные события. Модуль управления прерываниями (Interrupt controller) – представляет собой набор функций для управления состоянием системы прерываний и функцию для инсталляции обработчиков прерываний. Модуль ввода-вывода (IO manager) – предоставляет унифицированный интерфейс для доступа к устройствам ввода-вывода. Модуль начальной загрузки – инициализирует регистры процессора, а также структуры и модули операционной системы. Он инициализирует главную задачу (функция main) и запускает её.
Рис. 1. Базовая структура мини-ОС Для каждого из модулей имеются несколько реализаций в зависимости от конфигурации операционной системы. На Рис. 1 представлена базовая структура мини-ОС. Прерывистыми линиями обозначены так называемые «относительные зависимости», то есть, если один модуль включен в систему, то другой использует функции зависимого модуля. В противном случае используется реализация модуля без использования зависимого модуля. В качестве примера рассмотрим зависимость модуля синхронизационного примитива от модуля управления потоками. Если ОС сконфигурирована как однозадачная, то применяется простой опрос объекта синхронизации. В том 162
случае, когда используется многозадачность, при переходе в режим ожидания сигнала вызывается функция Sleep(), которая переводит текущую задачу в так называемый «спящий» режим и вызывает планировщик задач. При соответствующем изменении состояния объекта синхронизации для «спящей» задачи вызывается функция Resume(), которая переводит ожидающую задачу в активный режим. В минимальной конфигурации мини-ОС может состоять только из блока начальной загрузки, которая инициализирует процессор и передает управление main-функции приложения. Все остальные модули подключаются к системе в соответствии с заданной конфигурацией.
4. Портирование мини-ОС Процесс портирования мини-ОС осуществляется модуль за модулем, то есть для каждого модуля реализуется его платформеннозависимая часть, затем модуль тестируется с помощью соответствующей тестовой программы, и после этого портируется следующий модуль. Такой подход позволяет значительно сократить время локализации ошибок, возникающих при портировании операционной системы. Далее мы рассмотрим набор платформеннозависимых макросов для каждого из модулей, перечисленных в разд. 3. Заметим, что реализация любого макроса требуется только в том случае, если модуль, использующий данный макрос, применяется в требуемой конфигурации. Платформеннозависимая часть модуля управления потоками включает в себя следующие макросы: Сохранение контекста; Восстановление контекста; Сохранение указателя контекста; Загрузка указателя контекста. Для реализации данных макросов необходима следующая информация: Структура контекста, т.е. список регистров, которые отражают состояние процесса в момент прерывания потока. Указатель контекста – регистр, содержащий адрес, по которому сохраняется контекст. Очевидно, что указанные параметры уникальны для каждого конкретного процессора. Поэтому упомянутые макросы должны быть реализованы для каждого нового процессора, на который портируется ОС. Для портирования модуля динамического распределения памяти требуется определение следующих констант: MMU_START_ADDR – начальный адрес динамической памяти; MMU_WORD_SIZE – размер ячейки памяти, соответствующей единичному адресу в байтах; 163
MMU_PAGE_SIZE – размер страницы памяти в байтах; MMU_NUM_PAGES – число страниц памяти, изначально доступных для динамического выделения. Данные константы определяют адресное пространство блока динамически распределяемой памяти. Начальный адрес и размер блока определяется с учетом доступной памяти процессора, не занятой под хранение глобальных данных ОС или приложений, или памяти программ. Для портирования модуля управления прерываниями на новую платформу необходимо реализовать следующие макросы: DISABLE_ALL_IRQS() – запрещает обработку всех маскируемых прерываний; ENABLE_ALL_IRQS() – разрешает обработку маскируемых прерываний; ENABLE_IRQ_N()/DISABLE_IRQ_N() – запрещает/разрешает обработку прерывания N; SET_PRIOR_IRQ_N(prior) – устанавливает приоритет прерывания N, если имеется программируемая система приоритетов. Для реализации этих макросов требуется следующая информация: флаг «запретить все прерывания», запрещающий все прерывания в системе; таблица векторов прерываний; таблица приоритетов прерываний; для каждого прерывания: флаг, запрещающий прерывание; приоритет прерывания или адрес ячейки, содержащий приоритет прерывания. Для портирования модуля синхронизационных примитивов для многопроцессорной системы требуется реализация макроса SYNC_SWAP(addr, read_val, send_val), который считывает из ячейки памяти по адресу addr значение переменной read_val и записывает значение send_val в ту же ячейку. Ключевым моментом, зависящим от особенностей целевой аппаратной платформы, является то, что в течение всего цикла чтения/записи никакой другой процессор или периферийное устройство не должны получить доступ к данной ячейке. Кроме того, требуется определение следующих констант: 1. START_SYNC_RAM – стартовый адрес синхронизационной памяти. 2. SIZE_SYNC_RAM – размер синхронизационной памяти (в байтах). Заметим, что реализация макроса модуля синхронизации требуется только тогда, когда требуется поддержка межпросцессорных взаимодействий через синхронизационную память.
5. Портирование мини-ОС на платформу ARM7TDMI В качестве аппаратной архитектуры для отладки и тестирования были использованы две системы: 164
1. Однопроцессорная система на базе процессора ARM7TDMI. 2. Архитектура ARM_MUSIC, состоящая из 32-х процессоров ARM7, общей памяти и специальной памяти синхронизации. В качестве приложения использовалась многопоточная реализация протокола WLAN 802.11 [7], а также набор специальных тестов для проверки корректной работы каждого отдельного модуля ОС. Для портирования мини-ОС был использован следующий сценарий: 1. Реализация блока начальной загрузки (за основу был взят стандартный boot, предоставленный разработчиками процессора). 2. Портирование модуля динамической памяти. 3. Портирование планировщика задач с программно управляемым вызовом планировщика (вызов планировщика осуществлялся с помощью функции Sleep()). 4. Портирование синхронизационных примитивов (однопроцессорная система). 5. Переход на многопроцессорную систему. Реализация макроса SYN_SWAP(). 6. Реализация драйвера таймера. Вызов планировщика по прерыванию таймера. 7. Портирование приложения WLAN, совместная компиляция WLAN и мини-ОС. Суммарное время портирования операционной среды составило 6 человекодней. Для сравнения, портирование операционной среды EUROS в минимальной конфигурации требует минимум один человеко-месяц.
5. A Case for Nano-Kernels. See-Mong Tan, David K. Raila, Roy H. Campbell Department of Computer Science University of Illinois at Urbana Champaign 1304 W. Springfiel Urbana, IL 61801. 6. A Pattern Language for Porting Micro-kernels. Michel de Champlain Departament of Computer Science, University of Canterbury, Christchurch, New Seland. 7. WLAN 802.11 available at http://grouper.ieee.org/groups/802/11/
6. Заключение В настоящей статье описаны принципы построения и структура операционной системы мини-ОС. Мини-ОС представляет собой набор модулей, каждый из которых может быть исключен из системы без изменения других модулей. При портировании мини-ОС требуется реализация только тех платформозависимых частей программы, которые используются приложением. Это позволяет осуществить пошаговый процесс портирования ОС на новую платформу, что существенно сокращает время, необходимое для портирования и отладки ОС. Литература 1. Wind River Systems, Inc., “VxWorks 5.4”, available at http://www.wrs.com/products/html/vxwks54.html 2. RTEMS available at http://www.rtems.com 3. Eonic Systems, Inc., “Virtuoso v.4.1”, available at http://www.eonic.com/ 4. EUROS documentation and manuals are available at http://www.kaneff.de/
165
166
Межпротокольный шлюз NAT-PT с функциями DNS-ALG и FTP-ALG для обеспечения взаимодействия между сетями IPv4 и IPv6 Г.В. Ключников, Д.С. Мишин, Д.В. Москалев, А.В. Никешин, В.З. Шнитман Аннотация. При переводе Internet на протокол нового поколения IPv6 возникает целый ряд проблем, связанных как с обеспечением взаимодействия между двумя или большим числом островков IPv6, изолированных в мире IPv4, так и с установлением связи (или некоторого вида связи) между существующим миром IPv4 и нарождающимся миром IPv6. В статье рассматриваются вопросы реализации шлюзов прикладного уровня DNSALG и FTP-ALG для межпротокольного шлюза NAT-PT, соответствующих проекту стандарта IETF RFC 2766 “Network Address Translation - Protocol Translation”, а также особенности применения этих средств для обеспечения плавного и безболезненного перехода на протокол IPv6.
1. Введение IPv6 [1, 2] представляет собой новую версию IP-протокола, созданную с целью замены протокола IPv4 [3], который был разработан в конце 70-х годов. IPv6 по сравнению с IPv4 имеет ряд преимуществ, которые будут способствовать будущему росту Internet и упростят конфигурирование и администрирование IP-сетей. IPv6 имеет большее адресное пространство, чем IPv4, модель адресации, которая способствует агрегированию маршрутов, и предлагает мощный механизм автоконфигурирования. Ожидается, что со временем рост Internet и потребность в решениях, обеспечивающих возможность немедленного применения (plug-and-play), приведут к широкой адаптации протокола IPv6. При переводе Internet на протокол нового поколения IPv6 возникает целый ряд проблем, связанных как с обеспечением взаимодействия между двумя или большим числом островков IPv6, изолированных в мире IPv4, так и с установлением связи (или некоторого вида связи) между существующим миром IPv4 и нарождающимся миром IPv6. Как правило, решения первого круга проблем основываются на реализации маршрутизаторов с двойным стеком и организации туннелей IPv4 для передачи трафика IPv6. Для решения второго круга проблем также был предложен ряд механизмов, в основе 167
которых лежат технологии двойного стека, бесконтекстной и контекстной трансляции протоколов, а также организации туннелей IPv6 для передачи трафика IPv4. Данная работа связана с реализацией механизма контекстной трансляции протоколов, поэтому, прежде всего, следует определить, что представляют собой IP/ICMP-трансляторы. Механизм бесконтекстного IP/ICMP-транслятора (SIIT) [4] предполагает установку на границе IPv6-сети специального агента, осуществляющего трансляцию протоколов. При этом IPv6-хостам присваиваются специальные, так называемые IPv4-транслированные, адреса. Приходящие извне IPv4-пакеты перенаправляются этому агенту, проходя которого, они подвергаются преобразованию в формат протокола IPv6 и пересылаются далее к своим получателям. Ответные пакеты, идущие от IPv6-хостов к IPv4-хостам (это индуцируется специальным типом IPv6-адреса назначения), также должны пройти через IP/ICMP-транслятор, но не обязательно через тот же самый, так как сам транслятор является бесконтекстным. Пройдя транслятор, IPv6-пакеты становятся IPv4-пакетами и доставляются по назначению. Удобством этой схемы является ее прозрачность для взаимодействующих хостов и полная бесконтекстность, что существенно облегчает реализацию и использование. К сожалению, спецификация SIIT [4] предполагает, что узлам V6 для организации связи с узлами V4 присваивается V4-адрес (точнее, IPv4-транслированный адрес), но не описывает механизм присваивания этих адресов. Механизм контекстной IP/ICMP трансляции (NAT-PT) [5] является логическим продолжением предыдущего. Для динамического присваивания адресов V6 узлам NAT-PT использует пул V4-адресов, когда через границы V4-V6 инициируются сеансы связи. Предполагается, что V4-адреса являются глобально уникальными. NAT-PT для обеспечения прозрачной маршрутизации дейтаграмм, пересекающих области различной адресации, связывает адреса в сети V6 с адресами в сети V4 и наоборот. Этот механизм не требует проведения каких-либо изменений в оконечных узлах, и маршрутизация IPпакетов для оконечных узлов оказывается совершенно прозрачной. Однако он требует, чтобы в NAT-PT отслеживались поддерживаемые сеансы связи, и предполагает, что входящие и исходящие дейтаграммы, относящиеся к некоторому сеансу, проходят через один и тот же маршрутизатор с установленным NAT-PT. Следует отметить, что некоторые приложения в своих данных передают сетевые адреса. NAT-PT ничего не знает о приложениях и не просматривает данные прикладного уровня. Поэтому для обеспечения работы таких приложений через NAT-PT необходимо использовать шлюзы прикладного уровня ALG (Application Level Gateway) [3]. ALG представляет собой специфического для приложения агента, позволяющего узлу IPv6 взаимодействовать с узлом IPv4 и наоборот. Объединение механизма протокольной трансляции SIIT с возможностями динамической трансляции 168
адресов NAT и соответствующими ALG предоставляет собой полное решение, которое позволит огромному числу широко используемых приложений взаимодействовать между узлами, работающими только на протоколе IPv6, и узлами, работающими только на протоколе IPv4, не требуя внесения никаких изменений в эти приложения. Основное предположение при применении NATPT заключается в том, чтобы он использовался, только если не возможны никакие иные средства взаимодействия между узлами собственно IPv6 или IPv6 через туннели IPv4. Другими словами, цель данного механизма заключается в том, чтобы использовать трансляцию лишь между узлами, работающими только на протоколе IPv6, и узлами, работающими только на протоколе IPv4, в то время как трансляцию между узлами, работающими только на протоколе IPv6, и IPv4-частью узлов с двойным стеком, необходимо реализовывать с помощью других альтернативных механизмов. Данная работа посвящена, главным образом, реализации шлюзов прикладного уровня службы доменных имен DNS-ALG и службы передачи файлов FTPALG, которые являются неотъемлемой частью полного механизма контекстной межпротокольной IP/ICMP-трансляции. Реализация SIIT и базовых функций NAT-PT была выполнена нами ранее при поддержке грантов РФФИ. Ниже приведено детальное описание работы транслятора, за которым следует описание его реализации и применявшихся подходов к тестированию разработанных программных средств.
2. Механизм трансляции протоколов и адресов с сохранением состояния соединений (NAT-PT) 2.1. Разновидности NAT-PT Существует несколько вариантов построения механизма трансляции протоколов и адресов с сохранением состояния соединений. Эти варианты подробно описаны в спецификации RFC 2663 “IP Network Address Translator (NAT) Terminology and Considerations” [6]. Однонаправленный NAT-PT В однонаправленном NAT-PT сеансы связи являются однонаправленными, исходящими из сети IPv6. Он отличается от двунаправленного NAT-PT, который позволяет инициировать сеансы связи в обоих направлениях, исходящем и входящем. В свою очередь, однонаправленный NAT-PT также имеет две разновидности, а именно: основной NAT-PT (Network Address Translation Protocol Translation); NAPT-PT (Network Address / Port Translation Protocol Translation).
169
В основном NAT-PT резервируется блок адресов IPv4, которые используются для трансляции адресов IPv6-хостов, когда они порождают сеансы связи с IPv4хостами во внешнем домене. Для пакетов, исходящих из домена IPv6, транслируются IP-адрес источника и связанные с ним поля, например, контрольные суммы заголовков IP, TCP, UDP и ICMP. Для входящих пакетов транслируются IP-адрес места назначения и перечисленные выше контрольные суммы. Механизм NAPT-PT распространяет понятие трансляции на один шаг дальше, транслируя также и транспортные идентификаторы (например, номера портов TCP и UDP, идентификаторы запросов ICMP). Это позволяет мультиплексировать транспортные идентификаторы некоторого числа IPv6-хостов в транс-портные идентификаторы единственного IPv4-адреса. Таким образом, NAPT-PT позволяет множеству IPv6-хостов разделять один IPv4-адрес. Заметим, что NAPT-PT может быть объединен с основным NAT-PT так, что вместе с трансляцией портов будет использоваться целый пул внешних адресов. Двунаправленный NAT-PT При использовании двунаправленного NAT-PT сеансы связи могут порождаться узлами как из IPv4-сети, так и из IPv6-сети. Адреса IPv6-сети связываются с IPv4-адресами статически или динамически в тот момент времени, когда в любом из направлений устанавливаются соединения. Предполагается, что пространство имен между хостами в сетях IPv4 и IPv6 (т.е. их полностью квалифицированные доменные имена) является насквозь уникальным. Хосты в области IPv4 обращаются к хостам в области IPv6, используя для разрешения адресов службу доменных имен DNS. Для отображения имен в адреса совместно с двунаправленным NAT-PT должен применяться шлюз прикладного уровня службы доменных имен DNS-ALG (DNS Application Level Gateway) [7]. В частности, DNS-ALG должен транслировать IPv6-адрес в запросах и ответах DNS в соответствующий ему IPv4адрес и наоборот, когда пакеты DNS пересекают адресные области IPv6-IPv4. Для адресации IPv6-узлов используются реальные IPv6-адреса, схему назначения которых NAT-PT не определяет. Узлы IPv6 узнают адреса узлов IPv4, выполняя запросы к серверам DNS в домене IPv4 или к серверу DNS, внутреннему для IPv6-сети. Узел IPv6, которому необходимо взаимодействовать с узлом IPv4, должен использовать специальный префикс (PREFIX::/96) перед IPv4-адресом узла IPv4, который задается конфигурацией NAT-PT. Описанный выше способ позволяет использовать этот префикс без какого-либо конфигурирования узлов. NAT-PT ничего не знает о приложениях и не просматривает данные прикладного уровня. Поэтому, как уже отмечалось, для обеспечения работы через NAT-PT приложений, которые в своих данных передают сетевые адреса, необходимо использование шлюзов прикладного уровня ALG (Application Level Gateway). По существу, шлюз прикладного уровня является специфическим для конкретного приложения агентом, который позволяет узлу IPv6 взаимодействовать с узлом IPv4 и наоборот. Спецификация NAT-PT [5] в 170
общих чертах описывает работу только шлюза прикладного уровня службы доменных имен – DNS-ALG и шлюза прикладного уровня службы передачи файлов – FTP-ALG.
2.2. Принципы функционирования NAT-PT
Если исходящий пакет не является пакетом инициализации сеанса связи, NATPT должен уже хранить некоторое состояние о соответствующем сеансе, включающее присвоенный IPv4-адрес и другие параметры для трансляции. Если такого состояния не существует, пакет должен молча отбрасываться.
Работа основного NAT-PT
NAT-PT
Работа основного NAT-PT иллюстрируется примером, взятым из RFC 2766 [5], и последующим описанием этого примера (рис. 1).
IPv6-A
IPv4-C
NAT-PT FEDC:BA98::7654:3211
FEDC:BA98::7654:3210
Сеть IPv4
Таблица соответствия адресов IPv6-адрес IPv4-адрес FEDC:BA98::7654:3210 120.130.26.10
IPv6-B IPv4-C IPv6-A
NAT-PT имеет пул IPv4-адресов 120.130.26/24 и хранит таблицу соответствия между IPv6-адресами и выделенными им IPv4-адресами FEDC:BA98::7654:3210
132.146.243.30
IPv6-пакет SA = FEDC:BA98::7654:3210 DA = PREFIX::132.146.243.30
IPv4-пакет SA = 120.130.26.10 DA = 132.146.243.30
132.146.243.30
Рис. 1. Работа основного NAT-PT
Рис. 2. Трансляция адресов в NAT-PT
NAT-PT имеет пул адресов, включающий IPv4-подсеть 120.130.26/24. IPv4адреса из этого пула могут быть присвоены IPv6-адресам оконечных узлов IPv6 статически с сохранением взаимно однозначного соответствия. В этом случае потребуется столько IPv4-адресов, сколько имеется оконечных узлов IPv6. Однако мы предполагаем, что количество IPv4-адресов в пуле меньше, чем число оконечных узлов IPv6. Поэтому по крайней мере для некоторых из этих узлов требуется динамическое распределение адресов. Пусть, например, узел IPv6-A хочет взаимодействовать с узлом IPv4-C. Узел IPv6-A создает и посылает пакет со следующими полями:
Если пакет является пакетом инициализации сеанса связи, NAT-PT локально распределяет адрес из своего пула адресов (например, назначает адрес 120.130.26.10) и транслирует этот пакет в IPv4 (рис.2). Параметры трансляции кэшируются на время сеанса связи, и отображение IPv6 на IPv4 запоминается в NAT-PT. Результирующий IPv4-пакет имеет SA=120.130.26.10 и DA=132.146.243.30. Любой возвращаемый пакет будет распознаваться NAT-PT как принадлежащий тому же самому сеансу связи. Для этого NAT-PT будет использовать сохраненную информацию о состоянии соединения и соответствующим образом транслировать такой пакет. При этом результирующие адреса будут равны SA=PREFIX::132.146.243.30, DA=FEDC:BA98::7654:3210. Заметим, что пакет с такими адресами может теперь маршрутизироваться как обычный пакет внутри оконечной сети, работающей только по протоколу IPv6.
Source Address, SA = FEDC:BA98::7654:3210 Destination Address, DA = PREFIX::132.146.243.30 Примечание: Префикс PREFIX::/96 выбирается произвольно и задается в конфигурации NAT-PT. Пакеты, адресованные по этому префиксу, будут маршрутизироваться на NAT-PT. Требуется только, чтобы заранее конфигурируемый PREFIX был бы маршрутизируемым в рамках домена IPv6, т.е. это может быть любой маршрутизируемый префикс, который выберет администратор сети. Этот IPv6-пакет маршрутизируется через шлюз NAT-PT, на котором он транслируется в формат IPv4. 171
Работа NAPT-PT Как уже отмечалось, механизм NAPT-PT (Network Address Port Translation + Protocol Translation) позволяет IPv6-узлам взаимодействовать с IPv4-узлами, используя всего один IPv4-адрес. При этом порты TCP/UDP узлов IPv6 транслируются в порты TCP/UDP этого глобального IPv4-адреса. 172
Смысл введения механизма NAPT-PT заключается в следующем. Если в обычном NAT-PT исчерпается пул IPv4-адресов, выделенных для целей трансляции, то ни один новый IPv6-узел больше не сможет открывать сеансы связи с внешним миром через NAT-PT. С другой стороны, NAPT-PT позволит открыть максимально до 63K сеансов TCP и до 63K сеансов UDP. Модифицируем рассмотренный выше пример, предполагая, что на пограничном маршрутизаторе вместо NAT-PT мы имеем NAPT-PT, и все IPv6адреса будут отображаться на единственный IPv4-адрес 120.130.26.10 (рис. 3). IPv6-A
NAPT-PT IPv4-C
132.146.243.30
FEDC:BA98::7654:3211
Таблица соответствия адресов и портов IPv6-адрес TCP IPv4-адрес порт FEDC:BA98::7654:3210 3017 120.130.26.10 FEDC:BA98::7654:3211 4077 120.130.26.10
IPv6-пакет (IPv6-A) Src addr = FEDC:BA98::7654:3210, Src TCP port = 3017 Dst addr = PREFIX::132.146.243.30, Dst TCP port = 23
SA=120.130.26.10, source TCP port = 1026 DA=132.146.243.30, destination TCP port = 110 Возвращаемый трафик от узла с адресом 132.146.243.30 с TCP-порта 110, посылаемый на адрес 120.130.26.10 и порт 1026, будет распознан как принадлежащий сеансу связи с узлом IPv6-B и будет транслироваться обратно в IPv6 следующим образом:
TCP порт 1025 1026
IPv4-пакет (IPv4-C) Src addr = 120.130.26.10, Src TCP port = 1025 Dst addr = 132.146.243.30, Dst TCP port = 23
IPv6-пакет (IPv6-B) Src addr = FEDC:BA98::7654:3211, Src TCP port = 4077 Dst addr = PREFIX::132.146.243.30, Dst TCP port = 110
SA = PREFIX::132.146.243.30, source TCP port = 23 DA = FEDC:BA98::7654:3210, destination TCP port = 3017 Теперь предположим, что узел IPv6-B устанавливает TCP-соединение с узлом IPv4-C на порт 110. Узел IPv6-B формирует и посылает пакет со следующими параметрами: Source Address, SA=FEDC:BA98::7654:3211 , source TCP port = 4077 Destination Address, DA = PREFIX::132.146.243.30, destination TCP port = 110 Когда этот пакет поступит в NAPT-PT, последний присвоит следующий свободный порт из списка TCP-портов выделенного IPv4-адреса и оттранслирует его в пакет IPv4 со следующими параметрами:
FEDC:BA98::7654:3210 IPv6-B
DA=132.146.243.30, destination TCP port = 23 Возвращаемый трафик от узла с адресом 132.146.243.30 с TCP-порта 23, посылаемый на адрес 120.130.26.10, порт 1025, будет распознан как принадлежащий тому же самому сеансу и будет транслироваться обратно в IPv6 следующим образом:
SA = PREFIX::132.146.243.30, source TCP port = 110 DA = FEDC:BA98::7654:3211, destination TCP port = 4077 Заметим, что входящие сеансы связи при использовании механизма NAPT-PT ограничиваются одним сервером на сервис, назначаемым с помощью статического отображения TCP/UDP портов. Например, в домене IPv6 только узел IPv6-A из нашего примера может быть HTTP-сервером (port 80). Пусть узел IPv4-C посылает пакет:
IPv4-пакет (IPv4-C) Src addr = 120.130.26.10, Src TCP port = 1026 Dst addr = 132.146.243.30, Dst TCP port = 110
Рис. 3. Работа основного NAPT-PT. Предположим, что узел IPv6-A создает сеанс TCP с узлом IPv4-C. Узел IPv6-A формирует и посылает пакет со следующими параметрами: Source Address, SA=FEDC:BA98::7654:3210 , source TCP port = 3017 Destination Address, DA = PREFIX::132.146.243.30, destination TCP port = 23 Когда этот пакет поступит в NAPT-PT, последний присвоит один из TCPпортов выделенного IPv4-адреса для трансляции кортежа (Source Address, Source TCP port) следующим образом: SA=120.130.26.10, source TCP port = 1025 173
SA=132.146.243.30, source TCP port = 1025 DA=120.130.26.10, destination TCP port = 80 NAPT-PT оттранслирует этот пакет следующим образом: SA=PREFIX::132.146.243.30, source TCP port = 1025 DA=FEDC:BA98::7654:3210, destination TCP port = 80 Таким образом, в приведенном выше примере все сеансы связи, которые достигают NAPT-PT с портом места назначения равным 80, будут переадресованы на один и тот же узел IPv6-A. Трансляция протоколов Трансляция заголовков IPv4 в заголовки IPv6 и обратно выполняется точно так же, как и в SIIT (RFC 2765) [4], но имеются некоторые изменения, которые 174
требуется внести в SIIT, поскольку NAT-PT выполняет также и трансляцию сетевых адресов. Эти изменения незначительны и детально описаны в спецификации NAT-PT RFC 2766 [5].
3. Шлюз прикладного уровня DNS-ALG Отображения имен на IPv4-адреса хранятся в DNS в виде записей о ресурсах (RR – resource record) типа "A". Отображения имен на IPv6-адреса хранятся в DNS в виде записей о ресурсах типа "AAAA" [7, 8]. Использование записи типа "A6" для отображения имен на IPv6-адреса мы не рассматриваем. Отображения адресов в имена хранятся в виде записей "PTR": для IPv4-адресов в зоне "IN-ADDR.ARPA", для IPv6 адресов в зоне "IP6.INT". Соответственно, и запросы могут быть типа "A", "AAAA" и "PTR". Только эти типы запросов и записи о ресурсах в ответах подлежат трансляции. Структурная схема DNS при использовании DNS-сервера внутри сети IPv6 показана на рис. 4. DNS-сервер IPv4 с именем DNS-IPv4 является полномочным сервером для какой-либо зоны, например, зоны "ispras.ru", и его имя – DNS-IPv4.ispras.ru. Сервер DNS-IPv4 делегирует полномочия для подзоны "ipv6.ispras.ru" серверу DNS-IPv6 с именем DNS-IPv6.ipv6.ispras.ru. Для передачи полномочий сервер DNS-IPv4 должен иметь так называемую «связующую запись» (glue record), т.е. запись о ресурсе типа "A", содержащую IPv4-адрес сервера DNS-IPv6.ipv6.ispras.ru (хотя это уже не его зона). Например: ipv6.ispras.ru. IN NS DNS-IPv6.ipv6.ispras.ru. IN A
DNS-IPv6.ipv6.ispras.ru. 195.208.53.20
Поэтому для внутреннего DNS-сервера сети IPv6 необходимо выделить фиксированный IPv4-адрес из пула IPv4-адресов NAT-PT и статически отобразить его на IPv6-адрес сервера DNS-IPv6. Таким образом, NAT-PT должен хранить взаимно однозначное соответствие между этим IPv4-адресом и IPv6-адресом внутреннего DNS-IPv6 сервера, и такая запись должна присутствовать в статических записях. IPv6 сеть DNS-IPv6
IPv4 сеть NAT-PT
DNS-IPv4
DNS-ALG
Рис. 4. Структурная схема DNS при использовании NAT-PT. 175
IPv6-адрес DNS-IPv4 сервера формируется из его IPv4-адреса и префикса (PREFIX::/96). IPv6-узлы должны использовать этот адрес при обращении к DNS-IPv4 серверу из сети IPv6. Значения времени жизни (TTL) для всех DNS-записей о ресурсах, передающиеся через NAT-PT, должны быть установлены в 0 так, чтобы DNSсерверы/клиенты не кэшировали временно присвоенные RR. Однако спецификация допускает и значение 1, которое в некоторых реализациях DNSклиентов, содержащих ошибки, работает лучше. В нашей реализации мы используем значение 1. Для статически отображаемых адресов значения TTL должны оставаться неизменными. В общем случае пакеты DNS переносятся с помощью протоколов TCP или UDP [9, 10]. Сервер DNS слушает запросы на порте 53. Мы рассматриваем работу DNS только по протоколу UDP, поскольку это упрощает реализацию; кроме того, в настоящее время для переноса запросов DNS транспорт TCP практически не используется, а передача зон DNS через границы IPv4/IPv6 не рекомендуется. Таким образом, все пакеты UDP, у которых значение порта источника или места назначения равно 53, рассматриваются как пакеты DNS и должны передаваться для обработки в DNS-ALG. Ниже представлена подробная спецификация функционирования шлюза DNS ALG.
3.1. Трансляция запросов со стороны IPv4 и ответов на них Запросы на разрешение имен домена IPv6 от IPv4-узлов направляются на сервер DNS-IPv6. Они могут иметь тип "A", "AAAA" и "PTR", но запросы типа "AAAA" не транслируются и передаются без изменения. DNS-ALG будет модифицировать DNS-запросы следующим образом: a)
Для запросов "по имени – адрес" типа "A": Изменяет поле QTYPE (тип запроса) с "A" (1) на "AAAA" (28). b) Для запросов "по адресу – имя" типа "PTR": Если существует однозначное отображение для IPv4-адреса, указанного в запросе, заменяет в поле QNAME строку "IN-ADDR.ARPA" строкой "IP6.ARPA". Кроме того, заменяет октеты адреса IPv4, следующие в обратном порядке и предшествующие строке "IN-ADDR.ARPA", соответствующими октетами адреса IPv6, следующими в обратном порядке. Это означает, что данный адрес выделен конкретному узлу, например, какому-либо серверу, и никакой другой IPv6-адрес для него не будет использоваться (это справедливо для адресов, участвующих только в адресной трансляции). При этом изменяется размер пакета, и модуль NAT-PT должен пересчитать контрольную сумму псевдозаголовка UDP, а также скорректировать IP-заголовок (поле длины пакета). Если отображения не существует, отбрасывает пакет.
176
После трансляции запроса в IPv6-пакет последний направляется серверу DNSIPv6. DNS-IPv6 обрабатывает запрос и возвращает ответ, который снова попадает в DNS-ALG и обрабатывается следующим образом:
FEDC:BA98::1110:2220 <-> 195.208.53.20 FEDC:BA98::7654:3210 <-> 195.208.53.21
a)
Со стороны IPv4 приходит запрос на разрешение имени:
Для ответов на запрос "по имени – адрес" типа "AAAA" в соответствующей записи о ресурсе: Изменяет тип записи в поле TYPE с "AAAA" на "A". Если существует отображение данного IPv6-адреса на IPv4-адрес, заменяет IPv6-адрес в поле RDATA (разрешенный сервером DNS-IPv6) на IPv4-адрес. Если отображения не существует, то именно в этот момент DNS-ALG выделяет для данного IPv6-адреса один из свободных адресов из пула IPv4-адресов NAT-PT и делает соответствующую DNS-запись в своей внутренней таблице. Далее он заменяет IPv6-адрес в поле RDATA на вновь выделенный IPv4-адрес. Если же свободного IPv4-адреса в пуле адресов NAT-PT не существует, отбрасывает такой пакет. В этом случае клиент будет повторно передавать запрос на разрешение имени и, возможно, за это время в пуле адресов появится свободный IPv4-адрес. Кроме того, в этом случае клиентское приложение получит сообщение об ошибке DNS-сервиса. Оно может отложить установление соединения на некоторое время и позже снова попытаться его установить. Корректирует поле RDLENGTH, сокращая его значение на 96 бит. Устанавливает поле TTL в 1. В модуле NAT-PT необходим пересчет контрольных сумм и изменение размера пакета в заголовке IP. b) Для ответов на запрос "по адресу – имя" типа "PTR" в соответствующей записи о ресурсе: Если ответ присутствует и существует отображение IPv6-адреса, указанного в секции ответов, на IPv4-адрес, то заменяет в поле RDATA строку "IP6.INT" строкой "IN-ADDR.ARPA". А также заменяет октеты адреса IPv6, следующие в обратном порядке и предшествующие строке "IP6.INT", на соответствующие октеты адреса IPv4, следующие в обратном порядке и предшествующие строке "IN-ADDR.ARPA". Если ответ отсутствует, переправляет такую запись без изменений. Если отображения не существует, отбрасывает такой пакет. Корректирует поле RDLENGTH. Устанавливает поле TTL в 1. В модуле NAT-PT необходим пересчет контрольных сумм и изменение размера пакета в заголовке IP. Пример Предположим, сервер DNS-IPv6 имеет следующие записи: node-a.ipv6.ispras.ru. DNS-IPv6.ipv6.ispras.ru.
IN AAAA IN AAAA
FEDC:BA98::7654:3210 FEDC:BA98::1110:2220
177
В DNS-ALG имеются следующие отображения адресов на IPv4-адреса:
SrcAddr = 132.146.243.30, source UDP port = 1025 DstAddr = 195.208.53.20, destination UDP port = 53 node-a.ipv6.ispras.ru, type = A, class = IN
DNS-ALG транслирует запрос: SrcAddr = PREFIX::132.146.243.30, source UDP port = 1025 DstAddr = FEDC:BA98::1110:2220, destination UDP port = 53 node-a.ipv6.ispras.ru, type = AAAA, class = IN
и направляет его в сеть IPv6. DNS-IPv6 сервер возвращает ответ: DstAddr = PREFIX::132.146.243.30, destination UDP port = 1025 SrcAddr = FEDC:BA98::1110:2220, source UDP port = 53 node-a.ipv6.ispras.ru. IN AAAA FEDC:BA98::7654:3210
Этот ответ перехватывается и транслируется DNS-ALG в следующий вид: DstAddr = 132.146.243.30, destination UDP port = 1025 SrcAddr = 195.208.53.20, source UDP port = 53 node-a.ipv6.ispras.ru. IN A 195.208.53.21
и отправляется запрашивающему узлу.
3.2. Трансляция запросов со стороны IPv6 и ответов на них В узлах IPv6 для получения IP-адреса удаленной стороны по имени используются запросы типа "A" и "AAAA", а для получения имени по IPадресу – запросы типа "PTR". DNS-ALG обрабатывает их следующим образом: a)
Запрос "по имени – адрес" типа "A": Пересылает такой запрос без изменений, но запоминает идентификатор DNS-сообщения (поле ID), чтобы при поступлении ответа иметь возможность определить, что это ответ на запрос типа "A", и не производить его трансляцию в ответ типа "AAAA". b) Запрос "по имени – адрес" типа "AAAA":
Заметим, что для такого запроса возникает неоднозначность в реализации. С одной стороны, RFC 2766 определяет, что необходимо переслать запрос типа "AAAA" без изменения и дополнительно выслать запрос типа "A" для того же имени. Тогда, если в удаленном узле имеются оба типа записей (например, это двухстековый узел), то запрашивающий IPv6-узел получит два ответа типа "AAAA": с глобальным IPv6-адресом и IPv6-адресом вида PREFIX::Х.Х.Х.Х. В 178
этом случае в узле-источнике запроса возникает проблема выбора адреса места назначения, т.е. не очевидно, какой из адресов он выберет. С другой стороны, если вообще не посылать запись типа "AAAA", предполагая, что NAT-PT предназначен для работы только с IPv4-узлами, то исключается возможность взаимодействия с удаленным узлом, если он, например, двухстековый, используя другие средства типа туннелей. Но мы все же придерживаемся последнего способа (расширить функциональность, связанную с посылкой еще и запроса типа "AAAA", не составит проблем, если это будет необходимо). Поэтому DNS-ALG транслирует запрос следующим образом: c)
Изменяет поле QTYPE (тип запроса) с "AAAA" (28) на "A" (1). Других изменений не происходит, и размер пакета не изменяется. d) Запрос "по адресу – имя " типа "PTR": Если в поле QNAME октеты адреса IPv6, следующие в обратном порядке и предшествующие строке "IP6.INT", содержат PREFIX::/96, то заменяет строку "IP6.INT" строкой "IN-ADDR.ARPA", а сами октеты адреса IPv6 – на соответствующие октеты адреса IPv4, следующие в обратном порядке и предшествующие строке "IN-ADDR.ARPA". При этом в модуле NAT-PT необходим пересчет контрольных сумм и изменение размера пакета в заголовке IP. Если в поле QNAME октеты адреса IPv6 PREFIX::/96 не содержат, запрос отправляется без изменений. При приходе ответа от сервера DNS-IPv4: a)
В ответе на запрос "по имени – адрес" во всех записях о ресурсах типа "A": Проверяет по ранее сохраненному идентификатору запроса, не является ли он ответом на запрос типа "A". Если является, пересылает ответ без изменений. Иначе DNS-ALG выполняет трансляцию: o Изменяет поле TYPE (тип записи) с "A" на "AAAA"; o Заменяет в поле RDATA IPv4-адрес на IPv6-адрес вида PREFIX::Х.Х.Х.Х, где Х.Х.Х.Х представляет данный IPv4-адрес; o Корректирует поле RDLENGTH; o Устанавливает поле TTL в 1. В модуле NAT-PT необходим пересчет контрольных сумм и изменение размера пакета в заголовке IP. b) В ответе на запрос "по адресу – имя" во всех записях о ресурсах типа "PTR": Если поле NAME содержит подстроку "IN-ADDR.ARPA" o Заменяет строку "IN-ADDR.ARPA" строкой "IP6.INT", а также октеты адреса IPv4, следующие в обратном порядке и предшествующие строке "IN-ADDR.ARPA", соответствующими октетами адреса IPv6 вида PREFIX::Х.Х.Х.Х, следующими в 179
обратном порядке и предшествующими строке "IP6.INT"; o Корректирует поле RDLENGTH; o Устанавливает поле TTL в 1. В модуле NAT-PT необходим пересчет контрольных сумм и изменение размера пакета в заголовке IP. Если подстрока "IN-ADDR.ARPA" не присутствует, пересылает сообщение без изменения.
4. Шлюз прикладного уровня FTP-ALG Для обеспечения прозрачной работы между узлами сетей IPv4 и IPv6 на прикладном уровне по протоколу FTP межпротокольный шлюз NAT-PT необходимо расширить функциями FTP-ALG. Протокол FTP использует два TCP-соединения: управляющее соединение (control connection) для передачи FTP-команд и соединение для передачи данных (data connection). Командное соединение всегда инициируется клиентом на TCP-порт 21 сервера. Команды на установление соединения данных содержат информацию об IP-адресе и номере порта. RFC 959 "File Transfer Protocol" [11] определяет две такие команды: PORT и PASV. RFC 2428 "FTP Extensions for IPv6 and NATs" [12] фактически заменяет их на команды EPRT и EPSV. Соединение данных устанавливается всякий раз, когда необходимо передать данные, и закрывается по окончании передачи данных. Все команды FTP передаются по управляющему соединению. Так как для корректной работы протокола FTP необходимо транслировать только управляющие команды PORT, PASV, EPRT, EPSV, шлюз прикладного уровня FTP-ALG должен обрабатывать только управляющие соединения FTP. Соединения данных остаются без изменения (т.е. FTP-ALG их не затрагивает). Управляющее соединение инициируется клиентом на TCP-порт 21 сервера. Поэтому все приходящие TCP-пакеты, у которых значение порта отправителя или получателя равно 21, направляются в модуль FTP-ALG. Модуль FTP-ALG работает с собственным списком записей типа FTP. Различаются два типа сеансов FTP: порождаемые со стороны IPv4 и порождаемые со стороны IPv6.
4.1. Соединения, порождаемые клиентом IPv4 Клиент IPv4 может порождать команды PORT и PASV, и, если он реализует RFC 2428, команды EPRT и EPSV. При этом команды PORT, PASV транслируются в команды EPRT и EPSV, соответственно. Кроме того, ответ EPSV перед отправкой клиенту IPv4 транслируется в ответ PASV. Трансляция команды PORT Команда PORT h1,h2,h3,h4,p1,p2 транслируется в команду
EPRT<space>. 180
Поле устанавливается равным 2 (AF_INET6), IPv4-адрес h1,h2,h3,h4 транслируется в IPv6 в представлении ASCII, IPv4 TCP-порт p1,p2 в десятичное значение IPv6 порта в представлении ASCII. Примечание. Кроме того, необходимо транслировать и ответ EPRT в ответ PORT, так как символьная строка ответа может включать описание команды, которая выполнилась или не выполнилась успешно. Например, Клиент: PORT 195,208,32,215,192,89 После трансляции: EPRT |2|1080::8:800:200C:417A|5282| Ответ: 200 EPRT command successful. После трансляции: 200 PORT command successful. Трансляция команды PASV Команда PASV транслируется в команду EPSV<space> Поле устанавливается равным 2 (AF_INET6). Ответ EPSV: 229 () транслируется в ответ PASV: 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) Так как в ответе EPSV не присутствует IPv6-адрес, то он получается из поля адреса отправителя IPv6-пакета и транслируется в IPv4-адрес h1,h2,h3,h4. Этот адрес может быть получен из соответствующего поля записи FTP-ALG. Поле IPv6-порта из ответа EPSV транслируется в IPv4-порт p1,p2. Если используется NAPT, необходима предварительная трансляция порта в соответствующее ему IPv4-значение перед переводом в p1,p2. Если клиент использует команды EPRT и EPSV, то FTP-ALG будет транслировать только параметры этих команд. Трансляция команды EPRT Поле транслируется из 1 (AF_INET) в 2 (AF_INET6), поле IPv4адреса – в соответствующий ему IPv6-адрес в представлении ASCII, поле порта не транслируется. Трансляция команды EPSV В команде EPSV транслируется поле из 1 (AF_INET) в 2 (AF_INET6). В ответе EPSV поле транслируется только в том случае, если используется порт-трансляция NAPT. Примечание. При трансляции команд PASV и EPSV со стороны IPv4-клиентов необходимо запомнить, какая именно команда была транслирована, чтобы корректно произвести трансляцию ответа. Если производится трансляция 181
ответа от команд PORT и EPRT, то для них также необходимо знать, какая была команда запроса.
4.2. Соединения, порождаемые IPv6 клиентом Возможны два подхода: 1) Транслировать v6-команды EPRT, EPSV в v4-команды EPRT, EPSV, и v4-ответы EPSV в v6-ответы EPSV; 2) Транслировать v6-команды EPRT и EPSV в команды PORT и PASV, и ответы PASV в ответы EPSV. В первом случае транслируются параметры команд , , из значений v6 в соответствующие им значения v4. При этом v4-сервер должен поддерживать команды EPRT и EPSV [12], иначе v4-FTP-сервер будет возвращать ошибку типа: “500 'EPSV': command not understood”, и v6-FTPклиент не сможет работать. Возможным решением является следующее: не переправлять клиенту ответ об ошибке, а запомнить команду (и все необходимые данные), и в случае такой ошибки транслировать команду в PORT или PASV и далее действовать по второму варианту. Во втором случае будет невозможно транслировать команду “EPSV<space>ALL”, по которой клиент извещает сервер, что в дальнейшей работе он будет использовать только команду EPSV; при этом сервер должен отбрасывать все другие команды данных PORT, PASV и EPRT. Эту ситуацию можно обойти, просто отвечая v6-клиенту на команду “EPSV<space>ALL” сообщением об успешном выполнении команды от имени сервера, но при этом реально не посылая серверу никакую команду. Этому варианту мы и следуем. Трансляция команды EPRT Команда EPRT<space> транслируется в команду PORT h1,h2,h3,h4,p1,p2. IPv6-адрес в представлении ASCII транслируется в IPv4-адрес h1,h2,h3,h4, десятичное значение IPv6-порта в представлении ASCII транслируется в IPv4-значение порта p1,p2. Примечание. Кроме того, необходимо транслировать и ответ PORT в ответ EPRT, так как символьная стока ответа может включать название команды, которая выполнилась или не выполнилась успешно. Трансляция команды EPSV Команда EPSV<space> транслируется в PASV. Ответ PASV: 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2) транслируется в ответ EPSV: 182
229 () Поле IPv4-порта p1,p2 из ответа PASV транслируется в поле IPv6-порта . Изменения значения порта не производится (нет NAPT). Примечание. Во всех случаях нет необходимости знать, какая была предыдущая команда. Примечание для всех случаев. Кроме того, необходимо обрабатывать ошибочные коды возврата. Так, например, в RFC 2428 [12] вводится код ошибки 522 и v4-клиент может его не понимать.
4.3. Коррекция заголовков пакетов управляющих соединений Трансляция данных управляющих команд может привести к изменению размера пакета. Это приводит к изменению порядковых номеров TCP в управляющем соединении в обоих направлениях (sequence and acknowledgment numbers) и к изменению полей размеров пакета в заголовках IP (IPv4 Total Length и IPv6 Payload Length). Потому FTP-ALG должен хранить дельты порядковых номеров для обоих направлений управляющего соединения, чтобы корректировать порядковые номера. Величина дельты получается путем вычитания длины команды после трансляции от длины команды до трансляции и прибавляется к предыдущему сохраненному значению этой дельты. Заметим, что разница может быть отрицательной. Обозначим дельту для исходящих соединений (из v6 в v4) как out, а для входящих (из v4 в v6) как in. Если пришедший пакет является пакетом из v6 в v4-область (исходящий) и в нем изменяется длина команды FTP, то разница между длиной v4 результирующей команды (после трансляции) и длиной оригинальной v6-команды (до трансляции) прибавляется к out. При этом если длина v4-команды будет меньше длины v6-команды, разница будет отрицательная. Если пришедший пакет является пакетом из v4 в v6-область (входящий) и в нем изменяется длина команды FTP, то разница между длиной результирующей v6-команды и длиной v4-команды прибавляется к in. При этом, если длина v6-команды будет меньше длины v4-команды, разница будет отрицательная. Коррекция дельты производится после трансляции пакета, так как порядковый номер пакета указывает на первый байт данных, а номер подтверждения – на следующий байт, который противоположная сторона ожидает принять. Тогда при приходе пакета от v6-хоста (и это исходящий пакет управляющего соединения) порядковый номер (sequence number, SN) этого пакета увеличивается на out, а порядковый номер подтверждения (acknowledgment number, AN) этого же пакета уменьшается на in. При приходе пакета от v4-
183
хоста (и это входящий пакет управляющего соединения) SN этого пакета увеличивается на in, а AN этого же пакета уменьшается на out. Поясним вышесказанное примером: 1) (OUT) IPv4 L4 = 20 SN΄out = SNout + out = 100 AN΄out = ANout - in = 500
IPv6 out = 0, in = 0 L6 = 50 ΄out = -30, ΄in = 0 SNout = 100 ANout = 500
Результирующие дельты: out += ΄out = -30, in += ΄in = 0 2) (IN) IPv4 L4 = 40 SNin = 500 ANin = 120
IPv6 out = -30, in = 0 L6 = 60 ΄out = 0, ΄in = 20 SN΄in = SNin + in = 500 AN΄in = ANin - out = 150
Результирующие дельты: out += ΄out = -30, in += ΄in = 20 3) (OUT) IPv4 L4 = 40 SN΄out = SNout + out = 120 AN΄out = ANout - in = 540
IPv6 out = -30, in = 20 L6 = 40 ΄out = 0, ΄in = 0 SNout = 150 ANout = 560
Результирующие дельты: out += ’out = -30, in += ’in = 20 4) (IN) IPv4 L4 = 40 SNin = 540 ANin = 160
IPv6 out = -30, in = 20 L6 = 60 ’out = 0, ’in = 20 SN΄in = SNin + in = 560 AN΄in = ANin - out = 190
Результирующие дельты: out += ΄out = -30, in += ΄in = 40 5) (OUT) IPv4 L4 = 30 SN΄out = SNout + out = 160 AN΄out = ANout - in = 580
IPv6 out = -30, in = 40 L6 = 40 ΄out = -10, ΄in = 0 SNout = 190 ANout = 620
Результирующие дельты: out += ΄out = -40, in += ΄in = 40 Описанный сценарий справедлив для случая, когда на каждый пакет, посланный с одной стороны, приходит ответный пакет с другой. Но когда с одной из сторон последовательно приходит несколько пакетов, а ответы на них приходят позже, происходит неправильная коррекция дельт, и последующие ответы будут транслированы неверно. Для того чтобы избежать таких ситуаций, необходимо вести историю дельт для каждого пакета, сохраняя значения SN и AN и текущие значения дельт для них и выставлять тайм-аут для каждой записи. Тогда по приходу ответного пакета можно применять для преобразования нужные дельты. Сразу удалять такую запись нельзя, так как возможны повторные передачи пакетов с одинаковыми 184
SN и AN. Кроме того, возможны ситуации, когда приходит ACK, а затем с той же стороны приходит, например, PUSH, в котором присутствует ACK с тем же номером. Еще нужно учесть возможность прихода ACK не на каждый пакет, а на несколько пакетов PUSH сразу, с указанием последнего принятого байта. По истечении тайм-аута запись будет удаляться сборщиком мусора. Если приходит пакет TCP-сеанса, и для номеров SN и AN не существует записи с дельтами, это означает, что данный пакет – либо заблудившийся, либо ошибочный, либо посланный преднамеренно для атаки. Такой пакет можно либо отбросить, либо переправить без изменений на откуп принимающей стороны. Мы выбрали первый вариант.
5. Ограничения, присущие механизму NAT-PT Использование механизмов трансляции адресов и протоколов связано с целым рядом ограничений. Все ограничения, присущие общему механизму трансляции сетевых адресов (NAT), в той же мере присущи и NAT-PT. Ниже подробно рассматриваются наиболее важные из этих ограничений, а также некоторые ограничения, присущие только механизму NAT-PT. Ограничения топологии сети Требуется, чтобы все запросы и ответы, относящиеся к одному сеансу связи, маршрутизировались через один и тот же маршрутизатор NAT-PT. Одним из способов обеспечения этого требования может быть организация NAT-PT на пограничном маршрутизаторе, который является уникальным для оконечного IPv6-домена, когда все IP-пакеты либо порождаются из этого домена, либо этот домен является их местом назначения. Это общая проблема, связанная с NAT, и она полностью описана в [6]. Заметим, что это ограничение не распространяется на пакеты, которые порождаются из узлов с двойным стеком или направляются на узлы с двойным стеком и не требуют трансляции адресов. Это справедливо, поскольку в устройстве с двойным стеком IPv4-адреса, заключенные в V6-адрес, могут быть идентифицированы по формату адреса PREFIX::x.y.z.w, и маршрутизатор с двойным стеком может соответствующим образом маршрутизировать пакет между V4 и узлами с двойным стеком без отслеживания информации о состоянии. Это ограничение также не должно влиять на взаимодействие между одной сетью IPv6 и другой сетью IPv6, и в действительности трансляцию нужно использовать только в тех случаях, когда невозможно применение никаких других средств связи. Например, NAT-PT может также иметь естественное IPv6-соединение и/или некоторый вид туннелированного IPv6-соединения. Эти виды соединений должны быть предпочтительными по сравнению с использованием механизма трансляции, если они возможны, поскольку NATPT представляет собой всего лишь инструмент, используемый для поддержки перехода к естественным взаимодействиям между IPv6 и IPv6. 185
Ограничения трансляции протоколов Некоторые поля IPv4 в IPv6 имеют измененный смысл, и трансляция оказывается не прямолинейной. Например, семантика и синтаксис заголовков расширения в IPv6 существенно изменились. Детали трансляции протоколов IPv4 в IPv6 даны в [4]. Эффект трансляции адресов Поскольку NAT-PT выполняет трансляцию адресов, приложения, которые передают IP-адрес на более высоких уровнях, не будут работать. В этом случае, чтобы обеспечить поддержку таким приложениям, необходимо встроить шлюз прикладного уровня (ALG). Это общая проблема NAT, и она полностью описана в [6]. Отсутствие сквозной защиты Одним из наиболее важных ограничений механизма NAT-PT является тот факт, что на сетевом уровне невозможно обеспечить сквозную защиту. Кроме того, может оказаться невозможной защита на транспортном и прикладном уровнях для тех приложений, которые передают IP-адреса на прикладном уровне. Это естественное ограничение функции трансляции сетевых адресов. Заметим, что независимо от механизмов адресной трансляции, организовать сквозную защиту IPSec через различные адресные области невозможно. Два оконечных узла, которым необходима защита на сетевом уровне IPSec, должны поддерживать либо IPv4, либо IPv6. Трансляция DNS и DNSSEC Описанная выше схема NAT-PT включает в себя трансляцию сообщений DNS. Ясно, что эта схема не может разворачиваться в комбинации с защищенным DNS. Т.е. полномочный сервер имен DNS в домене V6 не может подписывать ответы на запросы, которые порождаются из сети V4. В результате оконечный узел V4, который требует, чтобы DNS-ответы были подписаны, будет забраковывать ответы, которые были изменены NAT-PT. Однако за это ограничение вынуждены расплачиваться только серверы в домене V6, которые должны быть доступны из сети V4, поскольку оконечные узлы V4 могут не обращаться к V6-серверам из-за того, что DNS запросы не подписываются. Заметим, правда, что можно организовать передачи зон между серверами DNSSEC внутри одной и той же сети V6.
6. Область применения механизма NAT-PT NAT-PT может быть полезным инструментом обеспечения совместимости на границе оконечной сети, которая развернута и базируется только на протоколе IPv6, когда она подсоединяется к сети Internet, которая, в свою очередь, либо полностью базируется только на протоколе IPv4, либо представляет собой комбинацию сетей IPv4 и IPv6. 186
NAT-PT в своем простейшем виде, без поддержки шлюза DNS-ALG, обеспечивает только одностороннюю связь между оконечным доменом IPv6 и сетью IPv4, означающую, что только сеансы связи, инициализированные узлами IPv6, внутренними по отношению к этому домену IPv6, могут транслироваться, в то время как сеансы связи, инициированные узлами IPv4, просто прерываются. Это делает NAT-PT полезным инструментом для оконечных сетей, базирующихся только на IPv6, которым требуется поддержка коннективности с миром IPv4 без необходимости развертывания серверов, доступных из мира IPv4. NAT-PT, объединенный со шлюзом DNS-ALG, обеспечивает двунаправленную коннективность между оконечным доменом IPv6 и миром IPv4, и позволяет инициировать сеансы связи IPv4 узлам, находящимся вне этого домена IPv6. Это делает NAT-PT полезным для оконечных сетей, базирующихся только на IPv6, которым требуется развертывание серверов, доступных из мира IPv4. Некоторые приложения для своей работы рассчитывают на определенную степень стабильности адресов. Для таких приложений динамическое повторное использование адресов в NAT-PT может оказаться неприятным. Для хостов, выполняющих эти критические к адресам приложения, NAT-PT может быть сконфигурирован так, чтобы обеспечить статическое отображение адресов между V6-адресом хоста и конкретным V4-адресом. Это гарантирует, что выполняемые NAT-PT связанные с адресами изменения не станут существенным источником ошибок в работе.
7. Реализация механизмов трансляции протоколов и адресов В рамках проектов, поддержанных грантами РФФИ, нами были реализованы бесконтекстный протокольный транслятор SIIT (Stateless IP/ICMP Translation Algorithm [4]) и адресно-протокольный транслятор с сохранением состояния соединений NAT-PT (Network Address Translation Protocol Translation [5]) для операционных систем Linux и FreeBSD [15, 16]. Ниже описаны некоторые особенности нашей реализации [18].
7.1. Место транслятора в архитектуре ОС Транслятор выполнен в виде сетевого модуля ядра. Это позволяет избежать выполнения различных действий над приходящими пакетами непосредственно в трансляторе (например, проверка контрольных сумм), так как такую работу будет выполнять стек IP операционной системы. После загрузки модуля в ядро транслятор выглядит как обычный Ethernet-интерфейс с именем ntpt0. И над ним можно выполнять все операции, доступные для обычных сетевых интерфейсов. Узел, на котором выполняется транслятор, должен быть пограничным маршрутизатором между IPv6-only сетью и IPv4 внешним миром. Т.е. он должен иметь как минимум два реальных сетевых интерфейса, к одному 187
из которых подключена IPv6-only сеть, а к другому IPv4 сеть. Следующий рисунок поясняет местоположение транслятора в ядре операционной системы. Network layer IP Stack IPv4
IPv6
Datalink layer Network Interface (1) [IPv4]
IPv4 Network
PseudoInterface NAT-PT
Network Interface (2) [IPv6]
IPv6 Network
Рис. 5. Место транслятора в сетевой архитектуре ядра. В таком варианте реализации код транслятора будет выполнять только операции, связанные непосредственно с трансляцией, полагаясь в остальном на IP-стек ядра. Чтобы заставить транслятор работать по схеме, представленной на рисунке, необходимо, используя стандартные средства механизма маршрутизации, направлять на интерфейс транслятора: IPv6-пакеты с префиксом адреса назначения, равным PREFIX::/96, и пришедшие на интерфейс IPv6-only сети; IPv4-пакеты с адресами назначения из пула адресов, предназначенных для динамического выделения IPv6-only узлам, и пришедшие на интерфейс IPv4 сети. Для этого необходимо добавить соответствующие записи в таблицу маршрутизации и присвоить нужные адреса интерфейсу транслятора. Таким образом, пакет, пришедший из IPv4-сети, будет попадать в IP-стек (IPv4) и стандартно на нем обрабатываться. Если этот пакет предназначен для адреса из пула динамических IPv4-адресов IPv6-only узлов, то такой пакет будет направляться в интерфейс транслятора и, если существует соответствие адресов IPv4/IPv6 в таблице транслятора, преобразовываться на нем в IPv6пакет. Этот IPv6-пакет затем будет передан транслятором обратно в IP-стек (но уже IPv6), который направит его в соответствующий сетевой интерфейс, базируясь на адресе места назначения. 188
Пришедший из IPv6-сети пакет также попадет в IP-стек (IPv6), и если адрес назначения имеет префикс PREFIX::/96, пакет будет отправлен на транслятор и в случае соответствия адресов в таблице транслятора будет преобразован в IPv4-пакет. И этот пакет снова вернется в IP стек (IPv4) и далее будет направлен в нужный интерфейс для отправки. Теперь попытаемся пояснить все это на реальном примере нашего рабочего лабораторного стенда, схема которого представлена ниже на рис. 6. IPv4 Network
IPv4 Node
Сетевой интерфейс eth0: inet 194.67.37.213/24 Интерфейс транслятора ntpt0: inet 194.186.94.9/29 inet6 2222:3333::1/96
Сетевой интерфейс: inet 194.67.37.215/24 Пул глобальных IPv4-адресов для динамического присваивания IPv6-узлам равен 194.186.94.8/29.
NAT-PT
Префикс для IPv4-пакетов PREFIX::/96 равен 2222:3333::/96. Сетевой интерфейс eth1: inet6 FEDC:BA98::A985:3210
7.2. Описание реализации транслятора NAT-PT реализован на основе готовых модулей трансляции протоколов из реализованного нами алгоритма SIIT с добавлением соответствующей этому механизму адресной и портовой трансляции и шлюзов DNS-ALG и FTP-ALG (рис. 7).
Таблица маршрутизации: Destination 194.186.94.8/29 2222:3333::/96
адрес с префиксом 2222:3333::/96. Кроме того, добавляются необходимые записи в таблицу маршрутизации, как показано на рисунке. На IPv6-only узлах необходима запись в таблице маршрутизации для направления на узел с транслятором пакетов с адресами назначения, имеющими префикс 2222:3333::/96, а на IPv4-узлах необходима запись для направления на узел, где выполняется транслятор, пакетов c адресами назначения из диапазона 194.186.94.8/29. Таким образом, IPv4-пакеты, приходящие на интерфейс eth0 маршрутизатора NAT-PT, с адресом назначения из диапазона 194.186.94.8/29 будут направляться на модуль транслятора ntpt0, на нем транслироваться, и далее направляться на интерфейс eth1 и отправляться в IPv6-сеть. Приходящие на eth1 IPv6-пакеты, у которых адрес назначения имеет префикс 2222:3333::/96, будут направляться на интерфейс ntpt0, преобразовываться на нем в IPv4пакеты и переправляться на eth0 для отправки в IPv4-сеть.
Interface ntpt0 ntpt0
Распределительный модуль
Сетевой интерфейс: inet6 FEDC:BA98::7654:3211
IPv6 Network
Модуль трансляции IPv4 в IPv6
IPv6-only Node
Рис. 6. Схема лабораторного стенда. Узел, на котором выполняется транслятор, является маршрутизатором и имеет три сетевых интерфейса (помимо интерфейса loopback):
Шлюзы прикладного уровня DNS/FTP
Информация о текущих соединениях
Модуль трансляции ICMPv4 в ICMPv6
eth0 – для подключения IPv4 сети, eth1 – для подключения IPv6 сети, ntpt0 – псевдоинтерфейс NAT-PT транслятора.
Модуль трансляции адресов и портов IPv4 в IPv6
Модуль контроля таймаутов
Для динамического выделения IPv4-адресов в NAT-PT используются адреса 194.186.94.8/29 (маска 255.255.255.248). Как можно увидеть на рисунке, IPv6only узел и IPv6-интерфейс eth1 на NAT-PT имеют реальные unicast IPv6адреса с префиксом FEDC:BA98::/64. Интерфейсу транслятора ntpt0 назначается реальный IPv4-адрес 194.186.94.9/29 из пула динамических адресов и IPv6189
Прямой вызов Возврат
Рекурсивный вызов Возврат из рекурсии
Временные соответствия, выделяемые шлюзом DNS Модуль трансляции адресов и портов IPv6 в IPv4
Модуль трансляции IPv6 в IPv4
Модуль трансляции ICMPv6 в ICMPv4
Таблица соответствий, задаваемая администратором Обновления базы данных Информация из базы данных
Рис. 7. Схема отвечающей за трансляцию части модуля NAT-PT. Входной точкой и распределительным модулем NAT-PT является функция if_output псевдоинтерфейса. Она вызывается системой всякий раз, когда 190
интерфейс свободен и у него в очереди находится сетевой пакет. Далее определяется протокол и вызывается соответствующий обработчик. В случае сообщений об ошибке ICMP [13] или ICMPv6 [14] требуется трансляция вложенных IP-заголовков. Это реализовано как рекурсивный вызов обработчика трансляции соответствующего IP. Однако следует отметить тонкий момент. Дело в том, что в этом вложенном пакете отправитель и получатель поменялись местами по сравнению с обычным прохождением пакета через модуль транслятора NAT-PT. Для корректной обработки таких пакетов используется глобальный флаг, определяющий, рекурсивный это вызов или нет. Переведённый пакет возвращается по цепочке обратно распределительному модулю и помещается во входную очередь соответствующего протокола. Шлюзы прикладного уровня подключены к двум блокам трансляции, а не к распределительному модулю, потому что им также необходимо знать, в каком направлении через NAT-PT следует данный пакет. В принципе, они тоже могут быть разделены на две части, но в нашей реализации мы решили этого не делать, так как некоторая часть трансляции одинакова для обоих направлений следования пакетов. Кроме того, чтобы определить, что этот пакет является DNS/FTP-сообщением, нам необходимо узнать, является ли этот пакет TCP/UDP-сообщением, и если является, узнать его порты. А это невозможно без знания протокола сетевого уровня и поля, хранящего идентификатор протокола транспортного уровня. Поэтому самое логичное, на наш взгляд, место вызова этих модулей – после разбора пришедшего пакета и копирования его данных во временные переменные. Затем, если нужно, вызываются блоки DNS/FTP-трансляции, потом мы транслируем адреса и порты и собираем выходящий пакет. Если где-то на этом пути возникла ошибка, то пакет отбрасывается.
7.3. Особенности реализации шлюза DNS-ALG Заметим, что предложенный в RFC2766 [5] и реализованный на первом этапе проекта алгоритм работы DNS-ALG описан недостаточно детально и приводит к появлению целого ряда проблем. Спецификация шлюза вызывает оживленную дискуссию в сообществе Internet уже не первый год. В дополнение к определению DNS-шлюза в RFC 2663 был издан документ RFC 2694 “DNS extensions to Network Address Translators (DNS_ALG)” [7], рассматривающий варианты применения NAT с этим шлюзом. Кроме того, существует несколько проектов документов (IETF drafts), описывающих проблемы совместного использования NAT-PT и DNS-ALG и возможные варианты их решения, а именно: http://www.ietf.org/internet-drafts/draft-durandv6ops-natpt-dns-alg-issues-01.txt, http://www.ietf.org/internet-drafts/draft-hallinnatpt-dns-alg-solutions-02.txt, http://www.ietf.org/internet-drafts/draft-ietf-dnsopipv6-dns-issues-03.txt. Поэтому нам пришлось на основании этих документов и спецификации протокола DNS разработать описанную выше детальную спецификацию DNSALG, которую мы использовали в своей реализации. 191
Кроме того, при разработке и последующем тестировании модуля DNS-ALG мы столкнулись с рядом проблем, не оговоренных ни в одном из вышеупомянутых документов. Во-первых, применяется метод сжатия сообщений DNS, основанный на использовании ссылок на уже описанное доменное имя вместо повторного его описания. При коррекции сообщения DNS с изменением его размера, мы должны соответствующим образом скорректировать и данные ссылки. Для этой цели мы используем журнал изменений. При последовательном анализе сообщения все ссылки на доменные имена в этом журнале проверяются и, если необходимо, корректируются. Во-вторых, если в качестве транспортного протокола DNS используется ТСP, то требуется коррекция заголовка TCP в связи с изменением размера сообщения. Имеются в виду порядковые номера ack_num (AN) и syn_num (SN). Однако, несмотря на то определение, что «пакеты, пришедшие на 53-й порт протоколов TCP/UDP, должны передаваться в модуль трансляции сообщений DNS», эта особенность протокола TCP в документах IETF полностью проигнорирована. Ввиду данной особенности и редкости использования протокола TCP для разрешения имен, поддержка TCP в DNS-ALG нами не реализована. В-третьих, рассматриваемое в документах IETF множество вариантов использования NAT со шлюзом DNS-ALG не обладает полнотой. Например, в процессе тестирования нами обнаружен следующий вариант: Внешний IPv4-хост посылает запрос типа "А" на внутренний DNSv6-сервер, причём запрашивается доменное имя такого же IPv4-only хоста. В этом случае DNSv6-сервер, получив запрос «АААА» (он был транслирован при пересечении границы IPv4-IPv6) и не найдя у себя соответствующей записи, должен обратиться с этим запросом к внешним DNSv4-серверам, при условии, что он выполняет рекурсивные запросы. Это следует из необходимости обеспечивать работу DNS-сервиса внутренних IPv6-хостов и прямо описано в RFC. Этот запрос, транслируясь в «А», доходит до внешних DNS-служб и получает «А»-ответ, который транслируется в «АААА» и доставляется на DNSv6-сервер. Последний, не кэшируя ответ, пересылает его наружу, и возникает достаточно интересная ситуация. Шлюз, встретив в ответе незнакомый IPv6-адрес вида PREFIX:X.X.X.X, в который был транслирован IPv4-адрес, полученный от внешних серверов, должен выделить ему временный IPv4-адрес из своего пула. Если такое выделение произойдет и получивший наконец ответ IPv4-хост будет пытаться открыть соединение с запрошенным хостом по полученному им адресу, возникнет ситуация, при которой два IPv4-хоста при наличии между ними прямого IPv4-пути будут взаимодействовать через NAT. Кроме накладных расходов, подобное соединение может быть чревато злонамеренной и успешной DoS-атакой на NAT, так как у установленных соединений тайм-аут обрыва связи и удаления временного соответствия адресов в несколько раз больше соответствующего тайм-аута временных записей. Метод решения этой проблемы прост – либо 192
выделять IPv4-адреса только для IPv6-адресов, не подпадающих под маску PREFIX:0.0.0.0 (административный вариант), либо, как это сделано у нас, при выделении временных записей проверять IPv6-адрес на соответствие этой маске. Если ответ положительный, то новую запись не выделять, а в качестве IPv4-адреса использовать последние 32 бита. Придумать такие методы нетрудно, но есть вероятность, что недостаточно хорошо протестированные реализации столкнутся с этой проблемой только на практике.
7.4. Особенности реализации шлюза FTP Как уже было отмечено, все команды FTP передаются по управляющему соединению. Так как для корректной работы протокола FTP необходимо транслировать только управляющие команды PORT, PASV, EPRT, EPSV, FTPALG должен обрабатывать только управляющее соединение FTP. Соединения данных остаются без изменения (т.е. FTP-ALG их не затрагивает). Управляющее соединение инициируется клиентом на TCP-порт 21 сервера. Поэтому все приходящие TCP-пакеты, у которых порт отправителя или получателя равен 21, направляются в модуль FTP-ALG. Модуль FTP-ALG работает с собственным списком записей о соединениях FTP. Следует отметить, что алгоритм работы FTP-ALG, предложенный в RFC2766 [5], также описан недостаточно детально. Потому мы доработали этот алгоритм, добавив необходимую функциональность. В нашей реализации для обработки FTPсоединений используется специальная таблица, записи которой хранят информацию об управляющем соединении FTP. Все приходящие TCP-пакеты предварительно обрабатываются модулем обработки TCP-соединений, и пакеты, относящиеся к управляющим соединениям FTP (эти пакеты имеют порт источника или порт назначения 21), направляются в модуль FTP-ALG. Если соединение новое, в таблицу заносится новая запись, если нет, производится необходимая корректировка существующей записи и трансляция команды FTP, если необходимо. Затем оттранслированные данные возвращаются в модуль обработки TCP, который выполняет окончательное формирование выходного пакета TCP. Особенностью данного шлюза является реализация алгоритма коррекции порядковых номеров ACK и SYN заголовка TCP (этот алгоритм в RFC-спецификации не описан). Отметим ту его особенность, что он корректно работает и в том случае, когда одновременно приходят несколько пакетов с одной из сторон, так как он ведет всю историю дельт номеров SN и AN пакетов FTP-соединения.
7.5. Управление модулем NAT-PT Для управления модулем NAT-PT и его конфигурирования была реализована специальная утилита. Для получения и передачи управляющей информации в модуль NAT-PT она использует системную функцию ioctl(), подобную хорошо знакомой каждому системному администратору UNIX утилите для настройки сетевых интерфейсов ifconfig. Однако, в отличие от последней, она почти не работает со стандартными системными флагами, предназначенными для этой 193
цели. Дело в том, что используемой в таких случаях структуры ifreq зачастую бывает недостаточно, и нам нужны другие, большие структуры. Поэтому, поскольку сетевые подсистемы используемых нами операционных систем позволяют разработчикам драйверов сетевых устройств вводить специфические для своего устройства флаги, мы этой возможностью и воспользовались. Реализация сетевого стека передаёт их без изменений функции интерфейса, специфицированной в описывающей его структуре как if_ioctl. На данный момент наша утилита позволяет задавать отображения IPv4-адресов (и комбинаций IPv4-адрес + список портов) на IPv6-адреса и регулировать тайм-ауты.
8. Тестирование сетевых модулей Одним из важнейших этапов разработки сетевого программного обеспечения является тестирование [19]. Для тестирования нашей реализации транслятора NAT-PT помимо обычных методов проверки работоспособности программ мы использовали методологию UniTesK [17], а именно ее реализацию для языка программирования Си, которая получила название CTesK. Методология UniTesK и продукт CTesK разработаны одной из исследовательских групп нашего института. В технологии UniTesK используются два подхода к тестированию: "черный ящик", когда поведение тестируемой системы наблюдается извне только на основании выданных стимулов и полученных реакций, и "белый ящик", когда поведение тестируемой системы наблюдается посредством доступа к внутренним данным тестируемой системы. В своих тестах мы использовали подход "черный ящик". Основной целью тестирования была проверка работоспособности модуля NAT-PT в соответствии со спецификацией RFC 2766. Процесс тестирования в UniTesK, как правило, состоит из следующих фаз: определение интерфейса, разработка спецификаций, разработка тестов, прогон тестов, анализ результатов.
8.1. Определение интерфейса На этом этапе выделяется множество воздействий на тестируемую систему (стимулов) и реакций в ответ на эти воздействия. Множество стимулов и реакций определяют набор требований, которые должны быть описаны в формальных спецификациях. В случае NAT-PT стимулами выступают входящие пакеты со стороны IPv4 или IPv6, а реакциями – выходящие пакеты IPv4 или IPv6. Особенностью модуля транслятора NAT-PT является то, что он играет роль маршрутизатора, т.е. IPv4-пакет, полученный на IPv4-интерфейсе и предназначенный для узла IPv6сети, будет транслирован модулем NAT-PT в IPv6-пакет и передан для отправки на IPv6-интерфейс. Таким образом, если стимулом является IPv4пакет, то реакцией будет IPv6-пакет и наоборот. Реакции на стимул может не быть в случае, когда данный тип пакетов трансляции не подлежит, такие 194
пакеты просто отбрасываются транслятором с посылкой ICMP-сообщения отправителю или без посылки такого сообщения. Кроме того, при определенных стимулах реакцией может быть несколько пакетов. В наших тестах мы ограничились тестированием трансляции пакетов протоколов ICMP и UDP. Также мы исключили из рассмотрения реакции оконечных узлов на получение оттранслированных пакетов.
8.2. Разработка формальных спецификаций На этапе разработки спецификаций требования записываются в формальном виде. В технологии CTesK формальные спецификации выражаются средствами специального расширения языка Си, получившего название SEC (Specification Extention of the C language). Спецификация тестируемой системы состоит из следующих компонентов: абстрактного состояния, спецификации стимулов, спецификации реакций и вспомогательных процедур. Так как для тестирования мы используем модель "черного ящика", нам необходимо моделировать поведение тестируемого модуля NAT-PT. Абстрактное состояние – это набор данных, который моделирует внутренние данные тестируемой системы. Эти данные меняются всякий раз, когда целевая (тестируемая) система подвергается воздействию стимулов или выдает реакцию. В нашем случае в абстрактном состоянии мы сохраняем данные о входящих и исходящих пакетах, на основании чего формируется информация о состоянии соединений и выделении адресов. Стимулами выступают входящие пакеты ICMP и UDP протокола IPv4 для тестирования трансляции из IPv4 в IPv6 и входящие пакеты ICMP и UDP протокола IPv6 для тестирования трансляции из IPv6 в IPv4. Реакциями на данные стимулы являются исходящие пакеты ICMP и UDP протоколов IPv4 и IPv6 соответственно. Спецификации для стимулов (для входящих пакетов) разбирают входящий пакет и сохраняют данные в абстрактном состоянии. Спецификации реакций (для исходящих пакетов) на основании абстрактного состояния устанавливают, что данный пакет является допустимым, а также проверяют корректность трансляции.
8.3. Разработка тестов Связь между моделью и реализацией осуществляется посредством медиаторов. Такие медиаторы передают стимулы из тестовой системы в целевую систему, регистрируют реакции целевой системы и передают их в тестовую систему, а также отображают изменения состояния целевой системы в абстрактное состояние. Для медиаторов, реализующих посылку пакетов в тестируемую систему, используются дополнительные узлы в обоих сегментах сети IPv4 и IPv6, на которых установлен генератор сетевых пакетов, позволяющий выдавать пакеты в сеть по заданным параметрам. Специальные медиаторы стимулов и реакций осуществляют сбор входящих и исходящих пакетов на обоих сетевых 195
интерфейсах. Для этого устанавливаются специальные программы прослушивания сети. Все полученные пакеты собираются в специальный буфер и передаются в тестовую систему для обработки. Набор и последовательность стимулов определяется тестовым сценарием. Тестовый сценарий определяет стимул для воздействия на тестовую систему. Медиатор выдачи стимула передает стимул в тестовую систему. После этого специальные медиаторы в течение определенного интервала времени собирают реакции, которые происходят после выдачи стимула, и производят отображение состояния системы в абстрактном состоянии. Затем производится проверка на допустимость набора реакций целевой системы на данный стимул. При допустимости реакций для каждой из них вызываются оракулы стимулов и реакций. Если все оракулы возвращают положительный вердикт, то тестовый сценарий переходит к следующему стимулу, иначе тестовый сценарий сообщает об ошибке и завершает работу. Тестовый набор состоит из нескольких тестовых сценариев. Тестовые сценарии проверяют трансляцию заголовков IPv4 в IPv6 и обратно, ICMPv4 в ICMPv6 всех типов и обратно, UDPv4 в UDPv6 и обратно.
8.4. Результаты тестирования Прогон тестов выявил ряд ошибок в реализации NAT-PT. Это позволило локализовать местонахождение ошибок в коде реализации. Выявленные ошибки были связаны как с несоответствием спецификации, так и с программными ошибками в реализации.
8.5. Генератор сетевых пакетов Для тестирования разработанного нами транслятора NAT-PT потребовались средства генерации и отправки различных тестовых последовательностей пакетов с заданными параметрами. Существующие генераторы по различным причинам нам не подходили, поэтому пришлось создать свой генератор сетевых пакетов, отвечающий нашим требованиям. Разработанный нами генератор сетевых пакетов позволяет создавать основные типы пакетов TCP, UDP, ICMP протоколов IPv4 и IPv6 и отправлять их в сеть. Генератор позволяет посылать как одиночные пакеты заданного типа, так и серии пакетов. Акцент был сделан на его универсальности: возможности создавать все необходимые типы пакетов, простоте управления параметрами и добавления новых типов пакетов. Генератор состоит из двух основных модулей. Первый создает пакет, второй отправляет его в сеть. Генерация пакетов происходит путем последовательного формирования его заголовков. Главной особенностью генератора является наличие специального внешнего файла, в котором описаны структуры заголовков. В качестве входных параметров генератор получает файл с описанием заголовков и пользовательские данные.
196
Файл описания заголовков представляет собой текстовый файл в формате языка XML. Этот язык позволяет представить заголовки пакета и их поля в виде дерева и значительно упрощает поиск нужных элементов. В файле описаны все поля известных заголовков с указанием их длины, обязательности использования и значения по умолчанию. Это дает пользователю возможность передавать только часть полей заголовка. Структура, формируемая самим пользователем, – это список заголовков пакета, включающий только те параметры, которые необходимо изменить. Генератор последовательно просматривает эту структуру и находит соответствующий заголовок в файле описания заголовков. Значения полей формируются из входной структуры, или подставляются значения по умолчанию, или значения вычисляются с помощью специальных функций. Для создания каждого заголовка используется отдельная функция, однако общие принципы формирования заголовков и введение специальных типов данных позволяют для некоторых заголовков использовать одинаковые функции, а значит, для добавления нового заголовка иногда достаточно добавить его описание в файл заголовков. Эта часть генератора является платформеннонезависимой. Вторая часть генератора отправляет сформированный пакет непосредственно в сеть и позволяет посылать как одиночные пакеты, так и серии пакетов. Эта часть генератора реализована для платформ FreeBSD и Linux. Таким образом, внешний файл описания заголовков позволяет легко изменять параметры пакетов, значительно сокращает количество данных, передаваемых пользователем, дает возможность добавлять новые типы заголовков, для чего иногда достаточно описать новый заголовок, без добавления соответствующей функции. Генератор является библиотекой, написанной на языке C, и может встраиваться в другие программные продукты. Генератор прошел тестирование на платформах FreeBSD и Linux и применялся при тестировании нашей системы NAT-PT.
9. Заключение В результате проведенной работы создан полнофункциональный межпротокольный шлюз NAT-PT, соответствующий проекту стандарта IETF RFC 2766 “Network Address Translation Protocol Translation” и оснащенный шлюзами прикладного уровня DNS-ALG и FTP-ALG. NAT-PT может быть полезным инструментом обеспечения совместимости на границе оконечной сети, которая развернута и базируется только на протоколе IPv6, когда она подсоединяется к сети Internet, которая, в свою очередь, либо полностью базируется только на протоколе IPv4, либо представляет собой комбинацию сетей IPv4 и IPv6. Реализованный механизм представляется одним из важных средств для обеспечения плавного и безболезненного перехода на протокол IPv6. На основе документов IETF были разработаны подробные спецификации NATPT. Работа по его реализации проведена на базе ОС Linux (с использованием реализации IPv6 проекта USAGI http://www.linux-ipv6.org) и FreeBSD (с 197
использованием реализации IPv6 проекта KAME http://www.kame.net). На отладочном стенде проведена отладка и тестирование разработанного программного обеспечения. К разработанным программным средствам обеспечен свободный доступ с Web-сайта института (http://ipv6.ispras.ru). С целью проверки работоспособности транслятора в «боевых» условиях большой сетевой нагрузки в ИСП РАН развернут специальный стенд. Для этого в экспериментальной сети за транслятором NAT-PT развёрнуты IPv6серверы HTTP и FTP с зеркалами популярных серверов, а также IPv6 DNSсервер. Серверы доступны извне по адресам http://www.ipv6-test.ispras.ru и ftp://ftp.ipv6-test.ispras.ru. Литература 1. RFC 2460 “Internet Protocol, Version 6 (IPv6) Specification”, http://www.ietf.org/rfc/rfc2460.txt. 2. RFC 2373 “IP Version 6 Addressing Architecture”, http://www.ietf.org/rfc/rfc2373.txt. 3. RFC 791 “Internet Protocol”, http://www.ietf.org/rfc/rfc791.txt. 4. RFC 2765 “Stateless IP/ICMP Translator (SIIT)”, http://www.ietf.org/rfc/rfc2765.txt. 5. RFC 2766 “Network Address Translation - Protocol Translation (NAT-PT)”, http://www.ietf.org/rfc/rfc2766.txt. 6. RFC 2663 “IP Network Address Translator (NAT) Terminology and Considerations”, http://www.ietf.org/rfc/rfc2663.txt. 7. RFC 2694 “DNS extensions to Network Address Translators (DNS_ALG)”, http://www.ietf.org/rfc/rfc2694.txt. 8. RFC 3596 “DNS Extensions to Support IP Version 6”, http://www.ietf.org/rfc/rfc3596.txt. 9. RFC 793 “Transmission Control Protocol”, http://www.ietf.org/rfc/rfc793.txt. 10. RFC 768 “User Datagram Protocol (UDP)”, http://www.ietf.org/rfc/rfc768.txt. 11. RFC 959 “File Transfer Protocol (FTP)”, http://www.ietf.org/rfc/rfc959.txt. 12. RFC 2428 “FTP Extensions for IPv6 and NATs”, http://www.ietf.org/rfc/rfc2428.txt. 13. RFC 792 “Internet Control Message Protocol Specification“, http://www.ietf.org/rfc/rfc792.txt. 14. RFC 2463 “Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification”, http://www.ietf.org/rfc/rfc2463.txt. 15. Linux Man Pages, http://www.tldp.org/docs.html#man. 16. FreeBSD Man Pages, http://www.freebsd.org/cgi/man.cgi. 17. И.Б.Бурдонов, А.С.Косачев, В.В.Кулямин, А.К.Петренко "Подход UniTesK к разработке тестов","Программирование", 2003, №6–стр 25-43. 18. Г.В. Ключников, Д.С. Мишин, Д.В. Москалев, В.З. Шнитман «Механизмы перехода с IPv4 на IPv6. Использование методов трансляции протоколов и адресов для обеспечения совместимости протоколов IPv4 и IPv6» в сб. Тезисов международной конференции «Интернет нового поколения – IPv6», г. Ярославль, 2002, стр. 23-28. 19. Г.В. Ключников, А.В. Никешин, Д.С. Мишин, Д.В. Москалев, В.З. Шнитман «Тестирование сетевых модулей и генератор сетевых пакетов» в сб. Тезисов международной конференции «Интернет нового поколения – IPv6», г. Ярославль, 2003, стр. 26-31.
198
Исполнение моделей при помощи виртуальной машины К.В. Буздин
1. Для чего нужны модели Прошло уже почти 20 лет со времени появления в 1986 году вызвавшей множество споров статьи Фредерика Брукса «Серебряной пули нет». В данной работе автор предрекал, что в течение ближайшего десятилетия с момента её выхода не возникнет методов, использование которых позволит на порядок величин повысить производительность разработки программного обеспечения. Эта статья вызвала множество споров и работ с опровержениями, и в 1995 году Ф. Брукс опубликовал ответы на некоторые из критических замечаний [1]. По признанию самого Ф. Брукса, наиболее тщательный анализ его статьи был предпринят Дэвидом Харелом в работе «Кусая серебряную пулю» [2]. Д. Харел рассматривал «Серебряной пули нет» как чрезмерно пессимистическую и намеревался высветить в своей работе более яркую сторону проблемы, предпосылая статье подзаголовок «К светлому будущему программных разработок». Им была предложена унифицированная система разработки программного обеспечения, главной целью которой было освободить программиста от необходимости рассуждать на чрезмерно глубоком уровне детализации, предоставить возможность размышлять над решением проблемы и представлять свои идеи при помощи соответствующей высокоуровневой нотации, а именно моделей. «Использование подходящего графического формализма может оказать впечатляющий эффект на инженеров и программистов» [2]. Кроме самих моделей для эффективной работы необходимы средства для их изучения и анализа, среди которых одной из наиболее полезных, по мнению Д. Харела, является возможность исполнения модели. В настоящее время всё чаще упоминается метод разработки на основе моделей, или Model Driven Development (MDD) [5], обещающий стать первым технологическим скачком со времён появления компиляторов, который сможет значительно ускорить процесс создания программного обеспечения и улучшить его качество. Лидирующие позиции в данной области призван занять стандарт Model Driven Architecture (MDA), разработкой которого занимается консорциум OMG. Разработка в рамках MDA предполагает, что сначала создаётся одна или несколько абстрактных моделей системы, которые далее проходят несколько стадий автоматизированной трансформации в более дета199
льные модели и, в конечном итоге, преобразуются в код на языке программирования. Как проще написать одну строку на Java, чем 10 строк на языке ассемблера, так же проще построить графическую модель на языке UML, чем писать на Java, считают сторонники подхода MDA. Но для успешного применения моделей необходима не только удобная нотация для их записи, которую предоставляет язык UML, но и соответствующий инструментарий для их изучения. Автоматизация разработки является тем технологическим средством, которое должно привести к ускорению процесса создания программного обеспечения и повышению его качества. Тем не менее, ранние попытки применения автоматизации в сфере моделирования ограничивались лишь поддержкой в создании диаграмм и генерацией скелетного кода, что было недостаточным для того, чтобы существенно увеличить производительность разработки. Наибольшей же отдачи от MDD можно добиться лишь после полной реализации его потенциальных возможностей для автоматизации, которые включают: автоматическую генерацию полного программного кода по моделям (а не скелетного кода или отдельных фрагментов), автоматическую верификацию моделей (например, при помощи их исполнения). Данная статья посвящена теме исполнения моделей. Автоматическая верификация подразумевает возможность при помощи компьютера определить, удовлетворяет ли модель требованиям, предъявляемым к системе. Такая проверка может принимать различные формы, включая формальный математический вывод, но наиболее часто под этим подразумевается тестирование и отладка моделей путём их исполнения. В любом случае важно иметь такую возможность для моделей, находящихся на высоком уровне абстракции и даже неполных, которые появляются на ранних стадиях процесса разработки, поскольку именно тогда и принимается большинство фундаментальных решений.
2. Почему важно, чтобы модели были исполняемыми Одним из наиболее важных способов нашего познания является обучение посредством эксперимента. В контексте данной статьи это означает исполнение моделей. Дэвид Харел сравнивает модели, которые не могут быть исполнены, с машинами, у которых нет двигателя. Важным преимуществом исполняемых моделей является то, что они на ранних стадиях обеспечивают возможность получения опыта работы с создаваемой системой. В данном случае уместна аналогия с языками программирования. Когда мы изучаем новый язык, мы всегда бываем очень воодушевлены успешным выполнением первой тривиальной программы “Hello, world!”. Этот простой опыт укрепляет нашу уверенность в правильности действий и служит отправной точкой в дальнейших исследованиях. Информация, полученная в результате экспериментов, помогает перейти от формального знания к пониманию. Путём исполнения модели, отражающей наши ожидания относительно поведения системы, 200
задолго до собственно реализации самой системы мы можем убедиться, что она действительно будет работать правильно. В случае, если при этом будут обнаружены отклонения от ожидаемого поведения, мы можем вернуться к модели, изменить её и запустить тот же сценарий, на котором были выявлены недостатки. Таким образом, исполнение модели позволяет найти ошибки, которые иначе остались бы незамеченными до тех пор, пока большая часть системы уже не была бы реализована, и было бы слишком поздно. И это начинает проявляться сразу же после того, как модель впервые будет запущена на исполнение. Кроме того, на ранних стадиях создания системы к работе могут привлекаться представители заказчика. В этом случае исполняемая модель будет играть роль прототипа при уточнении требований.
3. Результат исполнения модели Результаты, получаемые при исполнении модели, могут быть подразделены на два вида: получаемые непосредственно в процессе исполнения; получаемые по завершении исполнения. В первом случае исполнение модели предоставляет возможность получить опыт от общения с реальной системой. При этом допустимо пошаговое исполнение с целью более детальной отладки. При интерактивном исполнении пользователь играет роль окружения системы, он может порождать события, наблюдать и изменять значения переменных, выбирать путь дальнейшего продолжения исполнения, если имеется несколько возможностей. В ответ инструмент поддержки исполнения должен перевести систему в новое состояние. Кроме того, поскольку большинство моделей имеют графическое представление, то изменение состояния должно отражаться на самой модели, например, путём изменения цвета некоторых её элементов. Для получения эффекта непрерывного исполнения интересующую последовательность событий из окружения системы можно подготовить заранее. В таком случае, при наличии соответствующей графической поддержки, можно наблюдать анимацию модели. Аналогичного эффекта можно добиться, если инструмент сам будет генерировать события случайным образом. Также полезна возможность использования точек останова для изучения конкретных состояний моделируемой системы. Особый интерес представляет полное исполнение модели, когда инструмент генерирует все возможные сочетания событий из окружения с целью проверки всех возможных вариантов поведения моделируемой системы. При этом необходима возможность формального описания интересующих разработчика ситуаций и автоматического их распознавания, поскольку результат полного исполнения для реальных систем будет слишком объёмен для исследования его человеком. В процессе исполнения модели инструмент может собирать различные данные, например, подсчитывать некоторые метрики или составлять трассы, которые представляют собой последовательность событий, возникающих в системе. 201
Подобные данные могут оказаться полезными в дальнейшей работе над системой, например для создания тестов.
4. Способы исполнения моделей Рассмотрим следующие способы исполнения моделей: непосредственная интерпретация, генерация автоматной модели и её исполнение, использование виртуальной машины.
4.1. Интерпретация В данном случае для исполнения модели создаётся специальная программа, называемая интерпретатором. На вход интерпретатору подаётся сама модель в том виде, в каком она была создана пользователем, без каких либо промежуточных преобразований. Основным недостатком интерпретации является её низкая скорость. модель
интерпретатор
пошаговое исполнение анимация модели генерация трасс
…
df;sdf;sdf;s d df;fdg;dfgf d xcv;xzcv;z xvc ds;xcv;zxc v; xcvxz;zxcv ;sdf;sdfgfd;
Рис. 1. Интерпретация На рисунке 1 представлена схема исполнения моделей с использованием непосредственной интерпретации.
4.2. Генерация автоматной модели Многие современные реализации исполняемых моделей базируются на синтезе автоматов. Далее конечные автоматы можно интерпретировать непосредственно или использовать для последующей генерации кода. Применение автоматов в данном случае обусловлено следующими их преимуществами: они представляются в хорошо формализованном виде, имеется развитый математический аппарат для работы с конечными автоматами, который позволяет проводить над ними формальные исследования и преобразования, и, наконец, автоматные модели довольно хорошо отображаются в код на целевых языках. 202
Но важно подчеркнуть, что хотя автоматные модели являются хорошо формализованным и мощным средством спецификации, они могут оказаться сложнее в работе и менее наглядными для неподготовленных пользователей, чем другие модели. Кроме того, при рассмотрении программной системы возможен разрыв, связанный с различиями в степени абстракции, используемой в автоматных и других спецификациях. набор интерпретатор модель автоматов генератор автоматов
текст на языке программирования
df;sdf;sdf;sdf df;fdg;dfgfd; xcv;xzcv;zxv cds;xcv;zxcv; xcvxz;zxcv;s df;sdfgfd;dfg fd;awf;dfgd;f df;uyk;dfg;fr h;gyju;fgy;yu iyu;fhy;yuij;
4.3. Виртуальная машина В качестве промежуточного представления для исполняемых моделей могут использоваться не только конечные автоматы. Дело в том, что они представляют собой слишком общий и низкоуровневый инструмент. Конечные автоматы подходят для решения очень широкого круга задач, поэтому в них может не оказаться инструментов, специфичных для выбранных для исполнения моделей. Кроме того, сложность конечных автоматов может резко возрастать при увеличении сложности самой описываемой ими модели [4]. Альтернативной основой для реализации исполняемых моделей может послужить виртуальная машина. Виртуальная машина — это абстрактная машина, для которой существует интерпретатор. В свою очередь, абстрактная машина — это модель процессора, которая не предназначена для реализации «в железе». Для неё определены набор команд и модель памяти. В памяти хранятся данные, с которыми может работать машина. В сравнении с автоматами при создании виртуальной машины имеется большая свобода выбора модели данных.
кодогенератор
последовательность команд
модель
транслятор
интерпретатор
df;sdf;sdf;sdf df;fdg;dfgfd;x cv;xzcv;zxvc ds;xcv;zxcv;x cvxz;zxcv;sdf ;sdfgfd;dfgfd; awf;dfgd;fdf; uyk;dfg;frh;g yju;fgy;yuiyu ;fhy;yuij;
пошаговое исполнение анимация модели df;sdf;sdf;sd df;fdg;dfgfd xcv;xzcv;zxv c ds;xcv;zxcv; xcvxz;zxcv ;sdf;sdfgfd;df gf d;awf;dfgd; fdf;uyk;dfg;
генерация трасс … пошаговое исполнение
Рис. 2. Использование автоматов
анимация модели
Следует отметить, что при исполнении моделей при помощи автоматов появляются дополнительные трудности при реализации визуализации исполнения. Дело в том, что исполнению подвергается уже не сама модель, а её представление в виде автоматов или даже код на языке программирования, а отображать изменения необходимо в терминах самой модели. Таким образом, по сравнению с непосредственной интерпретацией моделей, в данном случае необходимо прилагать дополнительные усилия для определения соответствия конструкций, употребляемых в моделях, и их образов в автоматах и коде. 203
df;sdf;sdf;sd df;fdg;dfgfd xcv;xzcv;zxv c ds;xcv;zxcv; xcvxz;zxcv ;sdf;sdfgfd;df gf d;awf;dfgd; fdf;uyk;dfg;
генерация трасс …
Рис. 3. Использование виртуальной машины. 204
На Рис. 3 схематически представлен процесс исполнения модели при помощи виртуальной машины. Модель подаётся на вход транслятору, который переводит её в набор команд виртуальной машины. Далее интерпретатор осуществляет исполнение команд в заданном пользователем режиме. Кроме того, в процессе исполнения может происходить запись отладочной информации. При визуализации исполнения здесь, как и в случае использования автоматов, необходимо определять соответствие между конструкциями, употребляемыми в моделях, и их образами в последовательности команд виртуальной машины. Исследуя рисунки 1, 2 и 3, можно заметить, что и непосредственная интерпретация, и использование автоматов являются частными случаями метода исполнения моделей, основанного на виртуальной машине. Действительно, в первом случае этап трансляции является вырожденным, и внешнее представление модели одновременно образует последовательность команд для интерпретационной компоненты виртуальной машины. Не представляет большой сложности определить набор команд для представления автоматов, в таком случае этап трансляции модели в такую последовательность команд представляет собой ни что иное, как генерацию автоматов по модели. Таким образом, последний из представленных способов исполнения моделей является, очевидно, наиболее гибким.
5. Создание виртуальной машины При создании виртуальной машины для некоторого класса задач необходимо выполнить следующие этапы: 1. Выявить весь объём данных, которые в процессе выполнения задачи могут подвергаться преобразованию. Эти данные образуют поле зрения виртуальной машины. 2. Определить набор команд, который, подобно командам универсальной ЭВМ, будет служить для записи решения конкретной задачи. Этот набор является специфичным для данного класса задач. Последовательность команд из этого набора, определяющая решение конкретной задачи, записывается в программное поле виртуальной машины и не меняется в процессе её функционирования. 3. Реализовать интерпретационный компонент виртуальной машины, представляющий собой совокупность механизмов. Посредством этих механизмов виртуальная машина осуществляет интерпретацию команд, находящихся в программном поле. 4. Реализовать транслятор из внешнего представления модели в последовательность команд виртуальной машины. Для виртуальной машины должен быть определён способ её использования, то есть: способ передачи входной информации и формирования начального поля зрения; способ активизации (запуска) системы; способ останова системы; 205
способ извлечения переработанной информации. Тот факт, что при создании виртуальной машины заданию подлежит набор команд, определяет большее удобство её применения. Для использования автоматов должны быть разработаны методы сведения моделей к автоматам, а решение данной задачи может представлять значительную сложность. В случае же виртуальной машины мы сами имеем возможность взять такой набор команд, который наилучшим образом будет отражать специфику выбранных для исполнения моделей. При этом сам процесс перевода модели в последовательность подобранных таким образом команд не потребует серьёзных усилий. Конечно же, процесс подбора команд должен представлять собой компромисс между простотой перевода моделей в команды и легкостью собственно интерпретации составленных из таких команд последовательностей.
5.1. Комплексирование работы нескольких виртуальных машин 5.1.1. Виртуальная машина как строительный компонент Взглянем ещё раз на то, что представляет собой виртуальная машина. Она состоит из некоторого интерпретатора, исполняющего последовательность известных ему команд и имеющего доступ к области памяти для хранения данных. Рассмотрим, что получится, если допустить возможность существования в пределах одного сеанса исполнения нескольких таких интерпретаторов. Поскольку программное поле виртуальной машины остаётся неизменным в течение всего процесса её функционирования, то несколько интерпретаторов могут работать с одной и той же последовательностью команд или каждый иметь свою собственную. Возможна также реализация, в которой различные интерпретаторы будут предназначены для работы с разными наборами команд. Таким образом, задача исполнения модели может быть разделена на подзадачи, для каждой из которых будет создан отдельный набор команд и его интерпретатор со своей моделью памяти. Кроме того, возможен также случай совместной работы нескольких интерпретаторов над общей областью памяти для хранения данных, но, поскольку данные в процессе исполнения модели могут изменяться, такой подход сопряжён с дополнительными трудностями и его использование не всегда оправдано. Таким образом, набор команд в совокупности с его интерпретатором и моделью памяти образуют своеобразный строительный компонент. Из таких компонентов при создании системы исполнения моделей можно составлять различные комбинации. Остаётся только вопрос о способах комплексирования работы таких компонентов, наибольший интерес из которых представляют два, описанные ниже.
5.1.2. Вызов виртуальной машины Пожалуй, одним из наиболее полезных способов комплексирования виртуальных машин является вызов одной виртуальной машины из другой. При его реализации вызываемые и вызывающие машины могут как совпадать, так и существенно различаться по своей структуре. В наборе команд той 206
виртуальной машины, из которой происходит вызов, выделяется команда или команды для порождения и вызова другой виртуальной машины. Порождённая машина получает ссылки на своё поле данных и последовательность команд, после чего запускается. Поведение вызывающей машины при этом может варьироваться: либо ожидание завершения вызванной машины, либо продолжение своего выполнения без ожидания завершения вызванной. Следует отметить, что вызовы могут быть многократно вложенными. Для иллюстрации вышесказанного рассмотрим две возможных реализации вызова виртуальных машин. 1. Виртуальные машины различны, имеют разные наборы команд и различную структуру области для хранения данных. Вызвавшая машина после осуществления вызова блокируется до окончания работы вызванной, после чего продолжает своё выполнение. Такой способ вызова полезен, например, при исполнении моделей на языке MSC 2000 [3], который имеет два уровня: HMSC (high-level MSC) и собственно MSC (message sequence charts — диаграммы последовательности сообщений). HMSC-диаграмма представляет собой граф потока управления, в узлах которого помещены ссылки на MSC-диаграммы. Таким образом, виртуальная HMSC-машина будет являться вызывающей, а MSC-машина — вызываемой. 2. Виртуальные машины имеют одинаковую структуру, работают с одним набором команд, но имеют отдельные экземпляры области данных. Вызвавшая машина не блокируется после вызова, а продолжает своё выполнение. Такая организация полезна для обработки альтернатив, которые часто встречаются во многих моделях и призваны отражать различные варианты поведения системы. Допустим, что в процессе своей работы виртуальная машина А1 дошла до такого места, где ей нужно обработать n альтернатив. В этом случае А1 создаёт n-1 своих копий А2, А3, … Аn. При этом копированию подвергается и поле зрения, поскольку предполагается, что до разветвления поведение всех машин было одинаковым. Таким образом, всего приходится по одной машине на каждую альтернативу, и каждая из машин продолжает своё выполнение в пределах назначенной альтернативы. Следует отметить, что с этих пор все машины действуют абсолютно независимо и ничего не знают друг о друге, хотя они и могут разделять один и тот же набор команд. Такой способ может быть применён для обработки альтернатив в языке MSC [3] на HMSC диаграммах и альтернативных выражений на самих MSC диаграммах. Данный пример поднимает ещё один важный вопрос об организации параллельной работы нескольких виртуальных машин.
5.1.3. Параллельное исполнение При комплексировании работы виртуальных машин могут возникать ситуации, когда нужно организовать их параллельное выполнение. Это может потребоваться, например, в случае необходимости рассмотрения всех 207
альтернатив сразу. Кроме этого, некоторые языки моделирования имеют в своём составе специальные конструкции для обозначения того, что элементы модели должны работать параллельно. Для организации параллельной работы нескольких виртуальных машин можно использовать механизм потоков (thread), предоставляемый многозадачными операционными системами. В этом случае каждая виртуальная машина запускается в отдельном потоке. Если же существует необходимость организации более детального контроля над параллельной работой виртуальных машин, то можно вручную организовать их псевдопараллельное выполнение. В этом случае в качестве единицы работы отдельной виртуальной машины можно использовать её команды. Каждая из параллельно работающих машин поочерёдно получает право сделать один шаг, после чего она возвращает управление. Для реализации этого можно использовать менеджер, содержащий список всех параллельно работающих машин и раздающий им право сделать очередной шаг на основании выбранной стратегии. Если в системе реализованы вызовы одной виртуальной машины из другой, то при получении машиной права сделать очередной шаг возможны два варианта: 1. Не существует машины, вызванной из данной машины. В этом случае машина выполняет одну команду из своего программного поля и возвращает управление. 2. Из данной машины A вызвана машина B, и машина A ожидает её завершения. В таком случае машина A делегирует право сделать очередной шаг машине B. Машина B выполняет шаг и возвращает управление в машину A, после чего та тоже возвращает управление. От того, насколько эффективно будет организована работа с моделями, напрямую зависит успех MDD. Один из наиболее удобных и эффективных путей познания пролегает через эксперимент. Именно поэтому исполнение моделей способно значительно улучшить и ускорить их понимание. В данной статье был предложен способ исполнения моделей, основанный на использовании виртуальных машин. Показаны его основные преимущества по сравнению с непосредственной интерпретацией и конечными автоматами, которые широко применяются в данной области. При проектировании системы исполнения моделей, основанной на виртуальной машине, разработчик обладает большей свободой в принятии решений, поскольку команды виртуальной машины создаются исходя из потребностей выбранной предметной области. Этим же обусловлена и простота перевода модели в последовательность таких команд. Кроме того, виртуальные машины обладают широкими возможностями комплексирования. Литература: 1. Брукс Ф. Мифический человеко-месяц или как создаются программные системы. — Пер. с англ. — СПб.:Символ-Плюс, 2001. — 304 с.: ил.
208
2. Harel D. Biting the silver bullet: Toward a brighter future for system development // Computer. — 1992. — Jan. — P. 8-20. 3. ITU-T Recommendation Z.120 Message Sequence Charts (MSC). 4. Ladkin P. B., Leue S. What do Message Sequence Charts mean? // Formal Description Techniques VI, IFIP Transactions: Proc. of the 6th Intern. Conf. on Formal Description Techniques / Ed. by Tenney R. L., Amer P. D., Uyar M. U. — Boston, 1994. P. 301-316. 5. Selic B. The Pragmatics of Model-Driven Development // IEEE Software. — 2003. — Vol.20, No.5. — P. 19-25.
209
Рефакторинг архитектуры программного обеспечения: выделение слоев
количество различных определений этого понятия, имеющих во многом схожий смысл. В качестве примера можно привести следующее определение: архитектура программного обеспечения это первичная организация системы, сформированная ее компонентами, отношениями между компонентами и внешней средой системы, а также принципами, определяющими дизайн и эволюцию системы [3].
2.1. Зачем менять архитектуру?
В последнее время наблюдается тенденция к увеличению продолжительности жизненного цикла успешных программных проектов. Как следствие, растет объем унаследованного кода, поддерживаемого сообществом разработчиков [1]. Именно это объясняет исключительную важность задач, связанных с облегчением сопровождения и развития существующего программного кода. В то же время, этим задачам уделяется недостаточное внимание со стороны научного сообщества и разработчиков инструментальных средств. Как следствие, современные методики переоценивают значение начальной фазы жизненного цикла программной системы и практически игнорируют ее дальнейшую эволюцию. Таким образом, в настоящее время существует явный недостаток методик и эффективных инструментов поддержки работы с существующим кодом. В последнее время наметился перелом ситуации: стали вызывать значительный интерес вопросы систематического использования трансформаций как центрального организующего принципа процесса развития и сопровождения существующего программного обеспечения. Однако большинство исследователей рассматривает трансформации достаточно узко – как трансформации на уровне исходного кода – рефакторинг [2]. Тем не менее, в настоящее время практически не существует исследований, посвященных трансформации на более высоком уровне абстракции – уровне архитектуры ПО. В то же время, многие сценарии сопровождения и развития существующего кода подразумевают изменение архитектуры существующей системы. В связи с этим, большой интерес вызывает разработка методики и сопровождающих ее инструментальных средств, нацеленных на организацию предсказуемого и управляемого процесса изменения архитектуры ПО. В данной работе рассматривается один из основных методов рефакторинга архитектуры ПО – выделение слоев, а также его место в контексте рефакторинга архитектуры как многошагового итеративного процесса.
Потребность в изменении существующего программного обеспечения может возникнуть в ходе решения широкого круга задач по его модернизации. В общем случае изменения существующего программного обеспечения способны затронуть не только его код, но и все остальные артефакты, связанные с трансформируемой программной системой. Одной из наиболее существенных разновидностей здесь является изменение архитектуры программной системы. В качестве примеров можно привести следующие сценарии, требующие изменения архитектуры существующего ПО: Преобразования, обусловленные функциональными изменениями ПО. Желательно, чтобы внедрение новой функциональности не затронуло существующую логику системы. Также желательно, чтобы сложность внедрения новой функциональности в существующую систему не превышало существенным образом сложность реализации этой функциональности в рамках нового проекта. Хорошая архитектура позволяет достичь поставленных целей. Итак, изменение существующей архитектуры – хороший шаг на пути внедрения новой функциональности, к тому же облегчающий и дальнейшую эволюцию системы. Смена платформы ПО. Крайне желательно, чтобы смена платформы ПО как можно меньше затронула существующий код, и чтобы можно было ограничиться изменениями только в узкой платформенно-зависимой прослойке системы. Выделение такой прослойки – архитектурная задача. Ее решение всегда сопряжено с необходимостью изменения архитектуры. Преобразования, связанные с реорганизацией компании, ведущей разработку. Примером такой реорганизации может стать аутсорсинг. Решение об использовании аутсорсинга типичный шаг по оптимизации производства. К сожалению, этот шаг зачастую затрудняется проблемой выделения и передачи компонентов для внешней разработки. Изменение архитектуры программной системы способно облегчить решение этой задачи. Список сценариев, приводящих к потребности в изменении архитектуры существующего ПО, на этом не исчерпывается: приведенные выше примеры призваны лишь продемонстрировать широкий спектр задач, которые обусловливают необходимость подобных изменений.
2. Архитектура ПО и ее рефакторинг
2.2. Как представить архитектуру и ее изменения?
В настоящее время не существует общепринятого определения термина “архитектура программного обеспечения”. В то же время, существует большое
Специфика описания архитектуры и ее изменений заключается в том, что, в отличие от программного кода, архитектура не имеет явного представления, за
М. Ксензов ([email protected])
1. Введение
211
212
исключением, может быть, тех случаев, когда она явно задокументирована. Однако даже в последнем, оптимистическом случае трудно гарантировать соответствие задокументированной архитектуры той фактической высокоуровневой логической структуре, которая на самом деле существует в системе. Способом описания архитектуры и ее изменений могут стать структурные модели. В настоящее время существует большое количество нотаций и инструментов, поддерживающих структурное моделирование ПО. Возможность автоматического извлечения моделей из кода гарантирует их точность и позволяет своевременно их обновлять. Эта возможность становится ключевой при выборе инструментария, поскольку соответствие модели фактической структуре существующего кода при моделировании архитектуры и ее изменений представляется исключительно важным для обеспечения точного и управляемого процесса. Для дальнейшего исследования архитектуры программных систем в данной статье используется нотация структурного моделирования, принятая в инструменте KLOCwork Architect, который предоставляет возможность автоматического извлечения моделей из программного кода и их редактирования. Приведем краткий обзор этой нотации. Модели программных систем, используемые в KLOCwork Architect (в дальнейшем модели) [4], отдаленно напоминают модели типа сущность-связь (Entity-Relationship models). Говоря строго, они относятся к классу контейнерных моделей, подробно рассматриваемых в работе [5]. Основными элементами модели являются следующие элементы: Архитектурный блок (Architecture Block). Модель KLOCwork Architect состоит из так называемых архитектурных блоков. Архитектурные блоки представляют в модели структурные элементы программной системы, вне зависимости от уровня абстракции, на котором идет моделирование. Архитектурные блоки обладают, по меньшей мере, двумя основными атрибутами: имя и тип. Имена архитектурных блоков предопределяются именами тех структурных элементов системы, которые они представляют в модели. Типы архитектурных блоков существенно зависят от уровня абстракции, на котором происходит моделирование, и конкретной задачи, в рамках которой проводятся исследование архитектуры. Например, при моделировании систем, построенных в рамках каких-либо компонентных технологий, основным используемым типом архитектурных блоков являются “компоненты”. При моделировании системы сборки ПО основными используемыми типами являются “папки” и “файлы”. Отношение (Relation). В модели KLOCwork Architect под отношением понимается односторонняя связь между парой архитектурных блоков. Так же, как и архитектурные блоки, отношения могут быть различных типов. В качестве примера можно привести следующие типы отношений:
Доступ к данным: A читает данные из B (блок A – функция, блок B – класс или атрибут класса). Между любой парой блоков в модели может существовать произвольное количество разнонаправленных отношений, при этом их типы также могут различаться. Пример модели. В качестве иллюстрации рассмотрим микроскопическую тестовую систему на языке C и модель, автоматически полученную из нее системой Architect. Система имеет следующую структуру: Папка test, содержащая: Файл a.h, содержащий текст void a();
Файл a.cpp, содержащий текст #include "a.h" void a() { int a = 0; a++; }
Для подобной системы извлеченная автоматически модель будет иметь следующую структуру: Корневая Содержит: диаграмма Имя блока: test, тип: DIRECTORY
Диаграмма test
Диаграмма a.cpp
Инстанциация: A инстанциирует B (блок A – функция, блок B – класс). 213
214
Содержит: Имя блока: a.cpp, тип: FILE Имя блока: b.cpp, тип: FILE Отношение между a.cpp и a.h, тип INCLUDES Отношение между a.cpp и a.h, тип DECLARED-BY Содержит: Имя блока: a, тип: FUNCTION Иcходящее отношение в блок a с диаграммы a.h, тип DECLARED-BY
Диаграмма a.h
Содержит: Имя блока: a, тип: FUNCTIONDECLARATION Входящее отношение из блока a с диаграммы a.cpp, тип DECLARED-BY
2.3. Рефакторинг архитектуры
Рефакторинг объектно-ориентированного кода зарекомендовал себя как эффективный способ решения задач эволюции и сопровождения программ. Однако в настоящее время практически не существует исследований, посвященных рефакторингу на более высоком уровне абстракции – уровне архитектуры ПО. Соответственно, вызывает значительный интерес перенос данной методологии на более высокий уровень абстракции. Общей целью усилий по разработке и стандартизации методики изменения архитектуры, а также инструментальных средств, поддерживающих эту методику, является получение управляемого и предсказуемого процесса преобразования архитектуры.
2.4. Отличия архитектурного и классического рефакторингов
Как уже говорилось, потребность в изменении архитектуры может возникнуть в различных сценариях. В силу большой актуальности задачи изменения архитектуры, возникает интерес в организации методического и управляемого подхода к ее решению, а также сопровождающих ее инструментальных средств. Одним из наиболее успешных подходов к изменению существующего программного обеспечения является рефакторинг – подход, основанный на систематических трансформациях исходного кода. Рефакторинг – это изменение во внутренней структуре ПО, имеющее целью облегчить понимание его работы и упростить модификацию, не затрагивая наблюдаемого поведения. В привычном понимании разработки ПО сначала создается дизайн системы, а потом пишется ее код. Со временем код модифицируется, и целостность системы, соответствие ее структуры изначально созданному дизайну постепенно ухудшается. Дальнейшее развитие системы постепенно сползает от направленной, проектируемой деятельности к хакерству. Рефакторинг представляет собой противоположную практику. С его помощью можно взять плохой, хаотический проект и переделать его в хорошо спроектированный код. Каждый шаг этого процесса чрезвычайно прост. Например, шагом может стать перемещение поля или метода из одного класса в другой, расщепление класса и т.д. Однако суммарный эффект таких небольших изменений оказывается куму-лятивным и может радикально улучшить проект. Процесс рефакторинга явля-ется прямой противоположностью постепенной деградации кода системы [2]. При документации и каталогизации методов рефакторинга принято использовать полуформальную нотацию, в которой каждый из методов описан так называемым паттерном. Любой паттерн описывает и именует типовую задачу, которая постоянно возникает в работе, а также принцип ее решения, причем таким образом, что это решение можно использовать потом снова и снова. Паттерн именует, абстрагирует и идентифицирует ключевые аспекты структуры общего решения [6]. Помимо прочего, паттерны формируют словарь решений для данной проблемной области и позволяют двум специалистам в этой области именовать типовые решения и понимать друг друга, не объясняя каждый раз суть самих решений. 215
При переносе методики рефакторинга на уровень архитектуры существует ряд особенностей, которые обуславливают изменение "облика" метода: Объекты. При переходе от классического рефакторинга к архитектурному меняются объекты, с которыми идет работа. В классическом рефакторинге сущностями, с которыми идет работа, являются такие элементы, как класс, экземпляр класса. Архитектурный рефакторинг применяется к системам и компонентам. В разных типах рефакторинга различаются также и виды связей, возникающих между объектами. Масштабы изменений. Классический рефакторинг применяется в существенно меньших масштабах – обычно последствия применения отдельного паттерна классического рефакторинга ограничивается несколькими файлами. Паттерны архитектурного рефакторинга применяются к компонентам архитектуры. При проецировании этих паттернов обратно с уровня структурной модели на программный код изменения могут затронуть существенно больший объем существующего кода (подробно о проецировании изменений модели на код – в разделе 2.3.2). Описание изменений. Методы классического рефакторинга можно проиллюстрировать статическими моделями языка UML и фрагментами кода. Для описания архитектурного рефакторинга представляется более удобным использовать специализированные структурные модели и инструментальные средства поддержки обратной инженерии. Тестирование. В соответствии с [2] трансформации можно считать корректными, если они не приводят к изменениям поведения программной системы в целом. Наличие достаточно полного набора модульных (unit) тестов позволяет убедиться в корректности трансформаций. Именно это делает модульное тестирование одним из ключевых аспектов классического рефакторинга. Для архитектурного рефакторинга также встает задача автоматической проверки корректности трансформаций. Конечно, наличие модульных тестов повышает уверенность разработчика в корректности проведенных изменений даже при применении архитектурного рефакторинга. Тем не менее, на настоящий момент неизвестно, насколько эффективно модульное тестирование работает с методами рефакторинга архитектуры. Это, 216
в частности, обуславливается разницей в масштабах изменений при классическом и архитектурном рефакторинге.
2.5. Фазы архитектурного рефакторинга Необходимо отметить еще одну специфическую черту рефакторинга архитектуры: для достижения промежуточных целей, возникающих в ходе архитектурного рефакторинга, как правило, приходится выполнять более одного шага. Эти шаги относятся к различным фазам решения поставленных архитектурных задач. Можно условно выделить следующие фазы архитектурного рефакторинга: фаза "раскопки" архитектуры, фаза трансформации архитектуры, фаза семантического анализа подсистем и фаза проецирования изменений модели на программный код. Более подробное рассмотрение фаз архитектурного рефакторинга целесообразно начать именно с проецирования изменений модели на программный код. Проецирование изменений модели на программный код. Как уже говорилось, для описания архитектурного рефакторинга представляется целесообразным использование структурных моделей. Модели извлекаются из программного кода автоматически, и каждому элементу модели соответствует некоторое множество символов исходного кода программной системы. Таким образом, редактирование модели, обусловленное применением шагов рефакторинга архитектуры, может (и зачастую должно) быть спроецировано на реальный программный код системы. Действительно, при проецировании удаления блоков из модели необходимо определить множество строк и файлов, которое соответствует удаленному блоку в программном коде. После этого необходимо удалить из программного проекта выявленные строки и файлы. При проецировании на код переноса блока в модели переносятся соответствующие строки и файлы в исходном коде программной системы и т.д. Проецирование на программный код системы шагов, выполненных в ходе некоторого архитектурного рефакторинга, хотя и является чисто механическим действием, тем не менее, позволяет извлечь практическую выгоду из проведенного анализа. Производимые таким образом трансформации можно рассматривать как архитектурно-управляемый рефакторинг программного кода. "Раскопка" архитектуры. Шаги, относящиеся к этой фазе, характеризуются тем, что соответствующие действия, применяемые к модели, не ориентированы на последующее проецирование на программный код. Они нужны только для понимания и структуризации модели. Трансформация архитектуры. Для шагов, относящихся к трансформации архитектуры, в отличие от шагов фазы раскопки, типично последующее проецирование их на реальный код программной системы. Шаги этой фазы четко связаны с реальной модификацией кода системы и, в конечном счете, ориентированы на его улучшение. Следует также отметить, что часть методов рефакторинга архитектуры не может быть строго отнесена к одной из названных категорий (раскопка и трансформация). На практике это означает, 217
что решение о проецировании этих шагов на код принимает разработчик, руководствуясь поставленной задачей. Семантический анализ подсистем. Как правило, между шагами описанных выше фаз архитектурного рефакторинга предпринимаются шаги, которые можно отнести к фазе семантического анализа подсистем: по ходу трансформаций часто встает задача выявления смысловой нагрузки подсистем. Для решения подобных задач, даже в первом приближении, зачастую приходится исследовать реальный программный код (здесь опять-таки помогает точность модели), анализировать сигнатуры функций и комментарии, а при отсутствии последних и сам код функций. Задача специалиста, вовлеченного в процесс архитектурного рефакторинга, – по возможности минимизировать объем семантического анализа (например, путем удаления вспомогательных блоков) и сделать его последовательным и направленным.
3. Выделение слоев Имеющийся опыт рефакторинга архитектуры программных систем позволил выделить несколько групп приемов, используемых наиболее часто. Эти приемы переходят из одного процесса рефакторинга архитектуры в другой с небольшими модификациями или даже вовсе без них. Естественно, повторяющиеся решения заслуживают выделения в самостоятельные паттерны или группы паттернов. Пожалуй, одним из важнейших и наиболее употребимых паттернов рефакторинга архитектуры ПО является паттерн "выделение слоев", рассматриваемый в этой статье. Значение паттерна "выделение слоев" легко объяснимо слои обладают целым рядом замечательных свойств, делающих их незаменимым инструментом структуризации системы.
3.1. Слои в архитектуре ПО Концепция слоев — одна из общеупотребительных моделей, используемых разработчиками программного обеспечения для разделения сложных систем на более простые части. В архитектурах компьютерных систем, например, различают слои кода на языке программирования, функций операционной системы, драйверов устройств, наборов инструкций центрального процессора и внутренней логики микросхем. В среде сетевого взаимодействия протокол FТР работает на основе протокола ТСР, который, в свою очередь, функционирует "поверх" протокола IР, расположенного "над" протоколом Ethernet [7]. Итак, рассмотрим основные причины интереса к слоям архитектуры программных систем: Слои легко формализуются. Интуитивно понятно, что если система разбита на ряд слоев, то слой n – это компонент или набор компонентов системы, которые используют только компоненты слоя n-1 и могут быть использованы только компонентами слоя n+1. 218
Слои обладают простой и наглядной семантикой. Как правило, в архитектуре программной системы слои представляют уровни абстракции. Слой n+1 использует слой n, следовательно, абстракция понятий слоя n+1, по меньшей мере, не ниже, чем у слоя n, а в идеале – если архитектура системы эффективна, его уровень абстракции должен быть выше. Соответственно, слой n скрывает (инкапсулирует) логику работы с понятиями, определенными на этом слое, позволяя, таким образом, слою n+1 реализовать работу с более сложными понятиями, организовать более сложную логику, используя выразительные средства нижележащего слоя. Слои широко распространены. Действительно, достаточно большое количество программных систем, особенно если речь идет о программных системах масштаба предприятия (enterprise systems), имеют именно слоистую структуру. Конечно, достаточно часто встречается ситуация, когда строгая послойная структура системы нарушается – как правило, это является следствием эрозии архитектуры (архитектурным дефектом) и ее устранение в большинстве случаев способно принести ощутимые выгоды (эти аспекты рассматриваются далее). Альтернативная реализация. Можно выбирать альтернативную реализацию базовых слоев – компоненты верхнего слоя способны работать без каких-либо изменений в нижележащих слоях [7], при условии сохранения интерфейсов. Минимизация интерфейсов. Зависимость между слоями, то есть, фактически, интерфейсы, предоставляемые нижними слоями верхним, можно свести к минимуму [7]. Такая минимизация интерфейсов позволяет увеличивать гибкость системы. Схема архитектурных слоев обладает и определенными недостатками [7]: Каскадные изменения. Слои способны удачно инкапсулировать многое, но не все: модификация одного слоя подчас связана с необходимостью внесения каскадных изменений в остальные слои. Классический пример из области корпоративных программных приложений: поле, добавленное в таблицу базы данных, подлежит воспроизведению в графическом интерфейсе и должно найти соответствующее отображение в каждом промежуточном слое. Падение производительности. Наличие избыточных слоев нередко снижает производительность системы. При переходе от слоя к слою данные обычно подвергаются преобразованиям из одного представления в другое. Несмотря на это, инкапсуляция нижележащих функций зачастую позволяет достичь весьма существенного преимущества. Например, оптимизация слоя транзакций обычно приводит к повышению производительности всех вышележащих слоев.
3.2. Паттерн В этом разделе рассматривается основной паттерн выделения слоев – это всего лишь один из вариантов, представитель целого семейства паттернов выделения слоев. Подвидов у этого паттерна существует достаточно много, и каждый из 219
них обладает своей спецификой. Эти подвиды и их особенности будут рассматриваться далее. Имя: выделение слоев. Ситуация: на диаграмме представлены элементы, для которых верны следующие условия: Исходящие связи ведут только в последний выделенный слой (слой с номером n), или их нет, если ранее не был выделен ни один слой. “Кандидаты на объединение в новый слой” c номером n+1 должны обладать общим смыслом и/или функциональностью. Простейшей проверкой на наличие общности является простой критерий: если для кандидатов можно подобрать "общее определение", то можно считать что они обладают требуемой общностью. Рецепт: Объединить блоки в новый слой “n+1”. Отметим, что для двух произвольных слоев слой, обладающий большим порядковым номером, считается "вышележащим". Важно отметить, что если в результате применения паттерна было выделено n слоев и еще остались блоки, которые в силу ограничений не смогли быть отнесены ни к одному из выделенных слоев и формально не могут быть выделены в новый слой, то эти блоки по умолчанию считаются n+1 слоем, который в дальнейшем именуется "чердаком".
3.3. Виды слоев Строгие слои. Это слои, описанные в разделе 2.1. Канонический вариант слоев, не допускающий никаких отклонений в строгой структуре и потому встречающийся относительно редко. На рис. 2 А) могут быть выделены следующие строгие слои: слой 1 – блоки с номерами 5, 6. Слой 2 – блоки 3, 4. Слой 3 – блоки 1, 2. Как уже упоминалось, систем с четкой послойной структурой, также известной как строгие слои, значительно меньше, чем систем с псевдослоями – то есть со структурой, близкой к послойной, но с допустимыми отклонениями. Далее рассматриваются некоторые виды послойных структур, используемых для рефакторинга архитектуры ПО. Нестрогие слои (Non-strict layers). Смягчение условий: нестрогие слои допускают связи от вышележащего слоя к нескольким нижележащим слоям (потенциально – ко всем), а не только к непосредственному соседу снизу. Архитектура с нестрогими слоями может быть как результатом эрозии, так и осознанным решением. Последствия: возможным дефектом архитектуры с нестрогими слоями является нарушение абстракции. Подобные дефекты затрудняют анализ системы. Кроме того, изменения слоя в такой архитектуре значительно сложнее локализовать – волна изменений прокатится по всем слоям, работающим с изменяемым слоем, то есть, в пессимистическом случае, по всем вышележащим слоям. 220
На рис. 2 Б) показаны шесть нестрогих слоев – по одному блоку в каждом слое: слой 1 – блок 6, слой 2 – блок 5, слой 3 – блок 4 и т.д. В то же время, выделение строгих слоев даст значительно более грубую структуризацию: слой 1 – блок 6, слой 2 – блок 5. Все остальные блоки попадут на "чердак", так как дальнейшее выделение строгих слоев невозможно – блок 4 нарушает строгую структуру и обращается к первому слою в обход второго слоя. Все оставшиеся блоки используют блок 4 и, соответственно, тоже попадают на "чердак". Поглощение сильносвязанных компонентов (СК). Смягчение условий: уже рассмотренные виды слоев можно модифицировать, позволив включать в произвольный слой СК [8]. При таком подходе СК рассматривается, фактически, как атомарный элемент. Без подобного смягчения условий как сам СК, так и все блоки, которые могли бы попасть в вышележащие слои, будут отправ-лены на "чердак". Действительно, рассмотренные подходы не смогут расщепить СК на слои, и он автоматически попадает на чердак, так же, как и блоки, зависящие от него. Тем не менее, отнюдь не всегда СК на структурных диаграммах свидетельствуют о плохой архитектуре системы. Например, в системах с рассылкой сообщений достаточно часто встречается сильно связанный компонент, подобный представленному на рис 1: источник событий "знает" о зарегистрированных в нем слушателях (listeners), слушатели знают о событиях (events), отправляемых источником, а в событии содержится ссылка на его источник. Таким образом, представляется удобным выделять слои и на тех диаграммах, которые содержат СК, именно поэтому было введено поглощение. Последствия: возможным дефектом архитектуры с поглощающими слоями может стать эффект "пропавшего слоя" – дефектная связь приводит к появлению СК, которые по смыслу должны находится на разных слоях.
EventListener
Event EventSource
Рис 1. События, слушатели и источники событий. На рис. 2 В) при использовании поглощения СК можно выделить те же слои, что и в случае применения строгих слоев к 2 А). Слой 1 – блоки 5, 6. Слой 2 – блоки 3, 4. Слой 3 – блоки 1, 2. Если применять обычный поиск строгих слоев, то все блоки окажутся на "чердаке". На рис. 2 Г) при использовании поглощения СК можно выделить слой 1 – блок 6, слой 2 – блок 5, слой 3 – блоки 3, 4, слой 4 – блоки 1, 2. Если применять 221
обычный поиск нестрогих слоев, блоки 1, 2, 3, 4 окажутся на "чердаке", то есть структуризация в таком случае будет значительно хуже.
1
2
1
6
3
4
2
5
5
6
3
4
А)
В)
Б)
1
2
1
6
3
4
2
5
5
6
3
4
Г) Рис.2. Примеры диаграмм.
3.4. Применение паттерна выделения слоев для анализа и улучшения архитектуры 3.4.1. Место паттерна выделения слоев в рефакторинге архитектуры Как уже говорилось, рефакторинг архитектуры содержит шаги, которые можно сгруппировать по их назначению. Выделение слоев примечательно тем, что в разных контекстах может быть отнесено к разным фазам в рамках рефакторинга архитектуры. Значение паттерна для анализа. Паттерн выделения слоев может быть применен для чистого анализа (раскопки) архитектуры. Для этого имеется несколько оснований. Выделение слоев – это прием, который в известной степени позволяет сократить и сделать более направленным семантический анализ системы за счет структурного анализа, что, несомненно, хорошо, поскольку семантический анализ – значительно более ресурсоемкий процесс. При этом нет никакой необходимости отражения слоев на программном коде и инфраструктуре его хранения. Например, при анализе архитектурного кода программной системы, написанной на Java, выделение слоев не предполагает обязательного выделения пакетов, соответствующих этим слоям. Если специалист, проводящий архитектурный рефакторинг, принимает решение все222
таки не отображать слои в пакеты языка Java, то можно считать, что выделение слоев было применено для чистого анализа архитектуры. Значение паттерна выделения слоев для улучшения системы. Выделение слоев – хорошая основа для улучшения системы. Как уже упоминалось, найти строгие слои в произвольной программной системе значительно труднее, чем найти слои с некоторыми допустимыми отклонениями, как, например, такие, которые были описаны в предыдущем разделе. Тем не менее, для каждого из допустимых отклонений известны побочные эффекты, которые можно устранять, по возможности приводя слои к строгим.
3.4.2. Примеры применения паттерна выделения слоев Улучшение архитектуры и нестрогие слои. В работе [9] описывается случай, когда найденные нестрогие слои позволили выявить архитектурный дефект в программной системе toolbus, существенно затруднявший реализацию задачи, поставленной перед разработчиками. Программная система toolbus представляет для своих клиентов механизмы коммуникации. В системе используется набор стандартных сообщений, которые были реализованы на основе сокетов операционной системы и специализированного текстового протокола для обмена данными по сокетам (на рис 3 – за это отвечает слой “Layer 2: Protocol Implementation”). Чтобы изолировать тонкости текстового протокола от разработчиков, был реализован API системы, скрывающий как ее сокеты, так и передаваемые через них коды команд за набором процедур языка программирования (Layer 3: Toolbus API). Однако по мере развития в систему были добавлены вспомогательные механизмы (“Layer 4: Applications”). Именно эти механизмы и нарушали строгость слоистой структуры, поскольку они обращались к уровню работы сокетов в обход специфицированного API системы (связь от “Layer 4” к “Layer 3”).
Поскольку перед разработчиками стояла задача смены текстового протокола, используемого в системе для обмена по сокетам, подобный дефект грозил переделкой всех стандартных механизмов системы. Архитектурный рефакторинг позволил избежать столь неприятной ситуации и впоследствии значительно сократить время, затрачиваемое непосредственно на кодирование. Анализ архитектуры и поглощающие слои. Характерным примером, демонстрирующим пользу смягчения условий для поиска слоев с целью анализа архитектуры, является ситуация, возникшая при анализе архитектуры Apache James 2.2. Apache James – это реализованный на Java мощный почтовый сервер масштаба предприятия, поддерживающий все наиболее распространенные современные почтовые протоколы и предоставляющий ряд дополнительных возможностей. После того, как для сервера Apache James была построена структурная модель и в ней были выделены основные подсистемы, была предпринята попытка выделить в модели нестрогие слои. При выделении слоев было разрешено поглощение СК. В результате была получена диаграмма 4 А). Эта диаграмма вполне осмыслена: на слое 1 оказались блок Constants.java, содержащий константы, используемые во всей системе, и блок Core, содержащий основные типы данных системы, такие как Message – сообщение, MessageHeader – заголовок сообщения и пр. Таким образом, слой 1 содержит основные определения системы. Слой 2 содержит authorization – систему контроля доступа, mailrepository – репозиторий писем, mailstore адаптер для mailrepository, созданный для унификации работы с различными системами хранения и управления сообщениями, в том числе он может служить для того, чтобы скрывать и mailrepository. Слой 3 – фактически, основной, поскольку содержит собственно ядро почтового сервера. На слое 4 располагаются вспомогательные механизмы для работы с почтовым сервером – система удаленного администрирования remotemanager и система fetch – облегчающая получение писем с сервера. С другой стороны, попытка выделения нестрогих слоев, но без поглощения СК дала гораздо более слабый результат. Выделенные слои представлены на рис. 4 Б). Из-за того, что в системе существует сильносвязанный компонент, в который входят блоки mailrepository и mailstore, на "чердак" попало большинство слоев системы, а дать слоям какое-либо внятное определение оказалось невозможно. Необходимо отметить, что СК, содержащий mailrepository и mailstore, свидетельствует о некоторой архитектурной проблеме. Как уже говорилось, mailstore – выступает как адаптер для mailrepository. Связь, идущая от mailrepository к адаптеру, свидетельствует о том, что изменение адаптера приведет к изменению адаптируемого объекта, что снижает гибкость архитектуры программной системы.
Рис.3 Архитектура toolbus. 223
224
3.5. Тактика применения Тактика применения паттерна выделения слоев имеет два основных аспекта: масштаб и время. Масштаб. Выделение слоев можно применять на разных масштабах и уровнях абстракции. Важно понимать, что каков бы ни был уровень абстракции, на котором идет рефакторинг, всегда можно попытаться выделить слои и иметь достаточно хорошие шансы на успех; при этом не так важно, идет ли моделирование в терминах пакетов, компонентов или даже файлов. Более того, слои облегчают изменение масштабов абстракции, используемые для моделирования. Если в системе найдены слои, то изменение масштабов можно производить и вручную – несколько соседних слоев достаточно легко объединяются в один слой, происходит укрупнение подсистем, с которыми идет работа. Понижение масштаба может быть достигнуто за счет разбиения одного слоя на несколько подслоев. Следует заметить, что это не всегда возможно, в отличие от объединения слоев, особенно без предварительной подготовки и вспомогательного рефакторинга систем разбиваемого уровня. Время. Этот аспект касается того, когда нужно применять выделение слоев. Это достаточно хороший паттерн, чтобы с него начать анализ и рефакторинг архитектуры системы, особенно если эрозия архитектуры не катастрофична. Выделение слоев ведет к тому, что рефакторинг архитектуры приобретает "направленность", задача сводится к меньшим подзадачам; теперь детальный семантический анализ можно применять только к тем слоям, которые имеют непосредственное отношение к поставленной задаче. Тем не менее, как и всякий другой паттерн, выделение слоев особенно хорошо работает в связке с другими паттернами. Шансы на успех резко вырастут, если провести предварительную подготовку, применив как минимум "удаление мертвых блоков" и "инкапсуляцию приватных блоков", описанных в [9].
А)
4. Заключение Выделение слоев является одним из ключевых паттернов для анализа и улучшения архитектуры. Слои позволяют упорядочивать архитектурные модели и, следовательно, упрощать их анализ. Кроме того, существуют различные разновидности слоев и различные тактики применения этого паттерна. С архитектурной точки зрения, с каждой из разновидностей слоев связаны определенные плюсы и минусы, что позволяет быстро находить и устранять различные архитектурные дефекты. В заключение необходимо отметить, что поиск блоков, которые могут быть выделены в слой на больших диаграммах, представляется затруднительной задачей. Тем не менее, решение такой задачи достаточно легко автоматизировать. Для автоматического поиска кандидатов на объединение в новый слой достаточно перебрать блоки исследуемой структурной модели и проанализировать направления их связей. В частности, при создании примеров
Б) Рис. 4. Анализ Apache James.
225
226
для этой статьи использовались подобные средства автоматического поиска слоев, реализованные как плагины к KLOCwork Architect. Литература: 1. Research Issues in The Renovation of Legacy Systems, A. van Deursen, P. Klint, C. Verhoef, CWI research report P9902, April 1999. 2. Рефакторинг: Улучшение существующего кода, Мартин Фаулер, Символ, СанктПетербург, 2003. 3. Recommended Practice for Architectural Description of Software-Intensive Systems, ANSI/IEEE Std 1471-2000. 4. Insight: reverse engineer case tool, N. Rajala, D. Campara, N. Mansurov, IEEE Computer Society Press, 1999, Los Alamitos, CA, USA Pages: 630 – 633. 5. Managed Achitecture of Existing Codeas a Practical Transition towards MDA, N. Mansurov, D. Campara, Klocwork, 1 Chrysalis Wat, Ottawa, Canada, K2G6P9, [email protected] 6. A Pattern Language, C. Alexander, S. Ishikawa, M. Silverstain, M. Jakobson, I. FiksdahlKing and S. Angel, Oxford University Press, 1977, New York. 7. Архитектура корпоративных программных приложений, Мартин Фаулер, Вильямс, Москва, 2004. 8. Алгоритмы: построение и анализ, Т. Кормен, Ч. Лейзерсон, Р. Ривест, МЦНМО, Москва, 2001. 9. Рефакторинг архитектуры программного обеспечения, М.В.Ксензов, ИСП РАН, Препринт 4, Москва, 2004.
227