Лекция 14.11.2001-21.11.2001
Тестирование и Формальные спецификации
Фазы разработки программного продукта:
· Определение требований(на этой фазе Формальные спецификации помогают)
· Эскизное проектирование(концепции и “наброски” по разработке, недоспецификации)
· Детальное проектирование(аксиомы, неявные спецификации, выработка пре и постусловий)
· Реверс-инженерия(цель этапа- извлечение знаний и понимание, что и как делает система )
Знания материализуются в мозгу у разработчика. Человек не может хранить и обозревать одновременно большие объёмы информации. Однако, свои знания должен хранить без искажений. Для этого и используются формальные спецификации(ФС)
· Документация пользователя (любой, кто будет читать ФС должен однозначно понимать/трактовать документацию )
· Документация сопровождения (описание интерфейсов взаимодействия между модулями системы и описание, дающее общее представление о работе системы)
· Тестирование (как проверить однозначность правильной работы системы? Надо знать, с чем сравнивать результат)
Можно сказать, что в этих вопросах использования ФС есть две задачи:
· Максимум - поддерживать все артефакты системы в согласованном состоянии
· Минимум - выделить минимальный набор тех артефактов, из которых следуют все остальные(некий базис).
Одного вида артефактов – недостаточно.
Задумаемся, какие артефакты нам обязательно нужны.
1. Определение требований
2. Необходима дополнительная информация для проверки требований.
3. Машинно-читаемые ФС в отличие от словесных спецификаций имеют машинную поддержку. Из ФС можно сгенерировать тесты.
4. Нельзя узнать из реализации, а верен ли результат, возвращаемый функцией. Нельзя проверить, а полной ли функциональностью обладает система. Например, программист не реализовал несколько функций, это можно узнать, лишь сверившись с ФС.
5. Врозможность проверить полноту системы.
Термины
Верификация - проверка соответствия процесса разработки требованиям к нему(а так ли мы ведем разработку, как нам предписано)
Валидация – проверка, что результат разработки ведет в соответствии с требованиями и ожиданиями пользователей.
Валидацию рассматриваем как характеристику продукта. Система делает то, что от нее требуется.
Тестирование- оценка качества программного обеспечивания(ПО) методом экспериментальной проверки, то есть путем исполнения тестов.
Нас интересует качество ПО, особенно функциональность.
Подходы к тестированию.
· “черный ящик”- строим тест без знания о внутренней структуре реализации системы. Неважно, как придумали тот или иной тест, но чтобы проверить, как хорошо он покрывает код, надо запустить профилировщик, и он покажет количество задействованных строк программы.
· “белый ящик”- наоборот, знаем детали реализации системы, и с учетом этого строим тесты, стараясь задействовать все ветви в коде.
Замечание. Критерии тестирования могут быть различными. Например, цель может состоять не в обходе всех ветвей, а в задействовании 75% операторов в программе. Все зависит, от метрики тестирования - критерия, согласно которому можно делать вывод об эффективности теста (обнаружение большого числа ошибок).[PP1]
· Функциональное тестирование- проверяем функциональность системы, но нас не интересуют такие критерии, как производительность и частота отказов. Возникает понятие доменного тестирования. (x, y,z)- тройка переменных, соответственно типов T1,T2,T3. Образовано пространство тестирования. В общем случае это небесконечномерное пространство, так как каждый из типов ограничен (это ограничение и есть домен), поэтому имеем куб в n-мерном пространстве. Фактически, ограничения типов разбивают области на подобласти. Задача тестирования может быть поставлена так: побывать в каждой области хотя бы один раз. Проблемы возникают в точках, лежащих на границах областей. Мы тестируем функцию, исходя из ее области определения. Это и есть доменный подход к тестированию
· Структурное тестирование - строим тесты, не для того, чтоб проверить действия операторов, а для проверки связей между большими частями системы(связи между модулями)
Виды тестирования:
· Сценарное
· Стрессовое(систему испытывают тем, что заставляют работать ее на пределе возможностей, обрабатывая большое количество информации или воспроизводя критические ситуации или даже одновременно несколько критических ситуаций)
· Модульное (тестируют взаимосвязи между модулиями, изолируя модули от их реального окружения системы)
· Интеграционное (тестирование, после того как система установлена на конкретную платформукогда в соответствии с некоторым планом[1], модули объединяются в подсистемы, каждая из которых тестируется отдельно, вне ее реального окружения)
· Временное (тестируются характеристики, связанные со временем поступления входных данных, наступления внешних и внутренних событий, временем отклика; важно для систем реального времени)
· Производительности (тестируются характеристики, связанные со скоростью обработки данных, пропускной способностью, количеством одновременно обслуживаемых запросов)
Разновидности объектов тестирования:
· API –(application program interface)
· Телекоммуникационный протокол


