Существуют ли какие-либо формализованные / математические теории тестирования программного обеспечения?

12

Поиск в Google "теории тестирования программного обеспечения", по-видимому, дает теории только в мягком смысле этого слова; Я не смог найти ничего, что можно было бы классифицировать как теорию в математическом, информационном или каком-либо другом научном смысле.

Что я ищу, так это то, что формализует, что такое тестирование, используемые понятия, что такое тестовый пример, выполнимость тестирования чего-либо, практичность тестирования чего-либо, степень, в которой что-то должно быть проверено, формальное определение / объяснение покрытие кода и т. д.

ОБНОВЛЕНИЕ: Кроме того, я не уверен, интуитивно, о связи между формальной проверкой и тем, что я спросил, но есть определенная связь.

Эрик Каплун
источник
1
Тестирование программного обеспечения очень ценно (например, применение модульного тестирования на практике), но в его теории всегда будут некоторые дыры. Рассмотрим этот классический пример: double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }который я узнал от своего учителя математики . Этот код имеет ровно одну дыру , которая не может быть обнаружена автоматически при тестировании черного ящика. В математике нет такой дыры. В исчислении вы можете закрыть дыру, если односторонние ограничения равны.
Rwong
4
Это может быть частью того, что вы ищете - en.wikipedia.org/wiki/Formal_verification
enderland
1
Я второе предложение @ enderland. Неважно, насколько строг ваш подход к тестированию; некоторые ошибки все равно будут проскальзывать через трещины, и по мере того, как вы будете покрывать больше кода своими тестами, стоимость поиска новых ошибок будет расти. Вероятно, поэтому никто не сталкивался с проблемой формализации понятия тестирования - «эвристический» подход работает почти так же, как и при меньшем обучении.
Доваль
С тех пор я ознакомился со страной формальной проверки через зависимые типы, и я могу полностью согласиться с @Doval и enderland.
Эрик Каплун
1
@ rwong Полагаю, вы намекаете на то, что числитель и знаменатель равны нулю. Частично проблема связана с плохим дизайном этой функции. Говоря о математической формальной верификации, вам нужно составлять свои функции не произвольно, а в соответствии с формальными правилами, основанными на правильных типах данных. В этом примере вам нужно будет использовать функцию деления (a,b)=>a/b, которая должна быть расширена значением переполнения, чтобы быть правильно компонуемой.
Дмитрий Зайцев

Ответы:

8

Для книги, исследующей математику в тестировании программного обеспечения ... оригинальная книга, которую можно получить, - это Искусство анализа производительности компьютерных систем: методы экспериментального проектирования, измерения, моделирования и моделирования.

Хотя он был впервые опубликован в 1991 году, он все еще популярен сегодня, потому что это книга по прикладной математике, которая фокусируется исключительно на анализе производительности, моделировании и измерениях.

Северная Дакота
источник
5

Я не могу указать на хороший онлайн-ресурс (статьи в английской Википедии по этим темам, как правило, нереальны), но я могу подвести итог услышанной лекции, которая также охватывала основы теории тестирования.

Режимы тестирования

Существуют разные классы тестов, такие как юнит-тесты или интеграционные тесты . В модульном тесте утверждается, что согласованный кусок кода (функция, класс, модуль), взятый сам по себе, работает, как и ожидалось, тогда как интеграционный тест утверждает, что несколько таких кусков правильно работают вместе.

Тестовый пример - это известная среда, в которой исполняется часть кода, например, с использованием конкретного тестового ввода или путем насмешки над другими классами. Поведение кода затем сравнивается с ожидаемым поведением, например, с конкретным возвращаемым значением.

Тест может доказать только наличие ошибки, но не отсутствие всех ошибок. Тесты устанавливают верхнюю границу правильности программы.

Покрытие кода

