Замечание1

advertisement
Тестирование (4 лекция)
4.12.2001
Схема тестового драйвера
Рассмотрим схему организации тестового драйвера. Выделяют 2 составляющие:
1 Сценарный драйвер
2 Базисный драйвер
В свою очередь, базисный драйвер образуют 2 компоненты: оракулы и
реализационные функции. (Рис.1)
Программа тестовый драйвер получает на вход тестовую последовательность.
Тестовая последовательность попадает на вход к итератору тествой
последовательности. Это уровень скриптового драйвера(script driver). Функцией
этого уровня является генерация различных тестовых наборов,покрывающих
различные ветки программы.
Оракулы-это функции второго уровня, занимающиеся вызовом реализации
проверки тестовой последовательности(третий уровень). Они также выносят
вердикт соответсвия фактического результата теста ожидаемому результату.
Проблема. Ни в алгоритме построения тестов, ни в реализации проверки тестов
нет идей, как строить итератор.
Результат сравнивается с post условием, и выносится вердикт оракулами: true
или false.(рис.2)
Замечание1
Найдя, например, ошибку в программе на значениях, меньших нуля, все равно
надо искать возможные ошибки и при значениях , больших либо равных нуля.
Замечание2
В тестировании любая программа, которая вызывает тестируемый объект
называется драйвером.
Рассмотрим схему проведения тестирования с учетом пространства, в котором
тестирование проводится(модельное или реализационное) (рис.3)
Согласно схеме результат соответствия может быть достигнуть 2 путями:
1. Выполнив операнд в реализационном пространстве, мы получаем новое
состояние реализационного пространства и воспользовавшись функцией
абстракции, переходим в состояние модели S mod.
2. Сразу воспользовавшись функцией абстракции, переходим в состояние
модели S’ mod и в модельном пространстве выполняем операнд, переходим
в состояние модели S mod.
Почти всегда S mod, полученные разными способами: либо первым, либо
вторым, различаются. Можно пытаться доказать их равенство эквивалентными
преобразованиями, но это сложная задача. Если бы мы могли требовать полного
совпадения S mod, то задача соответствия модели решалась бы очень просто. На
практике совпадения не происходит.
Пример В ходе реализации модели возникла нехватка памяти, чего не
происходило в самой модели. Это означает рассогласованность модели и
реализации? Нет. Так как модель не обязана заботиться о деталях реализации.
Поэтому, на практике отказались от написания явных(explicit) спецификаций и
используют неявные(implicit) с pre и post условиями.Сравнивая результат с
постусловием, проверяем корректность результата. Схема видоизменяется
(рис.4).
Представим себе, что мы написали спецификацию модели, естественно в
абстрактных терминах, не заботясь о реализации. Теперь мне надо
сгенерировать тестовые данные и проверить выполнение предусловия.
Проблема. Предусловие сформировано в модельном пространстве, а тестовые
данные надо проверять в реализационном. Не всегда существует
преобразование из модельного пространства в реализационное.
Мы написали генератор тестов абстрактных данных из другого пространства.
Как тесты “спустить вниз” в реализационное? Эта проблема влечет
модификацию схемы(рис.5)
Соответственно функции mod_to_equal и impl_to_equal преобразуют данные из
модельного пространства в реализационное и обратно.
Работа очередного тестового шага выглядит так : (рис.6)
Такая схема пригодна для оценки предусловия на модельном уровне. Мы могли
бы заниматься этой проблемой и на реализационном уровне, но это тип
тестирования “белый ящик”, дающий другой уровень надежности и качества
тестирования.
Проблема. Функция понижения уровня S’m S’i строится неоднозначно.
Всегда можно посторить несколько корректных примеров, но они между собой
различаются.
Решение этого вопроса таково: нас интересует генерация цепочки тестов и цель
– вызов методов их проверки из одного класса, результатом будут выходные
параметры и состояние самого класса. Рассмотрим рис.7
.
Проблемы компоновки тестового набора.
1.Явные и неявные спецификации.
Это и есть вопрос задачи. Какого рода спецификации хотим иметь.
Например, плюс явных состоит в легких сравнениях (например чисел),
в то время как в неявных сравнения сложны.
2.Существует ли функция mod_to_impl?
Если существует, то у нас развязаны руки, строим разнообразные схемы,
Иначе пользуемся эпизодической схемой.
3.Открытые/скрытые состояния целевого объекта.
До и после выполнения функции retrieve имеем различные состояния
модели, но нам важна неизменность состояния глобальных переменных.
Вдруг, у нас эти переменные скрыты. В случае “белого ящика” располагая
кодами, можем изменить в них что-то, а если же это запрещено, или у нас
бинарные файлы?(“черный ящик”) Использование функции retrieve
невозможно.
В этом случае меняются оракулы.
1 pre?
2 mod_to_impl
3 callout impl
4 retrieve(out impl)
 out mod
Опциональный шаг – проверка
предусловия
Трансформация входных тестовых
данных для модели реализации
Вызов целевой функции. Получение
выходных параметров модели
реализации
Получение выходных параметров
в модели
5 post_op(in mod,out mod)
6 v mod : =newstate(v mod,
in mod, out mod)
Сравнение входных и выходных
данных модели по постусловию.
Однако в общем случае постусловие
должно проверять и изменения
значений глобальных переменных. А
если они скрыты? Решением является
шаг 6.
Вместо проверки соответствия
постусловию глобальных переменных
мы вычисляем ожидаемые значения
глобальных переменных скрытого
состояния модели(v mod), которые,
конечно, зависят от своего состояния
в модели реализации. Теперь цикл
тестирования замкнулся, так как,
придя на первую итерацию, можем
воспользоваться новым
скорректированным состоянием
v mod.
Заключение. Если вы хотите, чтобы план тестирования был обоснован, надо
прикинуть, какой моделью вы пользуетесь. Надо найти
обходы,которые позволяют обхватить модель полно. От
профессиональных тестировщиков требуется не только сам
эффективный тестовый набор(он обнаруживает много ошибок),
но и качественное обоснование тестового набора.
В качестве заключительного примера рассмотрим тестирование компилятора.
Пусть глубина дерева, которое он строит в процессе работы – изменяемый
параметр. Проверка показала, что при глубине дерева равной 3 проходит
порядка 14 тыс. тестов, обнаруживается порядка 10 ошибок. Глубину
увеличили. Прошло 300 тыс. тестов, а вот количество найденных ошибок не
изменилось!!! При этом было потрачено очень много времени и ресурсов. Это
говорит о неправильно избранной схеме тестирования. При грамотном
разбиении на классы эквивалентности достаточно один раз проверить один
класс. Но достижение такого эффективного результата – это и есть навык(skill)
тестировщика.
Каналы. Параллельные процессы. Недетерминизм.
1. Каналы – новый вид сущностей на ряду с values, types в RSL.
Синтаксис: channel id: type_expr
В канал можно положить сообщение и получить из него сообщение. В RSL
межканальное взаимодействие осуществляется с помощью сообщений.
channel c : Int
variable a : Int
Выражение с!(a+1) является типа Unit. Происходит следующее: берется
значение a, к нему прибавляется 1, и полученное значение отправляется в канал.
a : = c? означает, что очередное значение извлечено из канала. Целиком
выражение типа Unit, однако c? является типа Channel.
2. Параллельность.
Рассмотрим выражение a : = c? || b : = c? || c!1 || c!2
Выражения слева и справа от || могут вычисляться в любом порядке. Упрощая
это выражение можем получить :
a : = 1 || b : = 2 либо a : = 2 || b : = 1
Семантика:
|| -означает одновременное, параллельное вычисление выражений и слева, и
справа.
Мы написали “либо” На самом деле в RSL оно заменяется оператором
внутреннего выбора
(в ASCII |^| ) Порядок присваивания a двойки или b
единицы неизвестен.
Семантика:
Замечание1 Эффект выполнения нашего выражения эквивалентен выполнению
a:=2;b:=1
Замечание2 Не может быть такого,чтобы и a, и b получили одновременно
значения 1. Так как передача и извлечение сообщений из канала
происходят синхронно. Извлечнное из канала значение сразу же
удаляется из канала. Сообщение не может пропасть или
раздвоиться. Любой процесс может быть остановлен, пока в
канале до него не дойдет очередь. Выстраивается очередь
сообщений. Как сообщения упорядочиваются – неопределено.
Возможен случай, когда сообщение послано одним процессом, но не получено
ни одним другим процессом. Процесс-отправитель останавливается.
В программе выражение c!x имеет значение stop (x- не прочли, так как в
окружении процесса нет ни одного другого процесса, то из канала c сообщение
не будет читаться)
Рассмотрим выражение (c!x
a : = b?) Символ, используемый в этом
выражении(ASCII |=|), обозначает оператор внешнего выбора.
Семантика:
Будет выполнено лишь одно из выражений либо слева, либо справа от |=|, по
следующему правилу: if p then a : = c? else b!y end p- некое условие выбора,
которое определяется окружением и, соответственно, в другом процессе
определяется, что выбрать: выражение слева либо справа от |=|.
Interlock
(в ASCII ++) (val_expr1 ++ val_expr2) || environment
c
c
взаимодействие через канал с.
Семантика:
выражение, заключенное в () не входит во взаимодействие с environment до тех
пор, пока не завершится взаимодействие между процессами в скобках.
(c! ++ b?) || c?
stop
(c! ++ c? ; b! ) || c?
Процесс c завершится. После этого оставшийся процесс b взаимодействует с
окружением, т.е.с с. Если процессы в () не могут терминировать, то вся
конструция получит значение stop, если же один из процессов завершился, то
может быть выполнена реакция остальных с окружением.
Download