Unit-tests и извечное зло

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

Опять же, рассмотрение нужно начать с крайностей, их ровно две.

  1. Тесты не нужны. Я редко такое слышу и читаю, но тем не менее - для некоторых людей это неочевидно в принципе. Как правило, в подавляющем большинстве хипстерософта под линупс тестов вы не найдете, вообще никаких - но тем не менее оно как-то работает, и даже хорошо. Почему так? У меня есть опять-таки теория, состоящая из
  • один кусок кода правится одним человеком, который знает “как там чо унутре”
  • все это пишется один раз, и при необходимости изменений - переписывается нахер с нуля
  • философия “не работает - вот тебе топор и карта исходники и ебись сам чини”

Я, признаюсь, не знаю как точно работает процесс писания софта под линуксы на крестах и прочих высокоуровневых ассемблерах, но вот все вышеуказанные принципы я имел счастье наблюдать в полный рост, работая в одном небольшом инвестбанке. Когда там внутри были реально тонны кода. При этом людей, которые писали этот код, не было в досягаемости, а чтобы найти кого-то кто знает - надо было позвонить в Лондон, потом в Нью-Йорк, Бангалор и еще пару локаций. Вся эта канитель могла занимать реально неделю, а то и больше. И выхлоп был бычно - поменять строчку в CSV файле.

Как мне кажется, в опенсорсных коммунити примерна та же херня - ты или разбираешься сам, или долго срешься в maillinglist с мейнтейнерами и разной околоихней тусовкой, или тупо переписываешь все как там тебе понятно и потом срешься уже с апстримом.

  1. Тесты нужны в покрытии 100%.

Такое я видел в опять же банке, когда упоротый на DDD (Domain Driven Development) скрам-мастер на полном серьезе рассказывал всем о необходимости получения 100% покрытия тестами всего, включая сеттеры и hashCode/equals. Мое мнение по этому поводу - товарищи, обжегшиеся на первом случае (когда тестов нет вообще), начинают сходить с ума в другую крайность - тестами должно быть покрыто все. То есть вообще все. Отсюда рождаются устойчивые мемы типа “юнит-тест на 2+2=4”.

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

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

  • зеленый билд не значит ВООБЩЕ НИЧЕГО. Почему-то тестодрочеры считают, что если билд зеленый - то все океюшки и заебушечки, можно выкатывать в продакшн. Это далеко не так, пройденные тесты означают лишь что они прошли, не более того. В тестах может быть ошибка. Тесты могут не покрывать какие-то corner cases или даже primary flow. Тесты могут быть тупо закомментированы или @Ignore.

  • красный билд не значит что все плохо. Он означает лишь, что какие-то тесты не соответствуют функционалу. Может быть потому что они неправильные. Может быть потому что они obsolete. Может быть банально “четное количество багов” больше не выполняется (в коде и тестах были баги, которые исправляли друг друга, вы нашли и починили один - и все сломалось к хуям в другом месте). Да что угодно.

Отсюда вывод - может быть зеленый билд, который не рабочий в принципе. И может быть красный билд, который можно смело отдавать в продакшн. Бессмыслица.

  • тесты приносят пользу бизнесу. Это очень и очень спорное утверждение, которое на 100% всегда выгодно только одним товарищам - коучерам разнообразного Agile и прочих адептов пропаганды TDD головного мозга. В реальной жизни на тесты

  • тратится время на написание
  • тратится время на поддержку
  • они ничего не гарантируют, кроме ложно чувства самоуспокоения (у нас зеленый билд, валим домой)

Соответственно вот этот подход - все должно быт покрыто тестами на N% - он изначально ошибочен. Цифра N показывает только степень задроченности разработчиков, не более того.

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

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

Умные чуваки придумали AGDA и Coq для доказательства корректности программ, но не придумали как доказать корректность любой реальной программы. Другие умные чуваки придумали QuickCheck/ScalaCheck и прочие штуки для параметризированных тестов. Но к сожалению вот этот второй путь, хоть и работает ок - не более чем костыль.

Означает ли это все, что тесты не нужны? Скорее нет, чем да. Если планируется делать рефакторинг - то без тестов будет очень сложно понять, что ничего не отломалось по дороге. Если таковой рефакторинг не планируется, все отливается в граните и навсегда - то тесты скорее всего не нужны. Если в команде один упоротый разработчик - то тесты точно не будут нужны.

Резюмируя

  • тесты не дают value бизнесу
  • тесты изначально требуют затрат на написание и поддержку, а значит время на тестах можно просто списать в расходную часть
  • тесты не гарантируют автоматическую корректность программы для всех вариантов входящих данных
  • зеленые тесты указывают, что для некоторого набора входящих данных система как-то выдала ожидаемый набор исходящих данных. Или совершила нужные телодвижения, если мы о моках. Не более.
  • красные тесты могут указывать как на проблему в системе, так и на проблему с тестами. Равно как и зеленые.
  • сильные системы типов, например как в хаскеле, убирают необходимость писать тесты в 80% случаев
  • тесты не нужны там, где не нужен рефакторинг. Или код настолько простой, что 2+2=4
  • тесты нужны там, где вы точно знаете все corner cases и можете их описать (переполнение разрядности или там состояние пустой очереди).