Чтобы определить метрики покрытия кода, исходный код может быть преобразован в граф потока управления, где каждый узел содержит линейный сегмент кода. Управление передается между этими узлами только в конце каждого блока и всегда является условным (если условие, то переход к узлу A, иначе переход к узлу B). Граф имеет один начальный узел и один конечный узел.

  • На этом графике охват операторов - это отношение всех посещенных узлов ко всем узлам. Полное покрытие заявления недостаточно для тщательного тестирования.
  • Покрытие ветви - это отношение всех посещенных ребер между узлами в CFG ко всем ребрам. Это недостаточно проверяет петли.
  • Покрытие пути - это отношение всех посещенных путей ко всем путям, где путь - это любая последовательность ребер от начального до конечного узла. Проблема состоит в том, что с циклами может быть бесконечное число путей, поэтому полное покрытие пути практически невозможно проверить.

Поэтому часто полезно проверять покрытие условий .

  • В простом покрытии условий каждое атомарное условие когда-то истинно, а когда-то ложно, но это не гарантирует полное покрытие выписки.
  • При многократном покрытии условий атомные условия принимают все комбинации trueи false. Это подразумевает полное покрытие филиала, но довольно дорого. Программа может иметь дополнительные ограничения, которые исключают определенные комбинации. Этот метод хорош для получения покрытия ветви, может найти мертвый код, но не может найти ошибки, возникающие из-за неправильного состояния.
  • В минимальном множественном покрытии условий каждое атомарное и составное условие однажды имеет значение true и false. Это все еще подразумевает полное покрытие филиала. Это подмножество нескольких условий покрытия, но требует меньше тестовых случаев.

При построении тестового ввода с использованием покрытия условий следует учитывать короткое замыкание. Например,

function foo(A, B) {
  if (A && B) x()
  else        y()
}

должен быть проверен с foo(false, whatever), foo(true, false)и foo(true, true)для полного минимального покрытия нескольких условий.

Если у вас есть объекты, которые могут находиться в нескольких состояниях, то тестирование всех переходов состояний, аналогичных потокам управления, представляется целесообразным.

Есть несколько более сложных метрик покрытия, но они в целом похожи на метрики, представленные здесь.

Это методы тестирования белого ящика , которые могут быть частично автоматизированы. Обратите внимание, что набор модульных тестов должен стремиться обеспечить высокий охват кода любой выбранной метрикой, но 100% не всегда возможно. Особенно сложно протестировать обработку исключений, когда ошибки должны быть введены в определенные места.

Функциональные тесты

Затем существуют функциональные тесты, которые утверждают, что код придерживается спецификации, рассматривая реализацию как черный ящик. Такие тесты полезны как для модульных, так и для интеграционных тестов. Поскольку невозможно выполнить тестирование со всеми возможными входными данными (например, тестирование длины строки со всеми возможными строками), полезно сгруппировать входные данные (и выходные данные) в эквивалентные классы - если length("foo")это правильно, foo("bar")вероятно, будет работать также. Для каждой возможной комбинации между эквивалентными входными и выходными классами выбирается и тестируется как минимум один представительный вход.

Нужно дополнительно проверить

  • крайние случаи length(""), foo("x"), length(longer_than_INT_MAX),
  • значения, которые разрешены языком, но не контрактом функции length(null), и
  • возможные нежелательные данные length("null byte in \x00 the middle")...

Для чисел это означает тестирование 0, ±1, ±x, MAX, MIN, ±∞, NaN, а для сравнений с плавающей запятой - два соседних с плавающей точкой. В качестве еще одного дополнения, случайные значения теста могут быть выбраны из классов эквивалентности. Чтобы облегчить отладку, стоит записать использованное семя…

Нефункциональные тесты: нагрузочные тесты, стресс-тесты

К программному обеспечению предъявляются нефункциональные требования, которые также должны быть проверены. К ним относятся испытания на определенных границах (нагрузочные тесты) и за их пределами (стресс-тесты). Для компьютерной игры это может быть утверждение минимального количества кадров в секунду в нагрузочном тесте. Веб-сайт может быть подвергнут стресс-тестированию для определения времени отклика, когда серверы подвергаются удвоенному количеству посетителей. Такие тесты актуальны не только для целых систем, но и для отдельных объектов - как хеш-таблица ухудшается с миллионом записей?

Другие виды тестов - это тесты всей системы, в которых моделируются сценарии, или приемочные тесты, подтверждающие выполнение контракта на разработку.

Не тестирующие методы

Отзывы

Существуют не тестирующие методы, которые можно использовать для обеспечения качества. Примерами являются пошаговые руководства, официальные обзоры кода или парное программирование. Хотя некоторые детали могут быть автоматизированы (например, с помощью линтеров), они обычно требуют много времени. Однако обзоры кода опытными программистами имеют высокую скорость обнаружения ошибок и особенно ценны при разработке, где автоматическое тестирование невозможно.

Когда обзоры кода настолько хороши, почему мы до сих пор пишем тесты? Большим преимуществом наборов тестов является то, что они могут работать (в основном) автоматически и поэтому очень полезны для регрессионных тестов .

Формальная проверка

Формальная проверка идет и доказывает определенные свойства кода. Ручная проверка в основном жизнеспособна для критических частей, в меньшей степени для целых программ. Доказательства ставят нижнюю границу правильности программы. Доказательства могут быть в определенной степени автоматизированы, например, с помощью статической проверки типов.

Определенные инварианты могут быть явно проверены с помощью assertоператоров.


Все эти методы имеют свое место и дополняют друг друга. TDD пишет функциональные тесты заранее, но тесты могут оцениваться по их метрикам покрытия после реализации кода.

Написание тестируемого кода означает написание небольших блоков кода, которые можно тестировать отдельно (вспомогательные функции с подходящей степенью детализации, принцип единой ответственности). Чем меньше аргументов принимает каждая функция, тем лучше. Такой код также пригоден для вставки фиктивных объектов, например, путем внедрения зависимостей.

Амон
источник
2
Я ценю тщательно продуманный ответ, но боюсь, что он почти не имеет ничего общего с тем, что я просил :) Но, к счастью, programmers.stackexchange.com/questions/78675/… кажется настолько близким, насколько я понимаю, что я был стремиться к.
Эрик Каплун
Это отличные вещи. Можете ли вы порекомендовать какие-либо книги или вещи?
Марчин
4

Может быть, «тестирование на основе спецификаций» также как бы отвечает на ваш вопрос. Проверьте эти модули тестирования (которые я еще не использовал). Они требуют написания математического выражения для указания наборов тестовых значений, а не написания модульного теста с использованием выбранных отдельных значений данных.

Test :: Lectrotest

Как говорит автор, этот модуль Perl был вдохновлен модулем быстрой проверки Haskell . На этой странице есть еще ссылки, некоторые из которых неактивны.

KNB
источник
2

Один математический подход - тестирование всех пар . Идея состоит в том, что большинство ошибок активируется одним выбором параметров конфигурации, а большинство остальных активируется определенной парой параметров, выбранных одновременно. Таким образом, большинство можно поймать, протестировав «все пары». Математическое объяснение (с обобщениями) здесь:

Система AETG: подход к тестированию на основе комбинаторного дизайна

(есть еще много таких ссылок)

Дэвид Кетчесон
источник
2

Используются некоторые математические уравнения, но это зависит от типа тестирования программного обеспечения, которое вы используете. Например, допущение о критических неисправностях предполагает, что сбои вряд ли являются результатом двух или более одновременных неисправностей. Следующее уравнение: f = 4n + 1. f = функция, которая вычисляет количество тестов для данного числа переменных ( n) + 1 - это сложение константы, в которой все переменные принимают номинальное значение.

Другой тип тестирования , который требует математических уравнений является грубости Тестирование тестирует надежность, или правильность тестов в процессе тестирования. В этом тесте вы вводите переменные в допустимом диапазоне ввода (чистые тестовые случаи) и вводите переменные вне диапазона ввода (грязные тестовые случаи). Вы бы использовали следующее математическое уравнение: f = 6n + 1 . 6n указывает, что каждая переменная должна принимать 6 различных значений, в то время как другие значения принимают номинальное значение. * + 1 * представляет сложение константы 1.

Чарльз Лукас Шабазз
источник