Препринт Института системного программирования РАН (ИСП РАН)
В статье описывается подход к построению инфраструктуры использования программных стандартов. Предлагаемый подход основан на формализации стандартов и автоматизации построения тестов для проверки соответствия им из полученных формальных спецификаций. В рамках этого подхода предлагается технологическая поддержка для решения ряда возникающих инженерных и организационных задач, что позволяет использовать его для сложных промышленных стандартов программного обеспечения. Этот тезис иллюстрируется использованием описанного подхода для формализации ядра Базового стандарта Linux (Linux Standard Base). Данная работа лежит в рамках подходов к решению задачи по обеспечению развития надежных крупномасштабных программных систем, провозглашенной международным академическим сообществом одним из Больших Вызовов в информатике [1,2].
Идея, лежащая в основе использования интерфейсных стандартов, проста - настаивая на их соблюдении, мы обеспечиваем компонентам, созданным разными разработчиками, возможность взаимодействовать через стандартизованные интерфейсы. Таким образом, их совместимость обеспечивается без введения чересчур жестких ограничений на возможные реализации, что ограничило бы как творчество отдельных разработчиков, так и инновационный потенциал компаний-поставщиков ПО. Этот подход хорошо работает, если стандарт определяет функциональность, реализуемую фиксируемым им интерфейсом, достаточно точно и недвусмысленно.
Однако, тексты многих современных стандартов, описывающих требования к программным интерфейсам, далеко не так точны. Это объясняется историей их появления. Обычно такие стандарты разрабатываются под давлением требований рынка и должны принимать во внимание противоречивые интересы многих поставщиков программного обеспечения, интерфейс которого предполагается стандартизовать. В таких условиях только базовая функциональность может быть определена непротиворечивым образом, а для сложных и специфических функций каждая группа разработчиков предлагает свое собственное решение. Поскольку многие поставщики ПО уже вложили деньги в имеющиеся у них решения, им очень тяжело отказаться от этих инвестиций в пользу варианта одного из конкурентов, который при этом остается в выигрыше.
Чаще всего группа по выработке стандарта приходит к компромиссу, при котором основные поставщики должны внести примерно одинаковые по объему изменения в ПО каждого из них, а в тех случаях, где необходима серьезная переработка, стандарт осознанно делается двусмысленным, чтобы любое из уже имеющихся решений могло декларировать соответствие ему.
Это позволяет поставщикам ПО потратить приемлемые для них деньги на обеспечение соответствия своих систем стандарту, но в то же время подрывает столь желаемую совместимость различных его реализаций. Некоторые из действующих стандартов на интерфейсы ПО или на телекоммуникационные протоколы появились еще 20-25 лет назад и неоднократно пересматривались. Конечно же, каждая новая их версия становилась более строгой и непротиворечивой, и большинство проблем первых выпусков уже устранено. Однако неясности и противоречия постоянно вносятся в новых добавлениях, которые так и будут появляться в связи с постоянным развитием технологий.
Выход из этого замкнутого круга многие специалисты по программной инженерии видят в формализации требований стандартов. Сама попытка переформулировать их в строгом виде вскрывает противоречия и двусмысленности. Формализация большого текста, конечно, требует приложения колоссальных усилий, но она может быть проведена не в один прием, а постепенно, инкрементально. Никто также не ожидает, что все неточности удастся удалить из стандарта одним махом. Начав процесс формализации, мы, по крайней мере, получаем информацию об имеющихся реальных проблемах стандарта и можем предлагать и анализировать различные варианты их решения.
Формализация делает стандарт более точным, а значит, и более полезным, но она одна все же недостаточна для адекватного обеспечения соблюдения стандарта на практике. Разработчики ПО нуждаются во вспомогательных инструментах, которые позволят им убедиться в соответствии или несоответствии той или иной системы стандарту. Поэтому, практически полезное формальное описание требований стандарта должно дополняться набором тестов, которые проверяют соответствие этим требованиям.
Эта статья представляет подход к формализации стандартов и разработке тестовых наборов, основанный на одной из наиболее детально проработанных методик тестирования программного обеспечения на соответствие стандартам, созданной для использования в разработке телекоммуникационного ПО и зафиксированной в стандартах ISO 9646 [3] и ITU-T Z.500 [4].
Продуманная методика делает тестирование на соответствие стандарту более строгим и тем самым обеспечивает более высокое качество ПО, реализующего его. Однако практическое использование наборов тестов на соответствие порождает ряд инженерных и организационных вопросов, без решения которых невозможно перенести применение этой методики для небольших примеров на современные стандарты, описывающие очень сложные системы. Можно отметить следующие аспекты.
Все перечисленные проблемы должны иметь удобные решения в рамках технологии построения и эксплуатации инфраструктуры для обеспечения соблюдения стандартов. В данной статье рассказывается о подходе, претендующем на роль такой технологии и основанном на методе автоматизированной разработки тестов UniTesK, созданном в ИСП РАН. Основные идеи и техники этого подхода рассматриваются в следующих двух разделах статьи. Четвертый раздел представляет предварительные результаты его применения к Базовому стандарту Linux (Linux Standard Base) [5], промышленному стандарту на интерфейс базовых библиотек операционной системы Linux. В последних двух разделах статьи содержится краткий обзор других подходов к построению тестов на соответствие стандартам и заключение, формулирующее основные результаты и перечисляющее направления возможного развития предложенного подхода.
Стандарты на программные интерфейсы чаще всего состоят из двух частей: обоснования (rationale), представляющего целостную картину основных концепций и функций в рамках данного стандарта, и справочника (reference), описывающего каждый элемент интерфейса отдельно. В дополнение к элементам интерфейса (типам данных, функциям, константам и глобальным переменным) справочник может описывать и более крупные компоненты: подсистемы, заголовочные файлы и пр. Отдельные разделы справочника могут ссылаться друг на друга и содержать части друг друга.
Формализация стандарта включает несколько видов деятельности.
Утверждения и ограничения из разных частей стандарта могут быть несовместимыми, двусмысленными, представлять похожие, но не одинаковые идеи. Только текста стандарта часто не достаточно, чтобы прийти к непротиворечивой полной картине. Поэтому при выделении требований необходимо пользоваться общими знаниями о предметной области, книгами и статьями по тематике, знанием о используемых в существующих системах решениях, а также общаться с авторами стандарта, экспертами в области и опытными разработчиками.
Некоторые аспекты специально не определяются стандартом точно, для того чтобы реализации различных производителей соответствовали ему. Занятный пример можно найти в текущей версии стандарта POSIX [6]. Описание функций fstatvfs() и statvfs() из sys/statvfs.h говорит: "Не специфицировано, имеют ли все члены структуры statvfs смысл для всех файловых систем" ("It is unspecified whether all members of the statvfs structure have meaningful values on all file systems").
Существует два возможных пути разрешения подобных ситуаций.
Пример из описания функции basename() в стандарте POSIX: "Если строка, на которую указывает параметр path, равна "//", то реализация может возвращать как '/', так и "//"" ("If the string pointed to by path is exactly "//", it is implementation-defined whether '/' or "//" is returned").
Выделение требований проводится до тех пор, пока весь текст стандарта, касающийся выбранной группы элементов интерфейса, не будет разбит на требования, которые можно проверить, и остальные фразы, не содержащие проверяемых утверждений. Основные результаты этой работы следующие.
Рисунок 1. Входные и выходные данные формализации стандарта и разработки тестов.
Большинство найденных требований записываются в форме контрактных спецификаций операций, типов данных и элементов данных. Каждая операция описывается с помощью предусловия и постусловия. Предусловие операции определяет область ее определения. Постусловие определяет ограничения на результаты работы операции и итоговое состояние системы в зависимости от значений ее входных параметров и состояния системы до ее вызова. Для типов данных и элементов данных определяются ограничения целостности, называемые инвариантами.
Те требования, которые не оформляются в виде контрактных спецификаций, делятся на следующие группы.
Эта деятельность имеет два результата.
Пример первого случая можно увидеть в описании функции pthread_create() из стандарта POSIX: "Если макрос _POSIX_THREAD_CPUTIME определен, то новый поток должен иметь доступ к часам процессорного времени, и начальное значение этих часов должно быть выставлено в ноль" ("If _POSIX_THREAD_CPUTIME is defined, the new thread shall have a CPU-time clock accessible, and the initial value of this clock shall be set to zero").
Критерии покрытия сложным образом связаны с конфигурационными параметрами. Возможность покрытия определенной ситуации может зависеть от текущих значений конфигурационных параметров, и эта зависимость должна быть зафиксирована, чтобы избежать многочисленных трудностей при анализе результатов тестирования.
Результатом этой работы является набор критериев покрытия, тесно связанный с конфигурационной системой.
Процесс формализации стандарта и последующей разработки тестов на основе ее результатов проиллюстрирован на Рис. 1.
Модельный тест - это модель в том же самом формализме, способная взаимодействовать с другими моделями и выдающая булевский вердикт как результат этого взаимодействия. Реализация проходит модельный тест, если он выдаёт вердикт "истина" в результате взаимодействия с ней. Имеет смысл строить только значимые тесты, т.е. те, что проходятся любой реализацией, соответствующей спецификации. Тест, не являющийся значимым, может отвергнуть совершенно корректную реализацию. Результатом извлечения тестов является набор модельных тестов или модельный тестовый набор. Желательно, чтобы он был полным тестовым набором - таким, что реализация проходит все тесты из него тогда и только тогда, когда она соответствует спецификации.
Используемые на практике стандарты обычно описывают довольно сложные и большие системы. Спецификации таких стандартов также сложны и объёмны, так что любой полный тестовый набор для такой спецификации содержит бесконечное множество тестов. Для того чтобы сделать формальное тестирование осуществимым на практике, мы добавили в описанный базовый подход понятие критерия тестового покрытия и нацеленность набора тестов на достижение критерия.
Рисунок 2. Отношения между реальными сущностями и их моделями.
Тестовый набор называется полным относительно критерия покрытия, если объединение классов эквивалентности по этому критерию тестов из этого набора есть полный тестовый набор в определенном выше смысле.
Следует обратить внимание, что критерий покрытия может быть выбран исходя из различных соображений. Единственное обязательное свойство - наличие конечного тестового набора, полного относительно избранного критерия. При построении тестового набора для проверки на соответствие стандарту разумно использовать критерии, построенные на основе текста стандарта и структуры спецификаций, являющихся его формальным представлением.
Технология UniTesK детально рассматривалась в работах [10-13]. Здесь мы приводим только краткое описание ее основных идей.
Построение тестового набора для проверки соответствия практически используемым стандартам ПО приводит к необходимости разработки конфигурационной системы тестов. Эта система включает конфигурационную систему стандарта (см. выше) и дополнительные параметры, управляющие работой тестов. Разные значения этих параметров соответствуют различным критериям покрытия, разным наборам тестовых сценариев и т.п. Хорошая конфигурационная система делает тестовый набор применимым в любых условиях, где может функционировать реализация рассматриваемого стандарта.
Более масштабным применением этого подхода стал проект Центра верификации ОС Linux [15] по формализации Базового стандарта Linux (Linux Standard Base, LSB) [5] и разработке тестового набора для проверки соответствия ему. Цель первой фазы проекта - создать формальные спецификации и соответствующий тестовый набор для 1532 функций основных библиотек Linux, перечисленных в разделах III и IV стандарта LSB 3.1 (они описывают базовые библиотеки и библиотеки утилит).
Стандарт LSB задает требования ко многим из этих функций, ссылаясь на уже существующие стандарты. Непосредственно в LSB описывается лишь 15% интерфейсов, большинство - 60% функций - определяются в стандарте Single UNIX Specification [6], который включает текущую версию POSIX, а остальные - в таких спецификациях как X/Open Curses [16], System V Interface Definition [17] и ISO/IEC 9899 (стандарт языка С) [18]. LSB не включает в себя все эти стандарты, а ссылается лишь на их части, касающиеся описания требований для отдельных функций.
Первая фаза проекта завершится в конце 2006 года. К этому времени планируется сделать все ее результаты доступными в виде открытого кода. В составе этих результатов будет следующее.
В начале проекта 1532 функции LSB были разбиты на 147 групп функционально связанных операций, которые в свою очередь группируются в большие подсистемы в соответствии с общей функциональностью - потоки, межпроцессное взаимодействие, файловая система, управление динамической памятью, математические функции, и т.д. За первые 3 месяца работы были проспецифицированы и снабжены базовыми тестовыми сценариями 320 функций из 41 группы. В процессе формализации было выделено около 2150 отдельных требований стандарта к этим функций. При этом к некоторым функциям предъявляется всего лишь несколько требований, в то время как к другим - несколько десятков.
На основе достигнутой производительности можно утверждать, что первая фаза проекта потребует около 15 человеко-лет. Чтобы начать работать в этом проекте, опытному разработчику программного обеспечения (не обычному тестировщику!) требуется около месяца для обучения особенностям технологии и изучения сопутствующих процессов. Полученные предварительные результаты позволяют надеяться, что рассмотренный подход может успешно применяться для проектов такого масштаба.
Этот подход имеет много общего с представленным в данной статье. Различия связаны, в основном, с необходимостью иметь дело со стандартами большего объема, такими как стандарты POSIX или LSB на интерфейсы библиотек операционных систем. Большой размер стандартов и соответствующих спецификаций приводит к практической невозможности использовать как полные тестовые наборы в терминах работы [9], поскольку они бесконечны, так и выбор набора целей тестирования "на глаз", который является одним из традиционных шагов разработки тестов для телекоммуникационных протоколов. Вместо этого используется более систематическое выделение отдельных требований, понятие критерия тестового покрытия, выбор критерия, ориентированного на учет всех выявленных требований, и генерация тестов, нацеленная на достижение высоких показателей покрытия по выбранному критерию.
Использование контрактных спецификаций также способствует большей практичности и масштабируемости нашего подхода. Программные контракты, с одной стороны, позволяют провести декомпозицию большой системы на более обозримые компоненты, что труднее сделать, используя автоматы или системы переходов, лежащие в основе традиционного формального тестирования. С другой стороны, пред- и постусловия лучше подходят для описания недетерминированного поведения, которое довольно часто вынужден фиксировать стандарт при наличии нескольких возможностей реализовать одну и ту же абстрактную функциональность.
Статья [19] представляет другую попытку формализации стандарта на примере ШЕЕ 1003.5 - POSIX Ada Language Interfaces (интерфейсы POSIX для языка Ada). В рамках описываемого в ней метода на базе требований стандарта сразу строились формальные описания проверяющих их тестов, без промежуточных спецификаций самих требований. Такой метод кажется нам принципиально мало отличающимся от традиционной ручной разработки тестов, при которой разработчик теста читает стандарт, придумывает на основе прочитанного тесты и записывает их в виде некоторых программ.
Существует ряд аналогичных работ по разработке тестовых наборов для проверки соответствия стандартам интерфейсов операционных систем. Наиболее известные стандарты в этой области это IEEE Std 1003.1, или POSIX [6], и Базовый стандарт Linux, или LSB [5]. Имеются и наборы тестов на соответствие этим стандартам - это сертификационные тесты POSIX от Open Group [20], открытый проект Open POSIX Test Suite [21], и официальные сертификационные тесты на соответствие LSB [22] от Free Standards Group [23]. Все эти проекты используют схожие технологии выделения требований из текста стандарта и создания соответствующего каталога требований. После этого тесты разрабатываются вручную на языке С с применением подхода "один тест на одно требование". Они не используют формализацию требований и автоматическую генерацию тестов.
Стоит отметить, что использование подхода "один тест на одно требование" создает предпосылки для укрупнения требований при их выделении, так как велик соблазн проверить как можно больше в одном тесте. К примеру, в тестах Open POSIX мы обнаружили требования, которые включают в себя десяток утверждений, которые в проекте Центра верификации ОС Linux выделяются в отдельные требования. Такое укрупнение требований может приводить к тому, что некоторые утверждения стандарта упускаются из виду и не проверяются, в то время как крупное интегральное требование, в которое они входят, считается протестированным. В результате построенный на основе таких требований отчет об их покрытии может искажать реальное положение дел, утверждая, что все требования стандарта проверены.
Кроме того, автоматическая генерация тестов из спецификаций, используемая в нашем подходе, делает тестовый набор более управляемым, позволяя описывать сами требования стандарта в одном месте, а технику, используемую для их проверки, и тестовые данные - в другом. Это значительно облегчает внесение изменений в тесты при развитии стандарта или их адаптации под требования специфической предметной области.
Однако формализация требует огромных усилий, что позволяет усомниться в применимости подходов на ее основе к реально используемым программным стандартам, достаточно объемным и сложным. Устранить эти сомнения помогут только практические примеры применения подобных методов к таким стандартам. Упомянутый выше проект [15] по формализации LSB похоже, является одной из первых попыток формализации значительной части используемого на практике стандарта высокой сложности и позволяет почувствовать все ее выгоды и недостатки.
Мы считаем, что представленный в этой статье подход позволит успешно справляться с подобными задачами. Аргументами в пользу этого мнения являются стабильное продвижение описанного проекта, история применений технологических составляющих данного подхода на практике ([12,14]) и лежащие в его основе базовые принципы хорошего проектирования больших систем [12,13]. Тот факт, что многие инженерные и организационные вопросы также находят отражение в данном подходе, позволяет надеяться на получение действительно полезных результатов, избежав участи многих проектов по использованию передовых методик разработки ПО на практике.
Описанный проект является частью проводимых международным сообществом исследований подходов к решению проблемы обеспечения развития надежных крупномасштабных программных систем, одного из Больших Вызовов в информатике [1,2]. Тони Xoap (Tony Hoar) предложил вести работы по этой проблеме в двух направлениях: разрабатывать методы и инструменты, в перспективе способные помочь в ее решении, и накапливать примеры использования подобных методов на программных системах реалистичных размеров и сложности ("challenge codes"). Второе направление необходимо как для проверки работоспособности подходов, предлагаемых в качестве возможных решений проблемы, так и для демонстрации путей от исследовательских работ в программной инженерии к их практическим приложениям, для нащупывания области применимости разрабатываемых в академической среде передовых подходов и демонстрации их возможностей инженерам-практикам и представителям промышленности.
Для стимулирования деятельности в этом направлении и вовлечения в него широких кругов разработчиков ПО ИСП РАН передает результаты этого проекта и все инструменты, необходимые для работы с ними, сообществу разработчиков ПО с открытым кодом (open source community). Мы надеемся также на помощь членов этого сообщества в решении более масштабных задач, таких, как формализация большого числа стандартов, входящих в набор Carrier Grade Linux [24], стандартов, регламентирующих встроенные версии Linux и Linux для систем реального времени, а также стандартов на широко используемые языки программирования.
| [1]обратно | http://www.fmnet.info/gc6/ |
| [2]обратно | Tony Hoare and Robin Milner, eds. Grand Challenges in Computing. Research. http://www.ukcrc.org.uk/gcresearch.pdf |
| [3]обратно | ISO 9646. Information Theory - Open System Interconnection - Conformance Testing Methodology and Framework. ISO, Geneve, 1991. |
| [4]обратно | ITU-T. Recommendation Z.500. Framework on formal methods in conformance testing. International Telecommunications Union, Geneve, Switzerland, 1997. |
| [5]обратно | http://www.linuxbase.org/spec |
| [6]обратно | http://www.unix.org/version3/ieee_std.html |
| [7]обратно | G. Bernot. Testing against Formal Specifications: A Theoretical View. In Proc. of TAPSOFT'91, Vol. 2. S. Abramsky and T. S. E. Maibaum, eds. LNCS 494, pp. 99-119, Springer-Verlag, 1991. |
| [8]обратно | E. Brinksma, R. Alderden, R. Langerak, J. van de Lagemaat, and J. Tretmans. A formal approach to conformance testing. In J. de Meer, L. Mackert, and W. Effelsberg, eds. 2-nd Int. Workshop on Protocol Test Systems, pp. 349-363. North-Holland, 1990. |
| [9]обратно | J. Tretmans. A Formal Approach to Conformance Testing. PhD thesis, University of Twente, Enschede, The Netherlands, 1992. |
| [10]обратно | I. Bourdonov, A. Kossatchev, V. Kuliamin, and A. Petrenko. UniTesK Test Suite Architecture. In Proc. ofFME2002. LNCS 2391, pp. 77-88, Springer-Verlag, 2002. |
| [11]обратно | V. Kuliamin, A. Petrenko, N. Pakoulin, A. Kossatchev, I. Bourdonov. Integration of Functional and Timed Testing of Real-time and Concurrent Systems. In Proc. of PSI 2003, LNCS 2890, pp. 450-461, Springer-Verlag, 2003. |
| [12]обратно | В. В. Кулямин, А. К. Петренко, А. С. Косачев, И. Б. Бурдонов. Подход UniTesK к разработке тестов. Программирование, 29(6):25-43, 2003. |
| [13]обратно | V. Kuliamin. Model Based Testing of Large-scale Software: How Can Simple Models Help to Test Complex System. In Proc. of 1-st International Symposium on Leveraging Applications of Formal Methods, Cyprus, October 2004, pp. 311-316. |
| [14]обратно | V. Kuliamin, A. Petrenko, N. Pakoulin. Practical Approach to Specification and Conformance Testing of Distributed Network Applications. In M. Malek, E. Nett, N. Suri, eds. Service Availability. LNCS 3694, pp. 68-83, Springer-Verlag, 2005. |
| [15]обратно | http://www.linuxtesting.ru |
| [16]обратно | http://www.opengroup.org/bookstore/catalog/c610.htm |
| [17]обратно | http://www.caldera.com/developers/devspecs/ |
| [18]обратно | ISO/IEC9899. Programming Languages -С ISO, Geneve, 1999. |
| [19]обратно | J. F. Leathrum and K. A. Liburdy. A Formal Approach to Requirements Based Testing in Open Systems Standards. In Proc. of 2-d International Conference on Requirements Engineering, 1996, pp. 94-100. |
| [20]обратно | http://www.opengroup.org/testing/testsuites/TestSuiteIndex.htm |
| [21]обратно | http://posixtest. sourceforge.net/ |
| [22]обратно | http://www.linuxbase.org/download/#test_suites |
| [23]обратно | http://fireestandards.org/ |
| [24]обратно | http://www.osdl.org/lab_activities/carrier_grade_linux |