пятница, 14 октября 2011 г.

Тестовые оракулы в автоматизации тестирования

"In testing, an oracle is a heuristic principle or mechanism by which we recognize a problem"
"The Cooking Detector", Майкл Болтон  

Основная причина выбора этой темы - то что автоматизация тестовых оракулов это половина пути к автоматизации исследовательского тестирования вообще и (FS)MBT в частности.

Собственно что это такое и с чем его варить?
Рассмотрим старую добрую IPO (input-process-output) модель. На выходе мы даем какие-то данные, сгенеренные волшебным образом (в контексте данного топика это не важно, тем более литературы по этому сильно больше чем по оракулам), а на выходе получаем какой-то результат который нужно интерпретировать.

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

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

Мы можем написать более сложный оракул, который будет проверять какой именно код ошибки вылез и в зависимости от входных данных будет определять правильный или не правильный результат теста и, соответственно, сообщать нам о провале или успехе.

Тут встает вторая проблема - а как мы поймем по входному значению какой именно код ошибки должен выйти?
Вариантов море:
  • Подавать на вход только те значения которые обязательно вызовут нужный нам код ошибки. Это частично можно приравнять к хардкоженным значениям и, по сути, хардкоженному оракулу. Такой вариант оракула очень не гибкий и по сути статичен, что не позволит его использовать для поиска багов, но он может послужить неплохой сигнализацией.
  • Мы можем статистически накопить себе информацию о том, как на определенных данных должна реагировать SUT. Т.е. подавать на вход в разных случаях какие-то классы значений, а на выходе получать код ошибки. Если для N предыдущих запусков выдавался один результат, а на N+1 получился другой - сигналим FAIL теста. Если N+1 результат по итогам ручной проверки оказался правильнее чем результат на N - правим ручками и фактически сводим к предыдущему примеру. Это тоже хороший способ сигнализировать об изменениях, немного чреватый ложными провалами (и ложными успехами, в случае, если у нас в первых прогонах были неизвестные нам ошибки), но зато покрывающий огромные объемы данных и чуть более гибкий чем предыдущий вариант (что расширяет возможности его использования). Как пример использования такого оракула можно привесим регрессионные тесты (и еще одна причина почему они не находят ошибки).
  • Мы можем независимо от SUT генерировать на основе входных данных ожидаемые результаты. Причем учитывая что мы пишем тесты, а не полноценный продукт - заморачиваться на оптимизацию алгоритмов генерации ожидаемого результата нужно только в том случае, если нам нужно прогнать много и быстро. Это сложный оракул, но он позволяет в теории обрабатывать огромные объемы данных и сам их сопоставлять. Как и любой сложный оракул он дополнительно вносит риск дефектов в его реализации. Но зато и отдача при правильном использовании такого оракула может быть просто огромной.
  • И еще 100500 вариантов
Самое важное тут то, что сам по себе механизм верификации результатов теста является логически совершенно отдельной сущностью. Его даже не обязательно включать в скрипт, который занимается прогоном выходных данных по SUT. Более того - мы можем сами результаты тестов (не бинарная логика вида PASS/FAIL, а реальные результаты, т.е. output) хранить где-то и уже постфактум прогонять по ним новые и новые оракулы. Мы можем честно запихать все это добро в механизм обхода (и генерации) модели SUT и получить себе полноценный MBT.

Само по себе логическое отделение оракулов от тестов (хотя в вырожденном случае наши тесты уже сами по себе являются оракулами) позволяет получить конструктор для тестов и просто более умные тесты (много оракулов для одного теста, например). Более того, оно делает оракулы тем чем они на самом деле являются - аналитическим инструментом для обработки результатов. И применимость того или иного оракула к тому или иному набору результатов зависит только от гибкости самого оракула. Правда это все еще будут тесты, просто там не будет processing части, но об этом позже.

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

Так, например, входными данными теста являются:
  1. Изначальное состояние SUT
  2. Состояние окружающей SUT среды
  3. Любые предусловия которые привели к данному состоянию или к дданному input'у
  4. Собственно input данные
  5. ...
  6. Придумайте еще 100500 вариантов (а они есть)
Выходные данные это:
  1. Конечное состояние SUT
  2. Состояние окружающей SUT сред после выполнения Process части теста
  3. Выходные данные
  4. Любые данные сгенерированные SUT, кроме тех, что мы ожидаем для данных входных данных
  5. ...
  6. Другое
Обработку данных так же можно разбить на части. И все это так или иначе может быть использовано в наших оракулах для получения более достоверной оценки и/или более интересных, с точки зрения тестирования, результатов.

Но мы можем пойти еще дальше и отказаться вообще от этой IPO-like модели. Например мы можем сосредоточиться только на output части и по ней определять что происходит успех или нет. Мониторинг серверов с автоопределением критичности/некритичности состояния это хороший пример таких оракулов. Собственно критерии по которым мы определяем хорошо или плохо серверу это и есть своего рода оракул.


Но создавая различные тесты и различные оракулы очень важно не забывать зачем они создаются и что они показывают. Более того - важно не вводить себя в заблуждение и не считать что тот или иной оракул нам показывает больше чем он показывает на самом деле. Надо помнить об их слабых местах и о цели с которой они были созданы. Ведь переоценка тех или иных тестов/оракулов и их последующая реализация в автоматизированном виде это самое что ни на есть настоящее мотовство. Более того - это потеря цели в той или иной активности. "А если нет цели, то нет будущего." (с) Кин-дза-дза

1 комментарий: