Автотесты – барское дело

    Зачем разработчикам писать автотесты? С таким же успехом их можно заставить класть плитку или вести бухгалтерию. Не барское это дело! Или, все-таки, барское?





    Примечание: в статье под автотестами имеются ввиду не unit-тесты, а автоматизированные функциональные тесты, выполняющиеся на полностью развернутом продукте в окружении, максимально приближенном к боевому.

    В разработке программного обеспечения существует много мифов и глупостей. Рассмотрим два из них.

    Так уж сложилось, что тестировщики в индустрии в среднем зарабатывают меньше, чем разработчики. Происходит это, видимо, из-за того, что бытует мнение, что на роль тестировщиков можно нанять студентов по три рубля пучок, показать им куда надо «тыкать» — и все, команда обезьяно-тестеров готова. Это ерунда, конечно. Хороший тестировщик является по сути опытным хакером-педантом с не дюжим интеллектом и должен стоить дорого, но это почему-то мало кто понимает.

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

    Если объединить эти две глупости, то получится ситуация, когда автотесты отдаются на откуп команде тестирования, в результате у нас нет ни команды тестирования, так как она занята разработкой автотестов, ни автотестов, так как выяснится, что:
    1. Автотесты окажутся крайне дороги в разработке.
    2. Большое количество ложных срабатываний. Редко когда полный прогон автотестов завершается удачно, в результате сломанным атотестам никто не верит.
    3. Трудоемкий процесс анализа результатов прогона автотестов из-за большого количества ложных срабатываний и трудностей с диагностикой.
    4. Очень высокая хрупкость. Тесты постоянно ломаются даже после незначительных изменений в продукте. Из-за этого их нужно постоянно «допиливать».
    5. В конце концов, на автотесты забивают и стараются забыть этот позор.

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

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

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

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

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

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

    Разработка автотестов является у нас неотъемлемой частью общего процесса разработки продукта. При планировании итерации для каждой пользовательской истории мы обязательно закладываем время на подготовку автотестов для неё. Трудоемкость автотестов обычно составляет 10-20% от трудоемкости истории, но потраченное время потом многократно окупается.

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

    После этого происходит удивительное – программисты внезапно замечают, что автотесты экономят им кучу времени:
    1. При внесении очередного изменения в код продукта разработчик не тратит свое время на проверку того, что он ничего не поломал. Разработчик просто помещает код в репозиторий, а проверку делает автоматика.
    2. Работающий код гораздо легче модифицировать, чем неработающий. Автотесты подтверждают работоспособность продукта перед тем, как программист приступает к очередной задаче, что дает ему твердую почву под ногами.
    3. Известно, что чем раньше ошибка обнаружена, чем легче её локализовать и исправить. Автотесты во многих случаях помогают сократить время обнаружения ошибки до минимума, благодаря чему ошибка исправляется по горячим следам.
    4. Автотесты позволяют эффективно проводить рефакторинг кода, так как у программиста появляется возможность автоматически проверить корректность проведенного рефакторинга. Благодаря этому рефакторинг перестает быть хождением по минному полю и становится рутинной рабочей процедурой.


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

    Comments 43

      0
      Если вы покажете своим разработчикам полезность и целесообразность тестирования как основного способа разработки, а не просто как одной из частей процесса разработки, то все вопросы про барское дело отпадут само собой.
        0
        Спасибо, добавил примечание в начале статьи. Идея применить TDD к автотестам давно витает в воздухе, но пока не очень понятно, как её реализовать на практике..
          0
          Есть подходы BDD и Speс by Example. Есть Cucumber и подобные фреймворки, с помощью которых можно описывать требования (в виде фич и сценариев) и автоматизировать прогон тестов по этим требованиям.
            0
            На своей практике сталкивался даже с DSL для приемочных тестов на ruby. Это очень упрощало жизнь нашей QA команде, т.к. предметно-ориентированный язык скрывал все сложности загрузки данных, вызова функций проекта и проверки результатов.

            В нынешнем проекте мы все-таки перешли от велосипедов в функциональном тестировании, к более стандартным вещам. Gherkin подобный язык есть не только в Cucumber, но и в Jbehave. А jbehave субьективно удобнее, если если проект написан на java.

            Но еще интереснее дело обстоит с нефункциональными приемочными тестами. В этом случае для проверки NFR применяются кластерные фрейморки и jmeter, отдельная деплоймент инфраструктура и т.п.

            В случае интеграционных проектов отличные средства для тестирования есть в apache camel
        0
        Правильные вещи говорите, жаль заказчики не оценят всех этих трудов и не заложат на это денежек.
          +3
          Если тесты увеличивают стоимость разработки, а не уменьшают, то нафиг нужны такие тесты.
            +10
            Ну дешёвое ПО с багами оно конечно круче ;)
              +6
              Дело в том, что заказчику не нужно объяснять про тесты. Решение должно приниматься внутри команды: хотим ли мы разрабатывать говнософт, или не хотим.

              Чтобы не проиграть конкурентам, нужно доводить заказчику бюджеты: или 1) делаем (как «те дешёвые» парни) быстро, недорого без закладывания на будущее развитие, 20 млн. запуск и по 10 млн. существенные изменения логики. или 2) играем в долгую, делаем тщательно, 40 млн. запуск и по 5 млн. на изменение. Заказчик может сознательно выбрать первый вариант, он лучше знает свои планы. Может, он хочет быстро запуститься на прототипе, а потом вообще перейти на коробочную энтерпрайз-систему, а эту выбросить.
                +1
                Увы обычно бывает так — заказчик говорит " я хочу быстро за 20 ". Все делается быстро как заказчик и просил. А потом — " я теперь я хочу чтобы вы это дородотали чтобы изменения были по 5 млн " и это хорошо если проект как-то грамотно спроектирован, но обычно это не так ибо заказчик четко деклариловал что ему нужно «быстро». Они какбы могут отказываться понимать и брать ответственность за какой-либо выбор :)
                  0
                  Именно это я и имел ввиду. Есть знаменитая картинка, на которую заказчики обычно почему-то не обращают внимания
                    0
                    Частый случай: делайте побыстрее, потом отрефакторите. Потом в этом говнокоде копаться никому не интересно, а на переразработку с нуля заказчик не согласен. Что-ж, заказчику придётся всю жизнь пользоваться плохим продуктом. Защита только одна — ставить ценник выше и делать хорошо.
            +7
            Автотесты, приёмочные тесты, функциональные тесты (синонимы) — это всё не «метод улучшить ПО», а метод хоть как-то сдержать разваливающийся проект под наплывом большого числа разработчиков.

            Если проект может жить и развиваться без таких тестов — ок. Если нет — надо делать. Громоздить их до того, как «припекло» экономически нерентабельно, ибо написание хотя бы небольшого репрезентативного комплекта тестов сложнее, чем написание самого ПО. То есть это как с «преднапряжённым бетоном» в строительстве. Если ты можешь построить без его участия — не надо его использовать. Если уж не можешь никак иначе — тогда, да.
              +1
              Имхо есть простые end-to-end тесты, которые могут помочь контроллировать целостность приложения. Они не так сложны в написании и поддержке, но разные простые вещи типа «изменили формат X но забыли поменать его разбор в Y» могут проверить.
                0
                Большой Вам плюс. Есть классический треугольник тестирования image т.е, основа тестов должны быть unit tests, они самые дешевые и быстрее всего дают обратную связь; потом integration tests с иммитацией частей системы, они дороже, чаще падают от «вуду» проблем, но все еще достаточно дешевы чтобы часто использоваться; ну и наконец идут end to end тесты, которых должен быть самый минимум, проще говоря — они должны проверять, что система работает и можно делать самые базовые операции. Почему e2e тестов должно быть минимум? Потому что их ОЧЕНЬ дорого писать и они, по своей натуре, не стабильны (зависимость от внешних API, реальные денежные транзакции, ошибки распознования в таких иструментах как Selenium: все эти маленькие вероятности падений банально перемножаются). Грубо говоря, чем выше мы поднимаемся в этой иерархии, тем меньше конечное соотношение «цена к ценности».
                  +1
                  Предположу, что очень дорого писать первый тест и поддерживать инфраструктуру е2е тестов (отлавливать гейзен баги, разбираться, почему падают тесты).

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

                  Но мы кроем всю функциональность юнит-тестами. Для меня это нецелесообразно и затрудняет рефакторинг, потому что все основные юз кейсы кроют селениум тесты.
                  В своих проектах у меня есть только селениум тесты и интеграционные тесты апи за исключением border-cases. Например, надо проверить, что в апи, который выдает список сущностей параметр count не должен превышать 100 элементов, если пришло больше — юзаем значение 100. Для таких случаев пишется юнит-тест.
                  Результат: фактически покрыто 100% юз кейсов, свобода рефакторинга, меньше времени тратится на разработку (в том числе за счет написания малого количества тестов).
                    0
                    А в нашем проекте есть 3 сторонних API, плюс наш REST API который (скорее всего) будут использовать в догонку к UI. Поэтому шанс на поломку очень сильно возрастает. Плюс, из за любви к e2e тестам, в них так же проверяются всякие исключительные ситуации, что приводит к частым падениям и 2 часа полного прогона. Но мы папуасы и не пользовались Селениумом.
                    Но, за юнит тесты замолвлю слово. Юнит тесты объясняют как должен работать код. Написание юнит тестов приводит к лучшему пониманию что надо писать. И в юнит тестах очень дешево проверять потенциально скользкие ситуации (моким эксепшн, оттуда уже пляшем). К тому же, еще один огромный плюс юнит тестов — их (если писать правильно) можно запускать паралельно и моментально получать фидбэк (славься TeamCity pre-commit build), в отличии от e2e, где тесты привязаны к определенной среде.
                      0
                      Написание юнит тестов приводит к лучшему пониманию что надо писать

                      Имеете в виду качество кода? Имхо, после некоторой практики оптимальная структура кодовой базы сама пишется. :)

                      Юнит тесты объясняют как должен работать код

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

                        Конечно, если в команде 3 человека. А если 10 и многие из них новички + недавно с конкретным стаком технологий познакомились + они смертные, то увы. Много фич в узкие дедлайны приводят к говнокоду, а если дедлайн требует юнит тестов, то можно выторговать заодно пару часов на рефакторинг. Тем более что легче рефакторить, когда уже есть готовые тесты.

                        К тому же, если мудрить с самого начала с сложностью кода, есть шанс не угадать все (увы, мы не можем держать в голове все). Поэтому и есть TDD: пишем тест, пишем говнокод который его проходит, рефакторим говнокод — ведь уже есть тесты.

                        Это актуально, если модуль, который обложили юнит-тестами, выполняет какие то сложные вещи, ну например умные подсчеты.

                        Граница между сложными и простыми вещами очень размыта. Плюс требования меняются и модуль, который вчера был простым, уже сегодня стал с заковыркой.
                    +1
                    Проблема юнит-тестов в основании пирамиды: их много.
                    Хотя они и дешёвые, но всё равно имеют цену, надо поддерживать. При рефакторинге, изменении требований.
                    Один интеграционный тест заменит два десятка unit.
                  +1
                  Многое зависит от организации работы. Если выполняются штучные заказы, т.е. за небольшие деньги и нет возможности повторно использовать код, то тесты будут экономически не целесообразны (долго и дорого). Один черт заказчик примет и будет «ездить» на том что сделано. Если идет разработка продукта для широкого круга пользователей с большим временем жизни и желанием дожить до третьего релиза без переписывания всего, то тесты будут выгоднее, чем в предыдущем случае.
                    +1
                    Продуктовые разработчики звучат странно. Как продуктовые магазины.
                      0
                      Никогда не удавалось увидеть в проекте трудоемкость тестов в 10-20% от всей стори. Если бы это было так, никто бы никогда не спорил об их целесообразности, а просто делали бы и не парились. Мои субъективные наблюдения скорее приближают эту цифру ближе к 50% — половина код и половина тесты. Что не делает их менее важными.
                        0
                        У нас сейчас автотесты выглядят следующим образом: по коду продукта расставлены метки, которые генерируют специальные события для тестов. Тесты в этом случае представляют собой довольно простой скрипт, который анализирует корректность произошедших событий. В тесте формализуется ожидаемый «шаблон» событий (в простейшем случае — это просто линейная последовательность), и в случае расхождения с этим шаблоном, тест краснеет. Благодаря этому тесты дешевы в разработке и пригодны для legacy кода. Но это стало возможным только благодаря тому, что программисты могут модифицировать код и добавлять в него генерацию событий. Но это тема для отдельной статьи.
                          0
                          очень интересная тема, очень ждем статей :)
                            0
                            Самое интересно — как устроен запуск тестов.
                            Сложно ведь не проверить ассерты, а изолировать отдельные функции приложения, чтобы они могли выполняться независимо и без вмешательства оператора.
                              0
                              Хороший вопрос, это тоже будет описано в другой статье. Если коротко, то тесты делятся на два класса:
                              1) Интеграционные. Это трудоёмкие медленные тесты, но их очень мало. Они проверяют, что продукт может корректно общаться с внешним миром. Эти тесты не проверяют, что продукт реализует конкретный пункт ТЗ, они проверяют, например, что если продукт зачем-то захочет отправить email, то он сможет в принципе это сделать и письмо действительно будет отослано куда надо с корректной темой, телом и т.д.
                              2) Функциональные. Это уже тесты по ТЗ, их много, они дешевы, работают быстро, но они не совсем «честные». Тест с помощью специального тестового фасада инициирует некоторую операцию и, например, проверяет, что от продукта пришло событие, что он захотел отправить email с правильной темой, телом и получателем согласно ТЗ. При этом то, что письмо реально отправилось уже не проверяется. При этом тестовый фасад может опубликовать любой участок кода продукта, находящийся на любом уровне абстракции.
                                0
                                Ваши определения отличаются от общепринятых, насколько я понимаю. Принято называть интеграционными тестами быстрые тесты, в которых работает в сборе много кода, но другие части мокаются, а функциональными — честный полный запуск Application Under Test
                                  0
                                  Согласен, но общепринятых терминов для классификации функциональных тестов я не встречал, поэтому приходится выкручиваться. Если подскажете, буду благодарен.
                                    0
                                    Интересный вопрос. Я только что понял, что просто поменять местами определения у вас не получится =) Не назовешь тесты на отправку email функциональными никак.
                            +2
                            Для какого-нибудь стандартного enterprise (возьми отсюда, запиши сюда) это так.

                            Но если появляется изолированный кусочек со сколь либо сложной логикой, удобно запускать его через тесты. Всё равно программист будет дебажить свеженаписайнный код, и классически получается долго — запускать весь продукт (логиниться), доходить до места, вызывающего новый код, убеждаться, что в коде банальная опечатка, останавливать проект, исправлять, запускать заново, снова докликивать до нужного места. С тестом сразу попадаешь на отладку нового кода.
                              0
                              Для сложной логики есть юниттесты, мы ж не про них говорим сча.
                                0
                                Я думал про них, из исходного комментария непонятно.
                                Ну а так, да — только юнит-тесты могут ускорить фазу кодирования, остальные пишутся под готовый и уже отлаженный код.
                            0
                            В свое время был очень успешный опыт разработки проекта так: QA писали сценарии на Gherkin, а девелоперы их имплементили вместе с имплементацией фичи. Работало просто отлично. Команда из 15 девелоперов и 4 QA, деплоили проект на прод два раза в неделю. На подготовку релиза у команды QA уходило 2 часа (на ручное тестирование не покрытого автотестами функционала).
                              0
                              Очень правильная мысль: автотесты дело команды, а не заказчика. Точно так же как и стек технологий, фреймворк, воркфлоу и время митингов.

                              То есть, если уважающий себя разработчик пишет юнит-тесты на свой код, то уважающая себя команда пишет автотесты… либо просто не занимается проектом, если заказчик не может позволить себе эту команду.
                                0
                                Уважающая себя команда помогает бизнесу клиента, а не делает то, что ей нравится делать.
                                  0
                                  Может и о десятиминутных стендапах, ревью кода заказчика спрашивать — можно или нет их проводить? Ведь это трата времени не на реализацию фич, а на то, чтобы поболтать! Какая тут помощь бизнесу, прохлаждаться хотят…
                                    0
                                    «помогает бизнесу клиента» != «клиент одобряет»
                                      0
                                      Клиент приходит с проблемой, девелопер предлагает решение. Если эта проблема решается лучше без автотестов, но девелопер «уважает себя» и не хочет ее делать без автотестов, то это плохой девелопер. Не понимаю, откуда взялось «спрашивать у клиента».
                                        0
                                        Потом этот хороший девелопер сдаёт проект, в котором не то что тестов — классов нет, всё написано одним сплошным полотном. Имена переменных a, b, c, n1, n2, x, чтобы быстрее набирать. И говорит: «насяльника, я зе старался сделать тебе подешевле».
                                          0
                                          Почему когда начинаешь говорить про эффективность, сразу все передергивают, будто бы ты говорил про дешевизну независимо от качества?
                                            0
                                            Если проект небольшой, то 3-4 написанных теста погоды не сделают, но могут поднять мотивацию разработчика, если он любит писать тесты.
                                            Если тестов написано 100500, а проект небольшой, то навряд ли такой проект получит одобрение в проф. среде у коллег, поэтому делать заведомо глупые вещи никто не будет для «улучшения уважения».
                                            Если проект большой, то нужны тесты или нет, разработчик заранее не знает (и клиент ему не скажет). Поэтому нельзя предвидеть, как выгоднее бизнесу — писать или не писать.
                                          0
                                          Все эти best practices не на пустом месте родились. И для каждого правила есть исключение.
                                          Поэтому даже для простых проектов надо брать по-дефолту ревью, юнит тесты, и т.д. А потом стараться понять, что можно соптимизировать и нужно ли?

                                          Фраза «помогать бизнесу клиента» для меня означает только одно — спрашивать у него самого, что ему поможет или нет. Потому что только клиент знает, что поможет, а что нет его бизнесу. Если только бизнес не в айти, а программист — консультант.
                                        +1
                                        Вот именно! Проблема в том, что «бизнес» не эксперт, и на предложение построить фундамент под домом ответит вяло — давайте потом, сейчас на это времени нет.

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

                                    Only users with full accounts can post comments. Log in, please.