Лекция 04 В прошлый раз было разобрано разбиение области входных данных на классы эквивалентности. В качестве базы такого разбиения использовались постусловия. Также был затронут случай, когда результат операции зависит не только от входных данных, но и от различных условий. К рассмотрению этого вопроса мы сейчас и переходим. Тестирование системы с состоянием Самое естественное, пожалуй, представление таких систем – конечные автоматы. Считается, что реакция системы определяется входными воздействиями и состоянием системы. При воздействиях возможны 2 ситуации: Состояние не изменяется Состояние изменяется Воздействие характеризуется входными данными. Говорят, что определен алфавит входных данных. Кроме входных воздействий, можно рассматривать и результаты воздействия. Как правило, это получение некоторого значения. Появляется выходной алфавит. Это некоторый формализм. В реальных системах в качестве символов входного алфавита берутся: Методы Сигналы Экземпляры методов (не просто методы, а с определенными значениями входных параметров) В разных состояниях одним и тем же воздействиям могут отвечать различные реакции. Наш автомат является детерминированным. Если фиксировано состояние и воздействие, то мы можем точно указать, в каком состоянии мы окажемся, и какой символ подадим на вход. Хуже обстоит ситуация, если по одному и тому же входу можно попасть в одно из нескольких различных состояний. Иногда при этом выдаются даже один и тот же символ, что совсем плохо. В реальной жизни от недетерминизма всегда стараются избавиться. Хотя иногда это не удается. Некоторый неустранимый недетерминизм, например, может вноситься сетью. Неприятность заключается в том, что не все символы входного алфавита можно применять во всех состояниях. В некоторых состояниях могут быть запрещены некоторые методы. У операции может быть предусловие, запрещающее ее использование в некоторых состояниях. Когда у нас есть вся эта информация, то можно сказать, что описан конечный автомат, или, более точно, автомат Милля. По-английски часто пишут FA (finite automaton) или FSM (finite state machine). Иногда эту формализацию немного изменяют. a[x>0]/x++ a[x >= 0] / x++ 1 2 b[x>1]/x-- b[x == 1]/x-- На этом рисунке изображен пример автомата. 1 и 2 – состояния автомата. Стрелки указывают переходы. Над значком дроби указан входной символ с предусловием. Под знаком дроби указана реакция (она может состоять не только в постоянном ответе, но и в модификации входных данных в зависимости от состояния). Можно рассматривать так называемые расширенные конечные автоматы, в которых для каждой операции задается предусловие. По-английски – EFSM (extended finite state machine). Кроме того, можно и результат задавать не явно, а в виде постусловий, но это делается редко. Abstract State Machines (ASM) Пусть мы строим модель программной системы. Первое, что мы говорим – имеется некоторый набор состояний. На входе появляются некоторые символы, выдается некоторый результат, и на выходе появляются некоторые символы. А как будет описываться состояние? В каждом состоянии определяется набор операций, и эти операции характеризуются сигнатурой, результатами, следующим состояниями. Но можно считать, что эти операции сами по себе являются программами. В этих программах может быть несколько параллельных потоков. И обрабатывается эта информация до тех пор, пока не выполнится некоторое условие. Каждое состояние – это маленький многопроцессорный компьютер. Бывают следующие операции: 1. Функции: f(x1, … xn) = <expression> 2. Условный оператор: if(<condition>) <jump> 3. Цикл: forall x:P(x) do <statement> Пример: стек. Stack size: int, element(int): object, top: object, input: {pop, push} x object [[это задана сигнатура]] size = 0, all j elements(j) = undef; top = undef; [[это было начальное состояние]] if(pi1(input) = push) { size := size + 1; elements(size + 1) := pi2(input); top := pi2(input) } if(pi1(input) = pop)) { size := size – 1; elements(size) := undef; top := elements(size + 1) } [[это динамика]] Поскольку ASM рос из математики, то изначально не было фиксированной нотации. Но она появилась, когда появились системы, поддерживающие ASM. ASM разработал выходец из России Юрий Гуревич. Сначала появились «развивающиеся алгебры», а лет через 10 – «абстрактные машины состояний». Только тогда стало ясно, в чем преимущества такого подхода. Сейчас есть язык ASML и система ASMLT, которые поддерживают эту абстракцию. Заметим, что выбранный способ описания состояний позволяет описать не только конечное число фиксированных состояний, но бесконечное число состояний или параметризованные состояние. Это можно видеть в приведенном выше примере стека. Задание автомата в виде программы позволяет придать дополнительную гибкость. Машина работает следующим образом. Пришел некоторый стимул. При помощи pi1 мы увидели, что этот стимул – push. Запускаются 3 параллельных процесса (“;” означает параллельное выполнение). Все эти операции отталкиваются от того, каково было предыдущее состояние стека. Когда они выполнят свои вычисления, размер стека увеличится на единицу, и в стек добавились новые операции. pim(input) адресует входной параметр с номером m. Аналогично можно разобрать и операцию pop. Если бы мы пользовались нотацией RSL, то нужно было бы использовать значения до входа (предзначения), так что можно было бы записать: size = size’ – 1. Состояний здесь столько, сколько может быть элементов в стеке. То есть, вообще говоря, их число бесконечно. Программа задается на весь автомат. Поэтому можно себе мыслить некоторые параметризованные состояния. В чем состоит абстрактность? Нет собственно конечного автомата, присутствует только его дух. Лучше поэтому говорить не о конечном автомате, а о системе с переходами. Механизм простой и мощный. Мощность состоит в следующем: Явным образом задается параллелизм При грамотном использовании спецификации удается описать модель общего вида, то есть, соответствующую любой реализации. Это достигается за счет параллелизма. Самый яркий пример спецификации на ASM – спецификация программы сортировки. На ASM можно описать спецификацию сортировки, и управляя только очередностью выполнения операций, можно получить все возможные алгоритмы сортировки (имеются в виду разумные алгоритмы сортировки, которые на каждом шаге приближаются к результату, а не те, которые сначала все запутывают, а потом все распутывают). Тестирование при помощи автоматов Пусть имеется конечный автомат: a 0 1 a b b a 2 b Для того, можно было работать с переходами состояний, необходимо построить дерево переходов. Используя дерево переходов, можно идти от корня до любого состояния. Глубина определяется очень просто: дерево строится до тех пор, пока мы не попадаем в состояние, в котором были до этого хотя бы один раз. Если мы в такую штуку попали, откатываемся назад. В каждой вершине последовательно перебираем все возможные входные символы. Пример дерева переходов для приведенного выше автомата: 0 a 1 b a 0 b 1 2 a 1 b 2 Как на основании такого модельного автомата протестировать программу? Следует помнить, что в реальном мире символам a и b соответствуют другие символы – например, вызовы методов. Первая идея – закодировать пути в дереве. Получили некоторую последовательность символов, которая проходит по всем переходам: aaababaabb. Если нельзя получить начальное состояние – это очень плохо. Но и такое в реальной жизни бывает. Например, так бывает при тестировании ядерных реакторов. Итак, тестовая последовательность построена. А как с ее помощью протестировать реализацию? Возникают вопросы: 1. Как определять состояние, в котором мы находимся? 2. Какие результаты? Ответ: если нет совсем никакой информации, то можно надеяться только на некоторые внешние наблюдения (взорвался – не взорвался, жив – мертв, занимает процессор – не занимает процессор и т.п.). Однако получать реакцию все-таки нужно для программного тестирования. Хорошо, если есть возможность проверить состояние в любой момент. Но в реальной жизни это бывает крайне редко. Наиболее часто нам доступны выходные результаты. По крайней мере, для некоторых операций. Иногда мы спрашиваем, в правильное ли состояние мы переходим. Даже если нет такой информации. Иногда без этого жить нельзя: b/0 b/0 0 a/0 0 a/0 a/1 a/2, b/2 a/1 a/2, b/2 1 2 b/0 a/1 3 1 2 b/0 a/1 3 b/1 b/1 Слева – модель, справа – реализация. Отличаются они только одним переходом. И как проверить правильность переходов состояния? Это – как раз тот случай, когда вроде бы каждая операция работает правильно, но все вместе они работают неправильно. Конечные автоматы получили большое распространение в послевоенные годы, в связи прежде всего с тестированием систем связи, особенно систем специальной связи. В 1973 году Василевский предложил следующую схему тестирования: о состояниях ничего не известно, а результаты есть. Если состояние не то, должна быть цепочка, которая рано или поздно выдаст неправильный результат (вспомним теорему Мура). И эта цепочка имеет длину не более n, где n – количество состояний автомата. Пусть V – множество входных цепочек, W – множество выходных цепочек. Экспериментируем с состояниями. Если мы проверили все цепочки длины n в заданном состоянии, то мы попали туда, куда надо. Вычислительная сложность: O(r * n ^ 2), где n – количество состояний в модели. А если количество состояний в реализации намного больше, чем для модели, то O(K ^ (m – n + 1)), где K – некоторый коэффициент. Для оптимизации этого метода в случае программных схем (количество переходов достаточно велико) используются так называемые уникальные входные и выходные последовательности – uio, unique input-output. 1 b/y a/x b/x b/y 0 2 a/y a/z a/y b/x 3 Проверочная последовательность строится перебором всех возможных входных последовательностей. Сначала – все последовательности длины 1, затем – длины 2, и так далее. Такие задачи будут на экзамене, и там особо длинных последовательностей не понадобится. Мы хотим построить последовательность, которая позволила бы различить любые 2 состояния (то есть, находясь в любом состоянии и подавая на вход эту последовательность, мы сможем определить, в каком состоянии мы находимся). Экзаменационная задача на построение последовательности Формулировка Дан конечный автомат (в виде схемы переходов). Построить последовательность минимальной длины, обладающую следующим свойством: находясь в любом состоянии и подав на вход автомата эту последовательность, на выходе получим последовательность, однозначно идентифицирующую текущее состояние. Иначе говоря, построить такую последовательность входных символов, которая в каждом состоянии вызовет уникальную реакцию (по которой можно определить текущее состояние). Решение Для решения этой задачи используется переборный алгоритм. Сначала перебираются все последовательности длины 1 (впрочем, длины 1 обычно точно не хватит, так что сразу можно начинать с последовательностей длины 2), потом – длины 2, потом – длины 3. Для каждой последовательности для каждого состояния строится выходная последовательность. Как только мы получили уникальность выходных последовательностей для каждого состояния для заданной входной последовательности, эту самую входную последовательность можно давать в качестве ответа. Пример Рассматривается автомат, изображенный выше. Последовательности длины 1 не хватит, так как состояния 4, а выходных символов только 3. Совет: при рассмотрении последовательностей записывайте в конце состояния, в которые мы перешли из заданного начального состояния при реакции на эти последовательности. Если в разных состояниях мы получили не только одинаковую последовательность, но и одинаковое конечное состояние, то дальше можно отсечь все последовательности большей длины, начало которых совпадает с данной – они все равно не помогут различить состояния. Кроме того, это поможет быстрее строить более длинные последовательности. В данном случае хватит последовательности длины 2 (это будет последовательность ab). Начнем перебирать последовательности (в скобках указано состояние, в которое мы попадаем): aa: Начало 0 1 2 3 Выход Конец xy 3 yz 0 yz 0 zx 1 Заметим, что в 2-х случаях мы получили одинаковые последовательности и одинаковые состояния. Это говорит о том, что для различения состояний нельзя использовать входные последовательности, начинающиеся с aa. ab: Начало 0 1 2 3 Выход Конец xx 2 yx 2 yy 1 zy 1 Как видим, последовательность ab подошла: последовательности уникальны для каждого состояния. соответствующие выходные Жесткая реальность Количество состояний в реализации намного больше, чем в модели. Как с этим бороться? Можно количество состояний в модели сделать равным количеству состояний в реализации, но это лишает модель простоты. Поэтому обычно рассматривают каждое состояние модели как класс эквивалентности состояний реализации. И хотелось бы строить тесты в предположении, что структура переходов в реализации совпадает со структурой переходов в модели. Если к этому стремиться, очень часто оказывается, что модельный автомат становится недетерминированным. Как с этим бороться – рассказывается на следующей лекции.