воскресенье, 15 января 2012 г.

Регрессионный рай и ад

Да, я знаю, что я про это уже писал. Теперь попробуем разобраться в проблеме получше.

Что такое регрессия, я думаю знает каждый, кто занимается тестированием.

Набор регрессионных тестов у многих тоже наполняется по стандартному сценарию:

  1. Написали и провели тесты для новой функциональности
  2. Сохранили все это добро где-нибудь
  3. Может быть нашли баги, написали тест для проверки фикса и тоже куда-то сохранили
  4. Как только что-то изменилось - повторили первые три пункта.
Вот такой бесконечный цикл.
Так как эта раковая опухоль имеет свойство бесконтрольно расти (выше, собственно, приведен алгоритм бесконтрольного роста), то иногда добавляется еще вот такой пункт:
  • Автоматизировать столько регрессионных тестов сколько сможем
Есть различные подходы к сокращению этого тестового набора, но итог всегда примерно один и тот же - у нас есть относительно большой набор регрессионных тестов которые кушают время, и мы их все время повторяем.

Почему так? Ну, например, потому что страшный сон тестировщика, когда над нами стоит разгневанное высокое начальство и говорит: "Это работало, а теперь не работает. Почему вы это не протестировали? Вы же должны заниматься регресионными тестами!". А он же проверил все места где случались изменения! Сделал умно и правильно! И случилась какая-то мистика и сломалось там, где не должно было ломаться!
И бедный тестировщик такой:
И просыпается.

И бедный тестировщик заводит старую шарманку, и начинает проверять вообще все. Ведь там есть риски!

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

Еще из неприятного - старые баги. Т.е. был когда-то баг, его починили, а он опять появился. В самых клинических случаях доходим до разработки через баги: "Баг №1 починен, но баг №2 опять проявился... Баг №2 починен, но почему-то опять появился баг №1...". И так до бесконечности, пока кто-нибудь не поймет, что в текущей реализации требования, породившие оба бага, взаимоисключающие. Если система очень сложная, то на это может уйти очень много времени. Но как риск такая "разработка через баги" как правило не рассмаривается. А зря.

Бывают еще фантомные баги. Или баги-ниндзя, как я их называю. Они прячутся в потаенных глубинах кода, которые не всегда реально используются, или за другими багами. Через "черный ящик" такие баги, как правило, ловить очень сложно, а порой и вовсе невозможно.

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

Риски солидные, бороть их хочется.

Как бороть?

Во-первых, риски принято предотвращать:
  • Для новых багов можно проводить ревью требований, приводящих к изменениям, и самих изменений. Если все будет делаться аккуратно, то ничего не сломается. Еще можно попытаться вникнуть вместе с разработчиками в то, что происходит. Попытаться помочь им осознать риски от того или иного решения. По мне, так это очень помогает уменьшить количество проблем в итоговом коде.
  • Старые баги, как правило, постоянно проявляются потому, что у приложения образовались свои минные поля, изменения на которых череповаты. Очевидные минные поля разработчики, как правило, сами прекрасно знают. Не очень очевидные можно находить через попытки отследить шаблоны в появлении старых багов. Обнаружение и локализация этих минных полей тестировщиком поможет остальной команде (или менеджеру) заниматься хоть каким-то менеджментом рисков. Если они/он не желают с этими рисками справляться, то остается только молиться и брести в слепую по минному полю. Иногда работает. Рефакторинг, как средство разминирования, сильно помогает сократить количетсво (если не свести к нулю) старых багов. Часто, правда, почему-то предпочитают убивать кучу времени на регулярную починку симптомов, т.е. старых багов, чем заниматься рефакторингом. Но это обычная организационная близорукость, когда менеджер/команда не способны оценивать проблемы в долгосрочной перспективе.
  • Ниндзя-баги предотвращать сложнее. Это, например, статический анализ для обнаружения мертвых или полу-мертвых кусков кода. Ну и предыдущие два пункта, как средства борьбы за стабилизацию тестируемого приложения, т.к. баги прятавшиеся за другими багами могут рассосаться сами по себе или расползтись по старым/новым багам.
  • Просто факапы лечатся просто люлями, например, ликвидацией технического долга. Т.е. удаление старых и плохих инструментов, выстраивание инфраструктуры и так далее. Все это нужно для того, чтобы сократить возможность исполнителей стрелять себе в ноги. Деплой руками с сорцов, и деплой через тот же deployinator это две большие разницы.
Как видно, во многом вопросы предотвращения проблем это вопросы процесса. Грубо говоря: "чем здоровее процесс - тем меньше проблем".

Во-вторых, мы можем проверять, что все работает как раньше:
  • Для обнаружения новых багов прогонять старые тесты, которые должны убедить нас, что все работает как и раньше.
  • Для старых багов прогонять тесты, которые проверяют что старый баг починен.
  • Для ниндзя-багов опять прогонять старые тесты, которые должны убедить нас, что все работает как и раньше.
  • Для простых факапов снова прогонять старые тесты. Ну или сокращенный их набор под названием smoke test set.