![]()
![]()

Например


Клиент Сервер
Программы взаимодействуют через протокол. Но я тестирую в таком случае больше, чем протокол, но мне мало при этом тестировать каждый компонент по отдельности. В таких случаях производится loop back – тестирование.


![]()

Машина протокол
Loop back
Тестирование на одной машине. Запуск теста на ней, через протокол результат опять на той же машине. Нагрузка ложится на тестирование именно протокола.
· Системы реального времени
· GUI – можно тестировать как самостоятельный вид, а можно свести к API
· ![]()
СУБД
SQL API – проверка обращений к серверу, правильно ли отвечает сервер
запросы
Специфика: сложность почти всегда не в интерфейсных функциях, а в структурах данных.
· Базы Данных - вопрос тестирования заполненной БД неисследован
· Компиляторы
![]()
Программа Компилятор Exe –файл
Что тестировать? Что исполняемая программа работает согласно ожиданиям, или компилятор тестировать?
Компиляторы, условно состоят из 3 больших подчастей
![]() | |
front end оптимизаторы кодогенераторы
на входе входов столько, сколько
разобранный оптимизаторов


текст (В gcc- до 70 входов)
задача тестирования более менее ясна для этих
частей
Сложности: описание языка программирования – десятки страниц сложного серьезного текста.
· Тестирование характеристик качества
Тестирование при отладке: показать, что ошибка в ПО есть. Уже задача разработчика – найти и устранить ошибку по тестам.
Фазы сдачи системы:
· Настоящая предсдача
· Настоящая сдача
· Модификация системы, влечет новое тестирование(регрессионное тестировние)
Partition Analysis (Доменный анализ)
Пространство данных разбивается на области – классы эквивалентности тестовых данных.
Возникает задача фильтрации/селекции - отбирать интересные тестовые наборы и пропускать их через целевую систему.
И, наконец, задача генерации множества данных(тестов).
При помощи тестирования нельзя найти все ошибки.
Кредо: Тесты должны строиться на систематической основе.
Плохих тестов в жизни не бывает, если, конечно, специально не придумывать заранее бесполезные.
Такой подход позволяет :
1. Оценить качество тестов
2. Наращивать тесты
3. Передавать процесс другим людям. Им легче будет вникать в процесс тестирования.
Даже тесты, написанные талантливыми программистами, но без системного подхода, окажутся неудачными.
Идея: Давайте тестировать ФС системы, а не ее реализацию!!!
1. Трудоемко тестировать реализацию. Гораздо проще тестировать ФС, так как она меньше.
2. От ФС ожидаем описание полной функциональности системы и надеемся увидеть меньше ошибок, чем при тестировании реализации(это не всегда так).
value f: Nat><NatàBool
f(a, b) is
if (a > b) then true
elseif 2*a > b then true
else false
end
Классы эквивалентности 1) a > b
2 * a > b
~(a > b) /\ ~(2 * a > b)
– Здесь имеем пересечение классов эквивалентности, чего допускать нельзя, так как не знаем, какой случай сработает в общей области.
2) a > b 1-ая область
~(a > b) /\ (2 * a > b) 2-ая область
~(a > b) /\ ~(2 * a > b) 3-яя область

b
![]()
![]()
3-яя max
![]() |
2-ая
1-ая
a
0
Замечание. Если существуют некие ограничения на a и b, то конфликты возникают в окрестностях граничных точек ( например, (0,0) ; (max a, max b))
Размер окрестности для проверки выбирается эмпирически.
Когда мы сгенерировали большое количество тестов, мы должны были сначала задуматься, а разумно мы это сделали? Можно предложить масштабируемость. Например, в окрестностях граничных точек сделать сетку с более мелким шагом, в противном случае выбрать шаг побольше. Это повысит эффективность создания тестов.
Пример.
if c1 \/ c2 then …
elseif c3 /\ c4 then …
else …
end
Классы эквивалентности
1 c1 \/ c2
2 ~(c1 \/ c2) /\ (c3 /\ c4)
3 ~(c1 \/ c2) /\ ~(c3 /\ c4)
Разделим первую область на подклассы: c1 , ~c1 /\ c2
Запишем FDNF для с1 \/ c2
с1 \/ c2 is c1 /\ c2 \/ (~c1 /\ c2) \/ (c1 /\ ~c2)
![]()
этого класса нет в подразбиении области 1!!!!
Он пропал? Фактически, разработчик допускал, что такая ситуация может быть невычислима.
Замечание. В RSL действует правило “короткой логики”
Пример. (i isin S) /\ (m(i)=2) если с1=true, то с2 – я не имею права вычислять. с1 с2
[1] Такой план называют «стратегией» тестирования.
[PP1] - надо бы переформулировать эту фразу.




