вторник, 23 ноября 2010 г.

"Умная" автоматизация. Продолжаем дискотеку

"When people typically write their tests, they don't write down the model that generated the tests, they only write down the actions to perform"
(c) Harry Robinson


Продолжаем дискотеку

Слова:
SUT = System Under Test

Что у нас на входе обычного теста для SUT?

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

И что это значит?
Ну некто Harry Robinson приводит пример поиска в документах. Если мы берем заданные документы и прогоняем по ним тесты которые ищут заданные слова в этих документах, то мы в общем-то знаем только то что эти слова в этих документах ищутся. Все. Мы не уверены, что:
  • Эти слова найдутся в других документах
  • Другие слова найдутся в других документах
  • Другие слова найдутся в этих документах
Получается реально дофига. И все это дофига ручками покрыть будет сложно да и хардкоднутыми автотестами тоже заколебаться можно. Но нам никто не мешает автоматически генерировать документы и слова для поиска, например. Точно так же нам никто не мешает написать логику для определения результатов разных тестов и сценариев. В случае поиска по документу она вообще будет довольно простой. Так же в случае с тестированием не обязательно заморачиваться на агоритмы и прочая прочая. Это не боевая среда и мы можем себе позволить использовать простенькие алгоритмы которые будут относительно медленно принимать решения по результатам тестов. Это нормально, потому как нас в первую очередь интересует корректность, а не скорость.

Опять же мы можем сделать архитектуру тестов достаточно простой. Вынести все зависимые от среды вещи наружу и поставить их на зависимости от конфигов деплоя, например. А внутри оставить чистую логику.

Логеко?
Да, логика определения результатов. Не обязательно бинарный вывод (прошел/упал), можно сделать и более удобный прошел/выглядит подозрительно/упал. Мы можем сравнивать результат работы приложения с другим результатом (можем даже предсказывать этот результат). Мы точно так же можем сравнивать поведение приложения. Понятно что в случае когда результат предсказанный и результат действительный совпадают нам уже не очень интересно, т.к. тут все понятно. Но тут возникают ньюансы:
  • Ложные ошибки: ну действительно, иногда разница значений на выходе нам не важна.
  • Четность ошибок = все отлично: ошиблись в предсказании результата, ошиблась программа - тесты пишут что все ок (человек тоже так может ошибаться, да).
В итоге получаем что автоматизация тестов таким вот образом опирается в нашу способность формализовать распознание корректности/некорректности работы SUT.
Нам нужно чтобы логика всего этого добра обладала:
  • Полнотой информации (покрытие входных/выходных данных, покрытие функционала, достаточность, корректность обработки ошибок, покрытие среды и т.д. и т.п.)
  • Точностью своих выходных данных (арифметически и статистически сравнима с SUT, не зависела от SUT, размеры, типы ошибок и прочие тонкие места которые могут нам создавать искусственные ошибки)
  • Пригодностью для использования (легко и непринужденно использовалась, работала там же где и SUT и т.д. и т.п.)
  • Относительной легкостью в поддержке (нужно закладываться на изменения логики вслед за изменениями работы логики SUT, что накладывает определенные ограничения на ее сложность)
  • Сравнительной простотой в сравнении с SUT (опять же - нам важно покрытие функционала SUT и симуляция поведения SUT, но когда это становится едва ли не сложнее чем сама SUT - ну его нафиг такие тесты)
  • Временными отношениями с SUT (когда и как быстро генерировать входные данные и результаты, когда проверять)
  • Приемлемой стоимостью (цена промаха, цена ложной ошибки, цена реализации, ценя поддержки логики в актуальном состоянии и так далее)
В идеале логика сама по себе должна быть:
  • Полностью независимой
  • Полностью покрывать все аспекты SUT
  • Давать только корректные результаты
Но это что? Это фактически то же самое что написать SUT только без багов. То есть дорого и практически невозможно.

Тут начинаем обманывать.
Можно проверять на консистентность результатов. Будет работать только при уже имеющейся сравнительно серьезной базе, ну да фиг с ним. Что делаем?
  1. Берем предыдущие результаты
  2. Загоняем в систему
  3. Выносим приговор
  4. Заносим текущие результаты
Можно просто сравнивать с предыдущим и палиться на любые изменения. Можно вычислять статистически верное решение. Но дырки этого способа и так очевидны, хотя он и применим для некоторых областей в чистом виде.

Можно тупо включать во входные данные правильность результата. Это может быть просто сам результат или же как-той ключик. Вариантов много, но проблема одна - мы должны заранее задавать результат.

Можно написать модель поведения SUT, которая сама будет определять что такое хорошо, а что такое плохо. Это очень классный подход, про него много пишет Harry Robinson. Но так же он много пишет про оптимизацию этих моделей, про декомпозицию и т.п. Потому как есть серьезная проблема - как только у нас возникают очень сложные модели нам сразу становится сложно их поддерживать и начинает расти вероятность ошибки внутри самой модели, что так же плохо. Хотя и бонусы солидные - нам по сути нужно заниматься только поддержкой модели, потому как все тесты будут генерироваться из нее.

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

Вариантов много. Пока примемся за Robinson'а.

Комментариев нет:

Отправить комментарий