Вот она силища регрессионных тестов! Панацея!

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

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

Теперь у нас есть:
  1. В общих чертах риски, связанные с тем, что что-то изменили. Не бесформенная паранойя вида "всечтоугодноможетсломаться!", а мало-мальски оформленные риски.
  2. Стратегии для того чтобы эти риски бороть (предотвращать/проверять, что все как раньше/искать баги).
  3. Регрессионное тестирование, как повторение одних и тех же тестов раз за разом.
И как же регрессионное тестирование может помочь справиться с этими рисками?

Предотвратить? Не думаю. Регрессионные тесты работают с тем, что уже есть. Т.е. в плане предотвращения рисков тут полный провал.

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

Найти новые баги? Это второе больное место регрессионного тестирования, как стратегии:
  1. Для начала можно ознакомиться с работой Брайана Марика "How Many Bugs Do Regression Tests. Find?". Или у Болтона посмотреть, цифры примерно те же. В плане нахождения багов регрессионное тестирование не очень эффективно. Тесты уже по меньшей мере один раз прогонялись и большая часть багов, которые можно было обнаружить, скорее всего обнаружена и починена. Дальнейшее повторение много новых багов не найдет, а времени скушает порядочно, если проводить после каждого изменения. На этом можно закончить, но...
  2. При этом стоит помнить, что тестирование само по себе это сплошная проблема выборки. У нас бесконечный набор возможных тестов, и конечное время. Чем больше времени мы тратим на регрессионные тесты, тем меньше времени у нас на то, чтобы проводить другие тесты. А учитывая, что набор регрессионных тестов со временем совсем не сконен уменьшаться - времени на другие тесты будет все меньше и меньше. Нет, мы, конечно, можем нанять еще людей, но проблему это не решит.
  3. Автоматизация может не спасти. Автоматические тесты могут оказаться сильно сложнее и дороже ручных, в плане разработки и поддержки. Помимо этого, они совсем тупые.  Они находят только то, что сказано, тогда как человек может заметить что-то еще. В этом плане выхлоп от автоматических тестов в контексте нахождения новых багов плохой (как правило, больше находится багов в процессе написания автоматических тестов, чем через год-два их существования).
  4. Повторный прогон тестов на проверку фиксов багов во многом абсурден. Если баг починили и мы проверили, то все хорошо. Если старый фикс никуда не делся и никто его не менял и не трогал, то с чего бы этому багу опять появляться?
  5. Старые тесты пропускают старые баги. Тот самый "парадокс пестицида". Прогон регрессионных тестов руками тут слабо помогает. Старый добрый баг, пропущенный давным давно, может хорошенько вырасти и отожраться. Новые баги, кстати, тоже скорее всего не найдет, т.к. тесты разработаны под одну среду, которая много раз потом изменится.
Т.е. само по себе регрессионное тестирование с проблемами регрессионных же рисков справляется плохо (с другими рисками еще хуже).

И!..

Ответ на самый главный вопрос!

"Как правильно бороться с регрессионными рисками?"

Никак.

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

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

Что это значит для тестировщика?
Это значит, что нужно работать над тем, чтобы не возникало неприятных сюрпризов типа "Это работало, а теперь не работает. Почему вы это не протестировали? Вы же должны заниматься [*(&%*^%$##@%&*] тестами!".
Нужно чтобы менеджмент понимал, чем занимается тестирование, и какие у их подхода есть риски/проблемы. Чтобы он понимал как это можно лечить, и принимал решения о том, насколько это оправдано для проекта. Нужно чтобы тестовая стратегия не шла вразрез с направлением движения проекта, а двигалась вместе с ним. 
Каждый должен понимать свой маневр. Каждый должен понимать куда движется армия. И только в этом случае мы можем говорить, что стратегия тестирования оправдана. Даже если со стороны она выглядит совсем сумасшедшей, контекст многое может оправдать.

