Тестирование на основе моделей к.ф.-м.н. В. В. Кулямин ИСП РАН Тестирование вообще Тестирование (IEEE 610, SWEBOK): Оценка соответствия системы требованиям к ней на основе результатов наблюдения за ее работой в специально подготовленных ситуациях Система в ходе тестирования должна работать Нужно готовить специальные ситуации – тесты Оценивается соответствие – ищем ошибки Нужна общая оценка – ищем все ошибки «Обычное» тестирование 1. 2. 3. 4. 5. Придумываем ситуацию Оформляем ее в виде сценария взаимодействия теста с системой Понимаем, как должна система вести себя в его рамках Дополняем сценарий проверкой правильности Результат – тестовый вариант (test case) Оцениваем достаточность имеющегося набора ситуаций: достаточно – конец, нет – goto 1 «Обычное» тестирование Ограниченная очередь размера 3 1. В начале – пуста 1. В начале – полна 2. put(X) 2. put(X) 3. Y = take() 3. assert (exception) 4. assert (Y == X) ? Зачем здесь модели? Распознавание ошибки – ментальная модель правильной работы Math.abs(-2147483648) = -2147483648 Полнота набора ситуаций – ментальная модель всех важных ситуаций При тестировании на основе моделей модели выделяются явно и заранее Используемые модели Конечные автоматы (Finite State Machines, FSM) Программные контракты (Software Contracts) ... Конечный автомат (банкомат) Начало Время / Вывести исходное сообщение Время / Вывести исходное сообщение Карта / Вывести приглашение ввести PIN Сообщение о выдаче баланса Сообщение о блокировке Запрос баланса / Выдать чек Авторизация Некорректный PIN / Сообщение Корректный PIN / Авторизация 1 Вывести меню Некорректный PIN / Заблокировать карту Некорректный PIN / Сообщение Авторизация 2 Выбор операции Сообщение о выдаче денег Сумма / Выдать деньги Выбор суммы Запрос денег / Вывести вопрос о сумме Тестирование на основе FSM a/x 0 b/y a/x 1 b/x a/x b/y b/y a/x 2 1 a/y 0 b/x b/y 2 a/y Модель Реализация Можно ли проверить, ведет ли себя неизвестная реализация всегда так же, как модель? При каких условиях это можно сделать? Проверка соответствия Требования к модели Полная определенность Минимальность Сильная связность b/y Гипотезы о реализации 0 b/y Реализация – конечный автомат с теми же стимулами и реакциями Полная определенность В начале находимся в начальном состоянии Число состояний ограничено a/x a/x 0b/y b/y a/x a/x 1 1a/y 0 b/x a/x b/y 1a/x =2 0 a/y b/y a, b 0 1 3 2 x, y Методы проверки – обход Построение обхода (transition tour) aababb s = status – возвращает идентификатор состояния sasasbsasbsbs 0x1x1x2y2y0y2 (xxxyyy) 0x1x1x2y2y1x2 (xxxyyx) 0x1x1x2y2y2y2 (xxxyyy) a/x 0 b/y a/x 1 a/x b/x b/y 2 a/y 0 b/y a/x 1 b/x 2 a/y b/y a/x 0 b/y b/y a/x 1 b/x 2 a/y Методы проверки – W-метод 0 r = reset – переводит в начальное состояние a/x b/y S – множество последовательностей, достигающих b/y a/x всех состояний 1 2 S = {ε, a, b} b/x a/y W – множество последовательностей, reset различающих все состояния W = {a, b} – 0:{x, y}; 1:{x, x}; 2:{y, y} S{a,b}W – полный тест {ε, a, b}{a, b}{a, b} = {aa, ab, ba, bb, aaa, aab, aba, 0 abb, baa, bab, bba, bbb} 1 0 3 aarabrbarbbraaaraabrabarabbrbaarbabrbbarbbb a/x b/y xx.xx.yy.yy.xxx.xxx.xxy.xxy.yyy.yyy.yyx.yyy b/y 2 a/x xx.xx.yy.yy.xxx.xxx.xxy.xxy.yyy.yyy.yyy.yyy 1 2 b/x S{a,b}m-nW – полный тест для больших реализаций a/y Другие методы проверки D-метод различающая последовательность u = ba – 0:yy; 1:xy; 2:yx S{a,b}u abarbbaraabarabbarbabarbbba Методы, работающие без reset Адаптивные методы a/x 0 b/y a/x 1 b/x b/y 2 a/y Получаемые тесты abarbbaraabarabbarbabarbbba xxy.yyx.xxxy.xxyx.yyyx.yyyy z = a(); assert(z == ‘x’); z = b(); assert(z == ‘x’); z = a(); assert(z == ‘y’); reset(); … Тесты теперь можно генерировать полностью автоматически, если выполнены все ограничения на модель Конечные автоматы – еще не все Не всякую задачу удобно описывать конечным автоматом Часто можно только очень большим Сложность построения тестов O(p(m-n+1)n3) Чаще всего ограничения на число состояний в реализации неизвестны Часто требования недетерминированы Часто результат в некотором состоянии определен не для всех стимулов Конечные автоматы – еще не все Ограниченная очередь размера 3 [X, X, X] [X, X] [X, X, Y] [Y, X, X] [X] [X, Y] [X, Y, X] [Y, X, Y] [Y, X] [Y] [X, Y, Y] [Y, Y] [Y, Y, X] [Y, Y, Y] Конечные автоматы – еще не все Система управления памятью void* malloc(int n) void free(void* m) malloc(3); malloc(5); malloc(4); malloc(7); malloc(7); Конечные автоматы – еще не все Таймер выключить включить сигнал init(3) Барьер wait() wait() wait() Высота 3 Программные контракты Система – набор взаимодействующих компонентов Компоненты взаимодействуют с помощью вызовов интерфейсных операций друг друга Каждая операция имеет Предусловие Определяет, когда операцию можно вызывать Постусловие Описывает результаты работы операции Пример контракта Вычисление целочисленного логарифма int logn(int x, int b) pre (b > 1) & (x > 0) post blogn ≤ x & blogn+1 > x Что это дает для тестирования? Можно тестировать Постусловие дает критерий правильности Тест можно разбить на два компонента отдельные компоненты большие подсистемы систему в целом Генератор тестовых данных Оракул – проверяет правильность работы на любых данных Можно записывать сложные и недетерминированные ограничения Более сложный пример АРМ компоновщика заказов Показывает список заказов для обработки Позволяет Добавить новый заказ в конец списка Добавить срочный заказ в конец подсписка срочных заказов Пометить заказ как срочный Убрать первый заказ из списка (когда он обработан) Контракт добавления заказов List urgent; List ordinary; int MAX; boolean addOrdinary(Order o) { pre { return true; } post { if(@ordinary.size + @urgent.size < MAX) return ordinary == @( ordinary.add(o) ) && urgent == @urgent && addOrdinary == true ; else return ordinary == @ordinary && urgent == @urgent && addOrdinary == false ; } } Контракт переноса заказа в срочные boolean moveToUrgent(Order o) { pre { return true; } post { if(@( o in ordinary )) return ordinary == && urgent == && moveToUrgent == else return ordinary == && urgent == && moveToUrgent == } } @( ordinary.remove(o) ) @( urgent.add(o) ) true ; @ordinary @urgent false ; Контракт удаления заказа Order removeFirst() { pre { return urgent.size != 0 } post { if(urgent.size != 0) return ordinary && urgent && removeFirst else return ordinary && urgent && removeFirst } } || ordinary.size != 0 ; == @ordinary == @( urgent.removeFirst() ) == @( urgent[0] ) ; == @( ordinary.removeFirst() ) == @urgent == @( ordinary[0] ) ; Что получилось? true / false Тестируемая система Оракул Модель Что еще надо? Проверка правильности + Построение тестовых данных ? Оценка полноты набора тестов ? Полнота тестов Покрытие кода тестируемых функций Метод функциональных диаграмм Покрытие постусловия post { if ( a <= 0 || c.isActive() ) else if( a > 3 & !b.closed() ) … else … a > 0 && !c.isActive() && a <= 3 || a > 0 && !c.isActive() && b.closed() } … Покрытия контракта и кода Постусловие Код Если покрытие постусловия выше, то покрытие кода выше 100% постусловия и 100% кода не следуют друг из друга Метод функциональных диаграмм +: int × int → int int = {positive, 0, negative} Первый аргумент Второй аргумент Пример pos pos 2, 3 pos 0 2, 0 pos neg 2, -1 0 pos 0, 3 0 0 0, 0 0 neg 0, -1 neg pos -2, 3 neg 0 -2, 0 neg neg -2, -1 Метод функциональных диаграмм if(a > 0 && F(b) < 5) returna f > 0== g(a)-F(b)*b && a == @a + 1; f == g(a)-F(b)*b else if(isActive(c)) return f == -1 && a == @a; a == @a + 1 else F(b) < 5 return f == 0 && a == @a; f == -1 isActive(c) a == @a f == 0 Пример – ветви постусловий addOrdinary() ordinary.size + urgent.size ordinary.size + urgent.size addUrgent() ordinary.size + urgent.size ordinary.size + urgent.size moveToUrgent(o) ( o in ordinary ) !( o in ordinary ) removeFirst () urgent.size != 0 ordinary.size != 0 < MAX == MAX adOrN adOrE < MAX == MAX adUrN adUrE mvN mvE rmUr rmOr Обобщение состояний, шаг 1 Условия Состояние adOr adUr mv rm (0, 0) A B D A - (0, 0 < o < M) B B, C E, F E A, B (0, M) C C C F B (0 < u < M, 0) D E, F D, G D A, D (u, o) u+o < M E E, F E, F E B, E (u, o) u+o = M F F F F B, E (M,0) G G G E G Обобщение состояний, шаг 2 urgent.size addOrN MAX addUrN addOrE, addUrE mvN mvE rmUr 0 rmOr 0 MAX ordinary.size Редукция сложных автоматов Итоги Программные контракты Оракул Ветви постусловий – важные ситуации Построить обход переходов обобщенного автомата Подготовить тестовые данные (?) В чем цель тестирования? (Дейкстра) Тестирование не может подтвердить правильность, но может найти ошибки => (?) Цель тестирования – поиск ошибок <= Тестирование, не находящее ошибок – бесполезно В чем цель тестирования? А как бы мы иначе узнали, что ошибок в программе нет? Тестирование – это метод оценки качества системы Если ошибки есть, оно их должно находить Если их нет, оно должно это подтверждать Оно должно давать еще много информации Какие части системы наиболее устойчивы Где, вероятно, еще есть ошибки и какого рода Достаточно ли высоко качество системы в целом и стоит ли тратить усилия на доработку Модели – способ сделать эту оценку более надежной и объективной