Предисловие - почему пишется эта статья
Свой карьерный путь я провёл весьма неординарно по сравнению со множеством своих коллег. Начав всё же с Университета, я далеко не сразу попал в как таковой "бигтех", а долго перебивался всяким интересным. Я поработал на консалтеров в нескольких странах, на международного производителя оборудования, на стартап и даже на научное заведение. Не нахожу это чем-то выдающимся, но с отраслью у меня таким образом получилось познакомиться весьма всесторонне. Конечно, в каждом бизнесе из всех вышеперечисленных складывались какие-то уникальные особенности, характерные для здешних команд разработки, но одно ровняло большинство из них. Очень мало кто из разработчиков в сложившихся условиях занимался тестированием своего собственного кода, если занимался вообще. А даже там, где практика тестирования вроде бы была поставлена хорошо, очень часто прослеживались свои недостатки.
Постепенно я взял на себя роль этакого адвоката тестирования. Ведь на многих из этих мест бережное отношение к этому аспекту разработки прививал в результате именно я. Тут сразу начала прослеживаться ещё одна закономерность, а именно что от внедрения тестирования всегда появлялся вполне себе измеримый результат.
В связи с этим в какой-то момент у меня появился интерес узнать, что именно происходит с этим в сфере, а также как-то формально подытожить, почему, на мой взгляд, разработчики так редко пишут тесты на свой код, зачем это вообще нужно, а также как это можно улучшить.
Что происходит в отрасли
Начнём с наиболее интересного. Ежегодный опрос JetBrains DevEcosystem 2022 показывает, что 79% респондентов "видят тесты важной частью своего процесса разработки". Это число выросло на пару процентов по сравнению с прошлым годом. При этом 67% респондентов имеют unit тесты на своих проектах, 45% и 33% имеют интеграционные и E2E тесты соответственно. Not good, not terrible. Даже положительный тренд есть, да и цифры сами по себе неплохие. Выходит, что статистически в состоянии "всё плохо с тестами" находится лишь где-то каждый пятый бизнес. Внимательный же читатель, знакомый с теорией статистики и нюансами опросов, наверняка уже заметил тут следующие моменты.
Во-первых, речь идёт только лишь о тех разработчиках, кто знал об этом опроснике и пошёл на него отвечать. Такие люди могут являться несколько более активными в экосистеме разработчиками, что также отмечено в разделе о методологии. Отдельно можно отметить сравнительно молодую демографическую составляющую, хотя это также может быть реальным отображением общего состояния отрасли. Также, вкладка о тестировании нам говорит, что результаты показаны только от тех респондентов, кто "как-либо связан с тестированием в своей работе", в связи с чем согласились ответить на вопросы в этом разделе. Помимо разработчиков это также были архитекторы, team lead, product owner, либо вообще CTO, которые могут иметь несколько другое представление о состоянии кода, нежели чем более активно взаимодействующие с ним.
Во-вторых, хотелось бы отметить нечто, что мною было замечено в личном общении с некоторыми командами разработки. На прямые и простые вопрос "Есть ли у вас тесты?" либо же "Происходит ли у вас тестирование?" многие дают положительный ответ, но при знакомстве с кодовой базой вскрывается всякое. Тесты могут не поддерживаться и не писаться, не иметь какой-то чёткой структуры (не unit и не интеграционные, а что тестируют - не ясно) а также могут быть в принципе написаны неверно. Однажды я спросил специализированного тестировщика в одной компании, составляет ли он какую-то матрицу или план тестирования, прежде чем приступить к делу. На это я получил общий ответ вроде "Нет, я просто условно прокликиваю пользовательский интерфейс как посчитаю необходимым". О проблемах тестирования мы поговорим позже, но хочется отметить, что такое принципиальное непонимание тестирования как раз является, на мой взгляд, одной из причин его слабой распространённости и отказа многих команд от тестирования вообще.
Других хороших источников на данный повод я найти не смог, только лишь после некоторого поиска наткнулся на отчёт SmartBear State of Software Quality 2022. Там был обнаружён сильный перекос в сторону E2E тестов (57%) относительно unit тестов (50% или менее). Что вполне можно обосновать спецификой опроса - и проводившая компания является продуктовой относительно тестирования, и методология у нас другая, и аудитория по большей части состоит из QA инженеров (что нам на данный момент неинтересно), и выборка аж так вообще в примерно 20 раз меньше. Тем не менее даже там цифры коррелируют с моими ранними предположениями. Отдельно хочется отметить цифру в 80% пользующихся ручным тестированием, что нам понадобится в дальнейшем.
Зачем разработчику писать тесты
Сразу обозначим, что говорим мы о unit и о системных или сервисных тестах. На данном этапе стоит сразу отгородиться от возможной критики сказав лишь, что единого ответа на такой вопрос нет и возможно не будет. Тестирование является элементом процесса разработки, который может быть уникальным для каждой команды. Общие принципы я бы сформулировал следующим образом.
Потому что это уменьшает количество ошибок в коде
Этот момент является, пожалуй, самым очевидным - в коде с сопутствующими тестами обязательно будет меньше ошибок. Насколько меньше? Сомневаюсь, что на это есть какая-либо прикладная статистика - буду говорить из своего опыта.
Если я пишу "green field" (абсолютно новые фичи), то непременно каждый второй или третий кейс (фичу) я столкнусь с ситуацией, где тестирование помогает найти какие-то изъяны. Найденная проблема не всегда будет сиюминутным багом, но часто может таковым стать в обозримом будущем. Причём если оно станет реальной проблемой, то очень непросто диагностируемой.
Если я меняю текущий код - почти всегда я нахожу какие-то неточности или несоответствия, которые стоило бы починить перед релизом.
Отдельно хочу отметить следующее. Многие замечают, что для правильного внедрения unit тестов зачастую необходимо менять архитектуру приложения таким образом, чтобы она на более высоком уровне соответствовала практикам написания кода, в той частности Clean Code. Не считаю строгое соответствие Clean Code чем-то жизненно необходимым или единственно верным, но для некоторых такое может быть полезно при необходимости как-то более удобочитаемо структурировать уже имеющийся код, что может повысить его качество, а значит и уменьшить количество ошибок в нём.
Тем не менее, всё перечисленное выше на мой взгляд является далеко на самым важным. Если вы когда-либо видели громадную кодовую базу, то unit тесты в ней зачастую написаны даже не для этого.
Потому что так сложнее "сломать" уже имеющееся
Если бы мы написали код раз и о нём забыли, то первого пункта нам было бы достаточно.
Ранее я видел не раз подобные ситуации: один или несколько разработчиков спокойно работают над каким-то продуктом, при это очень часто забывая о качестве написанного. При этом как таковой продукт зачастую работает неплохо, не создавая особых проблем в плане поддержки. А если даже какие-то проблемы появляются, то упомянутые разработчики могут починить их сразу, как бы интуитивно догадываясь о местах их возникновения. Если же продукт начинает расти, часто начинается волна хайринга, в результате которой на проекте оказываются новые разработчики. Именно в данный момент очень часто случается резкое падение качества. Выражается это в количестве багов, фиксить которые команда не успевает прежде, чем они появляются.
Здесь можно увидеть хорошо известный пример с ростом проекта, описанный тем же Бобом Мартином. Увеличение числа разработчиков может вести к повышению ошибок при наличии плохой архитектуры. Я бы скорее отталкивался от наличия процесса тестирования. Ошибки в данном случае, как мы могли догадаться, возникают из-за того, что старый код перестаёт соответствовать поставленным перед ним требованиям. Это можно предотвратить, как мы уже догадались, наличием тестов, которые фиксировали бы данные требования к коду и не позволяли коду за них выходить по мере изменения.
Данный процесс валидации ещё иногда называется regression testing. Но в случае с написанием unit тестов regression testing у нас присутствует де-факто, потому упоминать его в дальнейшем давайте лучше не будем с целью не множить сущности.
Потому что так разработка становится быстрее
Это, пожалуй, не самый очевидный момент, вытекающий из предыдущего пункта. Ну и сразу скажем - имеется в виду, что больше времени будет уходить именно на разработку фичей, а не на поиск проблем. Как мы уже выяснили, увеличение штата разработки и в целом продукта ведёт к усложнению разработки, зачастую больше и больше времени уходит на закрытие открывшихся "дыр". Стало быть, имеет смысл понизить время необходимое на урегулирование путём инвестиции его в тестирование. Разумеется, поломка приложения приведёт и к другим потерям - репутационным, денежным, так далее, но хочется выделить здесь цену времени как наиболее близкую ответственному за приложение разработчику.
Потому что это понижает нагрузку на разработчика
В процессе анализа мне довелось наткнуться на ещё одно исследование помимо упомянутых выше, а именно Rollbar State of Code 2021. Чтобы не вдаваться в подробности, интересуют нас следующие цифры: порядка трети респондентов (31%) чувствуют недовольство при встрече с багами на их месте работы, каждый пятый (21%) чувствует себя ими перегруженным, а 7% так вообще из-за них готовы менять место работы. Желающий ознакомиться с отчётом найдёт в нём более внушительные цифры. Если же вынести из этого какой-то посыл, то это что работа с багами для разработчика является одной из наиболее стрессовых ситуаций. Как в рабочем и нерабочем времени для разработчика может материализоваться данный стресс мы оставим за рамками этого текста. Хочу лишь явно обозначить, что не стоит пренебрегать человеческой составляющей в разработке. Коллеги спасибо скажут, если им не придётся срочно срываться в свободное время, чтобы что-то там починить. А потому любая инвестиция в уменьшение числа проблем в кодовой базе есть инвестиция в эмоциональное здоровье в коллективе.
Потому что это помогает передать смысл написанного
Это скорее относится к функциональным тестам и всякого рода BDD спекам, но может в некоторой степени иметь место и на более низкоуровневых unit тестах. Бывает такое, что находишь в коде не до конца понятную конструкцию, а наличие конкретного теста объясняет, что тут поддерживается какой-то частный случай. Не будем останавливаться на этом долго, ведь BDD - отдельная тема для обсуждения.
Почему тестирование не приживается и есть ли причины отказаться от тестирования вовсе
Пойдём от обратного. Обсуждая с некоторыми разработчиками свой подход к тестированию, иногда я слышал их доводы и аргументы, почему они решили от тестирования отказаться. На данную тему сложно спорить продуктивно ввиду отсутствия какого-то измеримого базиса, от которого можно было бы отталкиваться. Впрочем, если что-то измеримое и есть, то оно в большинстве своём было перечислено выше. Я хотел бы остановиться на некоторых из услышанных мною за мою карьеру частных доводах, а также на найденных мною где-то в интернете, разобрать их, и объяснить моё отношение к ним.
"Нам проще и быстрее починить ошибки после их возникновения"
Мне довелось поработать с выбравшими такой вариант. Мол, мы потратим меньше времени, чиня баги, которые уже материализовались, нежели чем внедряя что-то хлопотное в процесс. Бывает так, что это правда - если, скажем, продукт небольшой, каждый ответственен за что-то своё, и все участники процесса коллективно способны держать весь код в памяти, а материализовавшиеся ошибки чинить уже потом. Такое может возникнуть в частных кейсах - какой-то математический софт или десктопное приложение, используемое узким кругом специалистов.
Если же ваш домен куда более "приземлённый", и развивается ваш продукт скорее по указанному ранее маршруту, то вам предстоит отлавливать ошибки после релиза, тратить время на ручное тестирование, настраивать и поддерживать систему мониторинга с целью обнаруживать ошибки после их появления. Что значит также взваливать часть процесса тестирования и на юзера тоже. Как показывает практика, в какой-то момент такой процесс может стать тяжёлым для некоторых членов команды.
"Мы попробовали и не увидели от этого пользы"
Возможно, есть кейсы, где unit или функциональное тестирование действительно будут неоправданы, хотя я таких встречал мало. Как показывает практика, если же коллектив отказался от тестирования уже после попытки его наладить, то, скорее всего, у них что-то не получилось. Я бы не стал обвинять такую команду в лени, ведь они чистосердечно попытались улучшить себе жизнь.
Основной же причиной, по которой у команд "не получается", я бы выделил плохую систематизацию знаний для разработчиков о правильном подходе к тестированию. Много литературы и курсов по тестированию можно найти отдельно для QA инженеров (что всё же немного отличается), а беглый поиск по литературе же именно для разработчиков показывает, что эта литературы либо посвящена ознакомлению с каким-то фреймворком через тесты, либо же стремится посвятить юзера в TDD и BDD, уже немного не то. В общем, смежная специальность хромает.
Если попробуем найти в интернете, как тестировать приложения, написанные на фреймворке Spring, мы найдём несколько диаметрально разных, но при этом официальных версий интеграционных тестов на данном фреймворке. Все они отличаются тем, что их тяжело настроить и поддерживать, а конечный продукт такого тестирования может получиться не до конца ясный автору. "Что именно я сейчас протестировал и какую это принесло пользу?". Думаю, именно на этом мы остановимся в следующий раз.
"Чем тестирование отличается от нашей системы мониторинга?"
Привожу это скорее как один из наиболее курьёзных случаев. В своё время данный вопрос немного поставил меня в ступор. Полагаю, что это можно отнести к диалогу выше. Чтобы не останавливаться, я укажу ,что одно помогает избавиться от проблем до их появления, а другое же помогает найти их быстро если они уже случились. В идеале же, разумеется, нужно иметь и то, и другое.
"Это приводит к увеличению абстракции в нашем коде, и как следствие увеличению самого кода без какого-либо смысла"
Как уже было упомянуто ранее, чтобы внедрить unit тесты иногда приходится видоизменять структуру с целью абстрагировать некоторые компоненты друг от друга, либо же просто поменять привычку письма. Такое приходится не каждому по душе, и некоторые впадают в обратную противоположность, а именно отказываются от следования стандартным паттернам проектирования принципиально. Как пример, наткнулся на следующую статью.
Дискуссия о надобности следования стандартным практикам является отдельным субъектом, и останавливаться на ней сейчас не имеет смысла. Но автор статьи справедливо отмечает, что для разработчиков свет не сошёлся клином только лишь на низкоуровневом unit тестировании, и при желании высокоуровневое тестирование также может быть внедрено с высокой степенью пользы. В любом случае, это полезнее ссоры с вашим коллективом на почве отношения к Фаулеру или Мартину.
К тому же не все экосистемы равно предрасположены одинаково к unit тестированию - Дэн Абрамов, известный по своей работе над React JS, также высказывается об их ненадобности в React.
Как разработчик может узнать о правильном тестировании?
Как показывает практика, у нас не совсем есть понимание о том, что в данном случае вообще правильно. Вот, например, 15 лет назад, когда отрасль была уже оформлена в её текущем на момент написания этой статьи состоянии, пишет примерно о том же Кент Бек, известный своим влиянием на современный процесс разработки и в т. ч. тестирования. Это, конечно, не значит, что писать тесты не стоит или что это бессмысленно, а означает лишь, что предприятия и отдельно взятые команды предпочитают налаживать процессы тестирования со своими уникальным особенностями. Ровно как это зачастую делается, например, и с Agile. Что не является проблемой.
О варианте написания тестовых кейсов, подведённом под научную базу, мне рассказали ещё в Университете на Магистратуре, когда большинство студентов уже не до конца были заинтересованы в слушании курса. А зря, ведь оказалось, что оно может быть полезно.
Отдельно здесь хотелось бы остановиться на следующем слуху. Кто-то мне однажды рассказал, что Колледжи и Университеты в некоторых западных странах не очень любят преподавать тестирование как предмет и не имеют программ или курсов для подготовки тестировщиков. А объяснилось это тем, что произведённые ими кадры являются для них продуктом, и если такие выпускники в реальном бизнесе начнут пропускать баги, то это может серьёзно сказаться на репутации данного ВУЗ-а.
В своё время воспринял подобную историю скорее как анекдот. Беглая проверка показала, что на курсах по Computer Science в Berkeley и MIT всё же осваиваются основы unit-тестирования. Правда, это всё же "Бакалавриат" (Ungergraduate) и не отдельный предмет. Насколько детально там изучается тестирование и как, по понятным причинам узнать я не могу.
Выводы
Если подытожить, я знаю и правда мало реальных случаев, где разработчикам имеет смысл отказаться от тестирования своего собственного кода. Хотя такие случаи есть - всё же на свои собственные скрипты я unit тесты не пишу, и хотел бы посмотреть на того, кто пишет. Вопрос как правило заключается скорее в том, как эта практика должна быть формально оформлена.
Здесь можно видеть, что данный текст попытался в очередной раз продемонстрировать нужду в тестировании, но так и не показал в деталях, как же это сделать правильно. Я действительно хочу написать о приглянувшихся мне процессах дабы использовать это как конспект в следующий раз как я буду с кем-то об этом дискутировать. Но я полагаю, что статья уже большая, потому хотел бы написать об этом отдельно в будущем.