суббота, 17 марта 2012 г.

Еще раз про Automation Bias: TDD, BDD и роющие осы

Вместо предисловия

Это Сфекс:

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

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

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

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

Увы, люди тоже порой уподобляются Сфексам.



А теперь про TDD и BDD

BDD - прекрасный, местами даже изумительный инструмент. Писать тесты человеческим языком это безумно круто, пока вы не наткнетесь на ограничения в выразительных средствах. Человеческий язык все же сильно богаче, чем имеющиеся фикстуры, сколь бы много вы их не написали. В итоге совершенно конские усилия начинают тратиться на написание фикстур для прикладывания усложненного, псевдонатурального языка тестов к настоящему API этих самых тестов. Брайан Марик нашел замечательнейший пример сложной конструкции в BDD:
Feature: Purchase Items in Cart

  Scenario: Using Existing Billing and Shipping Information
    
    Given I have an existing account
    And I have previously specified default payment options
    And I have previously specified default shipping options
    And I have an item in my shopping cart

    When I sign in to my account
    And I choose to check out

    Then I see my order summary
    And I see that my default payment options will be used
    And I see that my default shipping options will be used

Example taken from http://johnwilger.com/blog/2012/01/21/acceptance-and-integration-testing-with-kookaburra/
 Сложный, не очень читабельный тест, который в той же статье предлагают писать вот так:
describe "Purchase Items in Cart" do
  example "Using Existing Billing and Shipping Information" do
    given.existing_account(:my_account)
    given.default_payment_options_specified_for(:my_account)
    given.default_shipping_options_specified_for(:my_account)
    given.an_item_in_my_shopping_cart(:my_account)

    ui.sign_in(:my_account)
    ui.choose_to_check_out

    ui.order_summary.should be_visible
    ui.order_summary.payment_options.should be_account_default_options
    ui.order_summary.shipping_options.should be_account_default_options
  end
end

# Example taken from http://johnwilger.com/blog/2012/01/21/acceptance-and-integration-testing-with-kookaburra/
Уже больше похоже на unit тесты, больше укладывается в нормальный процесс работы того же программиста. Можно еще меньше маяться дурью и написать тест простым кодом. Да, его будет сложнее показывать заказчику/бизнесу, но и в сложных примерах приведенных выше он все равно нихрена не поймет, а лишних усилий они будут кушать много.

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

При этом BDD и TDD это не для того чтобы убедиться что что-то хорошо работает. Если вы все еще так думаете, то отмотайте вверх и прочитайте часть про Сфексов еще раз. "Примеры" из BDD это не тесты и не спецификация. Они неполны и далеко не всегда корректны для того, чтобы их так называть. Они, как и тесты в TDD, нужны для того, чтобы находить куски приложения, которые вам непонятны, которые сложно понять/сделать, в конце-концов куски, для которых банально нет достаточно информации для их реализации.

При этом существующая для TDD/BDD догма о 100% зеленых тестов демотивирует на написание и регулярный прогон негативных тестов, или тестов, результаты которых нам заранее неизвестны. Эти тесты могут быть очень эффективными и полезными, но у них мало шансов регулярно быть зелеными. То, что эти тесты не нужны в рамках TDD/BDD понятно - эти трехбуквенные комбинации нужны для того, чтобы воздействовать на разработчика как лампочка в эксперименте Павлова, а не для попыток выработать уверенность в разрабатываемом приложении, потому как эта уверенность подобна уверенности Сфекса, который по факту затаскивания в норку четырех кузнечиков спокойно обрек своих будущих детей на голодную смерть.

20 комментариев:

  1. Сергей, а почему все же написанный тест не должен стать в конце концов "зеленым"? Понятно, что в процессе разработки он будет разноцветным. Но после того, как история реализована, почему он не "зеленеет" и, после этого, не помогает верить в лучшее :) ?

    ОтветитьУдалить
  2. Потому что для того чтобы позеленеть тест должен уметь выдавать бинарный результат - pass/fail. Это накладывает серьезные ограничения на дизайн. У нас должен быть ожидаемый результат, мы должны уметь его интерпретировать.
    Я как-то писал скрипт который прогоняет через систему кучу данных, потом группирует пары ввод/вывод по разным характеристикам вывода. Я не знаю как, а главное зачем делать так чтобы такой скрипт выдавал результат вида pass/fail. При этом скрипт был весьма полезным и его нужно было прогонять на регулярной основе. Какие циферки там считать как pass а какие как fail я не знаю до сих пор.
    Да, такому скрипту не место в мире TDD/BDD, но он нуолезный. Да, не обязательно пихать его в кучу с юнит тестами, т.к. у них другая цель. Но и ограничивать дизайн тестов моделью с бинарным результатом тоже не стоит.

    ОтветитьУдалить
    Ответы
    1. Ну тогда это не тест. Что он проверяет? Это просто способ запустить систему и посмотреть, как она работает, что делает. При этом действительно сложно понять необходимость этого "теста". В чем была его полезность?

      Удалить
    2. Не очень понимаю как сочетать фразы "скрипт был весьма полезным" и "Какие циферки там считать как pass а какие как fail я не знаю до сих пор" Чем же он тогда полезен? Если есть некие граничные критерии, то почему бы их не указать в тесте, а если их нет то это и не тест...

      Удалить
    3. Могу предложить ознакомиться с вот этими работами: http://www.harryrobinson.net/
      Начать можно с Exploratory Test Automation slides from CAST 2010.
      В большинстве случаев у этих тестов полезный выхлоп это не pass/fail, а, например, 1.9 миллиона обнаруженных опечаток и 2000 подсказок, которые мы никак не смогли объяснить.
      В моем случае полезным было получаение знаний о качестве имеющейся в системе информации. Вынос оценочного суждения об этой информации при этом не является самоцелью - гораздо важнее дать рекомендации по улучшению и понять как это влияет на различные параметры работоспособности системы в целом. Так что даже если там и есть какие-то граничные значения, то их поиск и сведение через них результата до pass/fail это бесполезная работа, т.к. никакой ценности она не несет.

      Цель теста состоит не в получении pass/fail, а в получении информации о работе системы, о ее состоянии. При этом у вас наверняка не возникают вопросы касательно полезного выхлопа в тестах производительности, нагрузках или юзабилити. Ведь там, зачастую, простой pass/fail это ответ вида "она утонула". Но ту же функциональщину почему-то упорно пытаются загнать в рамки бинарной логики.

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

      Удалить
    5. Я не говорю что это невозможно сделать. Я вот, например, могу себе в ногу выстрелить. Но это будет глупо и опасно.
      Точно так же и с тестами - некоторые тесты не имеет смысла сводить к бинарному результату, а некоторые и вовсе опасно к нему сводить.

      Если брать пример Робинсона, то там задача не оставить количество опечаток в заданных рамках, а вообще от них избавиться. Т.е. фиксированное значение отличное от 0 это вредительство. А 0 не всегда возможно по независящим от нас обстоятельствам (это поисковик, он не контролирует поступающие данные), т.е. раз и навсегда от них избавиться не получится.

      С производительностью в заданных рамках все работает для коробочных продуктов - там вообще нагрузки/производительность это отдельная песня. Производительность в заданных рамках у онлайн-сервиса это несколько более сложный вопрос:
      - Вы должны быть быстрее конкурента.
      - Если вы медленнее конкурента, то у вас есть окно в 20%.
      - Одну и ту же страничку можно в заданные временные рамки подгружать совершенно по-разному. Например можно тупо выдать всю страницу на 100ms, а можно уже через 10ms начинать показывать активные элементы пользователю.
      - Вы не должны быть медленнее чем раньше.
      - Если вы медленнее чем раньше, то у вас все то же окно.
      - Если вы глобальный сервис, то вас очень интересует RTT, которое в разных точках мира может быть сильно разным до ваших ресурсов.
      ...
      Продолжать можно долго.

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


      В итоге возвращаемся к тому что я написал в посте:
      - Если у нас "100% зеленых" тестов то это еще не значит, что все хорошо.
      - Если у нас часто не "100% зеленых" тестов, то в TDD/BDD начинают возникать психологические моменты, вроде "доверия к тестам". Т.е. запихивание туда тестов изначально плохо приспособленных к выдаче бинарного результата весьма сомнительное занятие.
      - Отказываться от тестов плохо укладываемых в бинарную модель нельзя, т.к. они дают полезную информацию.

      Удалить
    6. По-моему мы говорим о разных вещах. В моем понимании автоматизация тестирования нужна в основном для регрессионного тестирования, которое позволяет быстро дать ответ, что продукт не стал хуже.

      Удалить
    7. Вам? Может быть.
      Только регрессионное тестирование (особенно автоматическое) не отвечает на вопрос "стал ли продукт хуже?". Оно отвечает на вопрос "продукт еще работает для заданного набора предположений?". Это разные вещи.

      Удалить
  3. Спасибо. Интересно. Получается так, как я и предположил - сбор сведений о системе.

    ОтветитьУдалить
  4. TDD и BDD относится к unit-тестированию, и причём тут сбор информации о работе сиситемы которой в понятии юните-тестов и нет ещё. К тому же надо определиться всё-таки тестирование или сбор информацмм. Потому что если TDD и BDD, то именно тестирование, потому что есть метод и у него есть назначение которое он выполняет или нет. Потому и результат бинарный.

    ОтветитьУдалить
    Ответы
    1. А почему TDD и BDD это про unit-тестирование? Вовсе нет. Это разработка через тесты, а тесты могут быть на любом уровне. Другое дело, что Сергей (как я это понял) предлагает использовать инструменты BDD (сценарии) для исследования написанного кода (ну или в процессе его написания).

      Удалить
    2. я к тому что это тесты функциональных блоков кода а не пользовательские тесты функциональности приложения, да и нет ещё приложения если разработка через тестирование

      Удалить
    3. Я предлагаю писать тесты за пределами парадигмы TDD/BDD, т.к. они не очень хорошо приспособлены "к неожиданным обстоятельствам и внезапным изменениям окружающей среды".

      Удалить
    4. > да и нет ещё приложения если разработка через тестирование

      Вот с этим совсем не согласен.

      Проблема тех же приемочных автоматизированных тестов в том же - они недалеко ушли от TDD/BDD и у них не сильно шире область применения.

      Удалить
    5. это не тестерские методологии, это методологии разработки. Чем пользоваться при разработке это отдельный разговор, но эти методологии предполагают использования с начала разработки приложения.
      У нас есть метод свободного поиска например. и пр. То для чего создаются сами тесты и какие они - это другой вопрос. Я не спорю что идея хорошая по поводу сбора информации.
      Цель не автоматизация, цель - проверить что всё работает максимально быстрым и удобным способом.

      Удалить
    6. TDD и BDD не показывают что все работает. Они показывают что заданный набор предположений корректен для данного приложений или нет. Тестирование тоже не показывает что все работает - это физически невозможно в большинстве случаев. К тому же демонстрация работоспособности это не единственный полезный выхлоп у тестирования.

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

      ЗЫ: А можно поподробнее про разницу между методологиями тестирования и методолгиями разработки? Exploratory testing это не методолгия, если что. Это подход. TDD тоже не методолгия - это процесс. BDD - техника для расширения TDD предложенная Дэном Нортом.

      Удалить
  5. BDD не очень относится к unit-тестированию. Сбор информации при том, что кроме unit-like тестов есть еще много интересных вещей и возможностей. Делать их никто не заставляет, но в целом подобные утверждения сродни утверждениям что есть только веб-программирование, например.

    Касательно сбор информации vs тестирование - это одни и те же вещи. Что такое тестирование производительности? Что такое тестирование usability? Сбор информации как он есть. Убивание всего этого в бинарный результат это как сведение всего программирования к веб-программированию.

    Конкретно TDD и BDD вне бинарных результатов существовать не могут и не должны. Это дает понять ограничения в их применимости вот и все.

    ОтветитьУдалить
  6. Ясно. Я слышала от тебя про сбор информации - это интересно и полезно, просто в статье речь идёт непосредственно про TDD и BDD.

    ОтветитьУдалить
    Ответы
    1. Да про TDD и BDD и ограничения в их применимости. По сути пример со Сфексом, на мой взгляд, хорошо их демонстрирует.

      Просто тот же Гарри Робинсон (на минуточку - тестовый архитектор Bing) предлагает использовать автоматические тесты которые делают много рутинной работы, собирают информацию и обрабатывают ее мало-мальски. С результатом работает человек. Они это называют human-assisted automation и, на мой взгляд, результаты впечатляющие. Одна проблема - этот подход к автоматизации сильно отличается от того что сейчас повсеместно практикуется для функциональных и прочих GUI-based тестов.

      Удалить