ЗЫ: Маленький конспект для Сирожы - регрессионное тестирование в вакууме полнейший мусор. Как калькированный процесс - еще и пустая трата времени в проекте. Как осмысленный и тщательно продуманный процесс - может быть оправдано контектом. Надо спорить и обсуждать.

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

  1. Отличная статья! Много правильных мыслей. Все же, не стоит опускать руки. Из моего опыта можно построить реальную пирамидку из тестов разных уровней (модульные, интеграционные, функциональные) и предотвратить проблемы регрессии. Главное со всем трепетом ее поддерживать и постоянно запускать. Ну и понятное дело как можно меньше оставлять на ручное тестирование.

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

    При этом у автоматических тестов есть ряд фундаментальных проблем, помимо того, что есть случаи, где построение хорошей пирамидки практически невозможно:
    1) Автоматические регрессионные тесты очень плохо находят новые баги (хуже чем ручные регрессионные, которые тоже мало находят). Можно заняться Model-Based тестированием, например, но это далеко не самая тривиальная активность. И у нее тоже есть проблемы (две ниже).
    2) Как недавно писал Dan North (JBehave он, кажется, написал?): "it's incredibly hard to test if something "looks right", and it's impossible to test whether it "feels right", using any automated system. But it is knowable.
    3) Написать автоматические тесты для проверки не бинарного результата (оракулы, ок) как правило очень сложно или невозможно.

    ОтветитьУдалить
    Ответы
    1. Зачем нужно это инородное "пенальти", когда есть отличное русское "одинадцатиметровый штрафной удар"

      Удалить
    2. Ну то, что вероятность найти новые баги регрессионными тестами низкая, это вполне очевидно. И задачи перед ними такой не ставится. Тут главное чтобы:

      - Каждый модуль кода работал в точности как был задуман, а не как реализован.
      - Каждый модуль кода взаимодействовал с другими так как это задумано, а не как получилось.
      - Объединение всех модулей делало то, что хотел заказчик.

      Речь конечно идет только об измеримых результатах. Практика показывает, что почти все можно измерить. Даже то, что большая часть аудитории считает невозможным. :)

      Вообщем, я за то, что большая часть задач по обеспечению качества посредством предварительного тестирования ложится на разработчиков. А тестировщики им в помощь.

      Удалить
    3. Любой софт решает какие-то проблемы. Если он не решает какие-то проблему, то он бесполезен. Если решает не ту - есть ньюансы. Если решает плохо - тоже.

      Поэтому любые проверки на то что все есть "как задумано" или "как хотел заказчик" предполагают, что все задумано и хочется как надо.

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

      ЗЫ: Количественный подход к качеству это плохо. Мерить все подряд тоже не стоит, т.к. любые измерения это субъективная штука, сильно зависимая от контекста.

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

    ОтветитьУдалить
  4. У меня "Это работало, а сейчас не работает" стоило компании очень дорого. А "Это только что сделали и оно не очень хорошее" стоило очень дёшево. Ты сам помнишь где. И, насколько я знаю, во всех аналогичных проектах ситуация схожа.

    ОтветитьУдалить
    Ответы
    1. "Это работало, а сейчас не работает" это риск. Насколько этот риск огромен - вопрос во многом к менеджеру.
      Откуда этот риск растет - не всегда однозначно. Борьба при помощи постоянных регресионных тестов руками через черный ящик (да и автоматически тоже) не самое лучшее средство как по сжираемым усилиям так и по времени (КПД плохое). К тому же оно имеет свойство конски удлинять процесс, т.е. во многих случаях контрпродуктивно само по себе.
      Комплекс мер, вроде хорошей пирамидки автоматических тестов, организационных изменений и т.д. и т.п., лучше, но не всегда возможен. Если нет возможности использовать эффективные меры (так бывает, да), то ничего плохого в неэффективных мерах нет. Надо просто осознавать риски, которые эти меры несут.

      Регрессионные циклы это зло. Местами необходимое, но с ним все равно надо бороться путем ввода более эффективных мер.

      ЗЫ: Круто, ветки!

      Удалить
    2. Ну КПД конечно считать стоит, но не встречал еще проекта, где автоматизация регрессионного тестирования сжирала больше усилий, чем приносила прибыли. А вот его отсутствие на моем веку много проектов довело до стадии "племя тестировщиков гоняет стада багов по бесконечной равнине". :)

      Удалить
    3. Я как правило чаще встречаю проекты, где такие вещи не считают в принципе. То есть попой чуют или на практтике проверили, что вообще без тестирования все плохо, а внятно объяснить почему и как - не могут. Панику и паранойю разводить проще. Посчитать во сколько выходят задержки в работе и скорлько эти задержки кушают - сильно сложнее.

      При этом хорошая команда разработчиков и/или менеджеров, имхо, может вообще обойтись без тестировщиков. Facebook и Twitter тому пример (качество софта довольно плохое, но им лучше и не требуется). Etsy пример чуть лучше, но тоже живут себе без выделенной роли тестировщика (просто сами очень дружно занимаются тестированием).

      Удалить
    4. Ну вот мы с вами и пришли к единому мнению. Я просто как раз такие команды всегда и стремлюсь построить. http://www.slideshare.net/alimenkou/development-without-testers-myth-or-real-option

      Удалить
    5. Ну, вариант вообще без тестировщика, как отдельного человека, сработает далеко не для любого проекта. В остальном да, хорошая калька. Правда к голой кальке я не очень хорошо отношщусь: http://goblingame.blogspot.com/2011/07/blog-post.html

      Удалить