Про автоматизированное тестирование

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

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

А как вы тестируете свой код?

Разнообразия ради, я начну эту статью не с рассказа о том, как я сначала не писал тесты, потом писал, потом опять не писал, а потом наконец прозрел и снова начал писать. Я зайду с конца и начну со своего нынешнего понимания ситуации. Почему я сейчас стараюсь писать тесты на свой код? Нет, даже не так, не стараюсь — я люблю писать тесты на свой код! Серьезно, мне очень нравится возможность написать тест, я испытываю некоторое беспокойство, если я пишу код и не пишу тесты. Почему?

Все просто. Для начала давайте согласимся с одной простой истиной: код надо тестировать. Я не имею в виду непременно автоматизированное тестирование,1 это может быть старое доброе ручное тестирование. Написал программу, запустил, потыкал, нашел баг, поправил — о, теперь вроде работает. Если код не тестировать, то с вероятностью 99% в нем останутся баги.2 Если вы, как программист, не тестируете свой код, значит вы халтурщик и вас надо гнать из профессии. Нет, я серьезно, почему вы думаете, что можете вываливать на QA или, упаси боже, на пользователей непроверенное решение?

Если вы все еще со мной, я предположу, что вы согласились с тем, что программист должен тестировать свой код. Поймите меня правильно: я не утверждаю, что тестирование — единственный способ убедится, что код работает. Можно проводить анализ кода или даже пытаться доказать корректность. Просто опыт, по крайней мере мой, показывает, что тестирование — способ, который работает в 99% случаев и дает оптимальное соотношение цена/качество. Конечно, с опытом мы учимся писать более надежный код и заранее думать о возможных проблемах. Но все мы люди, и, какими бы опытными и матерыми кодерами мы бы ни были, мы все равно будем ошибаться, отвлекаться, забывать и пропускать. И в наших творениях неизбежно будут ошибки. И эти ошибки надо находить и устранять. Именно поэтому мы тестируем.

Давайте теперь подумаем, как именно мы тестируем код. Рассмотрим два примера из мира видеоигр.

  1. Сервис, который принимает результаты очередного матча и начисляет игрокам награды в соответствии с тем, как хорошо они показали себя в бою. Скажем, за первое место мы выдаем 1000 золотых монет, за второе место — 500 и т.д.
  2. Новая абилка, которая при активации наносит урон всем противникам в определенном радиусе и восстанавливает здоровье всем союзникам в том же радиусе.

Как мне проверить первый случай? Развернуть всю инфраструктуру, запустить клиент (или даже несколько, чтобы можно было сформировать многопользовательский матч), зайти на сервер, отыграть бой, убедится, что монеты начислены? Или, может быть, каким-нибудь Postman’ом запостить результаты матча, а потом кинуть запрос на получение имущества? Согласитесь, этот вариант звучит куда проще, хотя формирование правильных запросов может быть делом утомительным.

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

А вы ведь не забыли еще проверить, что врагов, находящихся за пределами радиуса действия, дамажить не должно? И что если в радиусе действия есть не только вражеские персонажи, но и автоматические турели, а также деревья и другие разрушаемые объекты, то система по-прежнему работает и ничего не падает? И что, если геймдизайнер прописал отрицательный радиус, то тоже ничего фатального не происходит? Ах, да, там ведь еще был пункт про восстановление здоровья союзников…

Не знаю как вас, но меня очень утомляет проверять все это вручную. И тут у нас есть два выхода из этой ситуации: легкий и правильный. Легкий — забить, скинуть все на QA, пускай разбираются. Найдут проблему — поправлю, не найдут — значит теперь это их косяк, ха-ха. А правильный — автоматизировать. Сделать так, чтобы все эти кейсы можно было удобненько проверить с минимальным количеством ручного труда.

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

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

Но ведь писать тесты сложно

Сложно, если вы не умеете это делать. Писать код в первый раз тоже очень сложно. Но вы же продолжали тренироваться, год за годом, потихоньку становилось все проще и проще, а код — все лучше и лучше, так? Почему вы думаете, что с тестами должно быть иначе? Да, конечно, если вы не имеете опыта в автоматизированном тестировании или, скажем, в TDD, вы будете делать это медленно и плохо. Заблуждение о том, что с тестами разработка стоит в 2-3 раза дороже обычно произрастает именно отсюда. Человек немного изучил вопрос, понял, что такое эти ваши юнит-тесты, попробовал пописать парочку и ужаснулся, как все медленно и неуклюже получается. Вывод понятный: тесты ок, если менеджеры готовы платить х3 времени за фичу. Менеджеры не готовы? Ну, значит идем без тестов.

Иногда находится достаточно убежденный и заряженный программист, который готов фигачить тесты тайком от менеджмента или еще как-то продавливать эту тему. Но тут его поджидает другая ловушка: тесты нужно проектировать. И этому надо учиться: не надо думать, что если вы уже пять лет как сеньор-помидор и умеете писать код по всем стандартам, то вы автоматом умеете писать тесты. Не умеете. Придется заложить время на обучение, придется смирится с тем, что поначалу будет медленно и придется много переделывать. Ну а когда вы, например, начинаете проект на незнакомом языке, не происходит ли того же самого? Если же тесты не проектировать, то вы придете к ситуации, когда для изменения внутрянок класса вам понадобится перефигачить все его тесты и еще вот эту километровую простыню настройки моков. Отсюда растет другое распространенное заблуждение: тесты затрудняют изменение кода. А раз они нас замедляют, то, может быть, не так уж они и полезны, может быть это все бред фанатиков? Уж теперь-то я точно знаю, сам проходил!

Но я скажу вам, по секрету, что если тесты сделаны правильно, то вы меняете внутрянки класса, не трогая при этом тесты. Потом прогоняете тесты и убеждаетесь, что ничего не сломали в процессе рефакторинга. А если вдруг сломали, то сразу чините, не оставляя подарочек для QA.

…и другие заблуждения

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

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

Заблуждение 1: с TDD не надо проектировать

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

Проектировать надо. Понимание того, с какого теста лучше начать, какой написать следующим, что следует замокать, а что протестировать интеграционно, приходит с опытом. Тесты не делают дизайн за вас. Более того, тесты добавляют еще одно требование к уравнению, которое вы решаете! Скажем, вы думаете, как бы выполнить бизнес-требования и обеспечить нормальную производительность. Или, если вы более опытный, думаете, как обеспечить бизнес-требования и сохранить простоту кода. А с тестами вы думаете обо всем этом, плюс о том, как сделать код тестируемым. Если вы работаете через TDD, то вы думаете еще и о том, как реализовать нужную функциональность постепенно, постоянно поддерживая код в рабочем состоянии.

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

Короткие итерации важны не только в тестировании, но в программировании в целом. Если вы умеете работать короткими итерациями, то вы, вероятно, умеете делать рефакторинг.5 Если вы умеете работать короткими итерациями, то вы, вероятно, умеете делать маленькие коммиты. Дробить задачи на подзадачи.

А если не умеете, то пишете код две недели, оставляя после себя грязевой след невнятных коммитов, а потом радостно сообщаете, что “осталось только отладить”. Ну, удачи.

Заблуждение 2: с тестами в моем коде будет меньше багов

У новичков в автоматическом тестировании бывает ложное впечатление, что код с тестами — это надежный, проверенный код. Хотя это и может быть правдой, само по себе наличие тестов не говорит ни о чем. Дело в том, что баги в коде оставляет программист и только он обладает суперспособностью убирать их оттуда. Делает он это с тестами или без — дело десятое. Вы можете иметь 100% покрытие для нерабочей системы, а можете иметь систему без тестов, которая работает в продакшене уже 10 лет и никаких проблем не имеет.

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

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

Заблуждение 3: тесты — не часть системы

Почему-то многие думают, что тесты — это что-то отдельное от их красивой и стройной системы, что это какие-то подпорки и обвязки, которые с легкостью можно убрать, дабы увидеть ее — Систему — во всей красе. А раз тесты — это что-то внешнее, чужеродное, то в них можно “хачить”. Можно копипастить, можно давать плохие имена, можно пихать кучу всего в один модуль. Это в корне неверное убеждение.

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

Почему? Это настолько очевидно, что я даже не знаю, требует ли это объяснения. Да просто потому, что вы будете постоянно читать эти тесты, вносить в них правки и добавлять новые тесты. Будете ли вы способы эффективно и быстро модифицировать некачественные тесты? Или вы не планируете сопровождать тесты? Если вы не планируете их сопровождать, значит планируете их выбросить. Зачем тогда вообще начинать?

Именно поэтому вам нужно относится к тестам точно так же, как к “основному” коду. Более того, я бы сказал, что качественные тесты важнее качественного “основного” кода. Потому что не очень качественный “основной” код, хорошо покрытый тестами, можно отрефакторить и превратить в качественный. Если вы что-то сломаете, тесты вам об этом скажут. А вот стройная система с плохими тестами рискует сначала очень быстро растерять все свои тесты, а потом приобрести гордое прозвище legacy.6

Как же писать тесты?

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

Определитесь со стратегией тестирования

Вы должны понимать, зачем вы вообще пишете тесты, какую задачу вы таким образом решаете. Не нужно писать тесты на класс просто потому, что это хороший тон или так положенно. Скажите себе, почему вы пишете тесты именно на этот класс? Что эти тесты проверяют, какую ценность проекту они принесут?

Существует очень много способов написать автоматизированные тесты на систему. Вы можете писать end-to-end-тесты на всю систему, тестировать отдельные сервисы как черные ящики внешними тестами, тестировать сервисы целиком внутренними тестами, тестировать сервисы без транспортного уровня (или без UI, если у вас десктопная разработка), тестировать отдельные компоненты внутри сервиса… Вроде бы очевидно, но об этом легко забыть, если у вас в голове живет идея, что для каждого класса MyClass в проекте должен быть класс MyClassTest.

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

Сохраняйте тесты быстрыми

От проекта к проекту “быстро” может запросто варьироваться на порядок и более: от десятков тестов в секунду до тысяч — все это может считаться быстрым. Главное, чтобы комплект тестов, который вы запускаете при работе, отрабатывал за несколько секунд. Чем меньше — тем лучше, но особо упарываться по этому поводу смысла не вижу. Все, что меньше 5 секунд — хорошо. До 10 секунд — жить можно, если больше, стоит задуматься. Почему я даю такой большой разброс по скорости? Да потому что количество и сложность тестов зависит от типа и архитектуры проекта. Если в вашем микросервисе несколько тысяч строк кода, вряд ли в нем будет очень много тестов. Так что даже при скромной скорости в 50 тестов в секунду мы получим 250 тестов за 5 секунд. Этого может быть более чем достаточно для проекта в несколько тысяч строк.

Самый важный момент, который вы, как инженер, должны понимать — это ваша забота спроектировать тесты таким образом, чтобы они были достаточно быстрыми.

Старайтесь делать тесты, стойкие к рефакторингу

На собеседованиях я люблю спрашивать программистов, как они справляются с ситуацией, когда при изменениях кода приходится переделывать тесты. Чаще всего я слышу, что, мол, да, надо переделывать тесты, что поделаешь — это цена, которую мы платим за тесты. Так вот, я не согласен платить такую цену. Делая так, мы упускаем очень крутую возможность тестов: помощь в рефакторинге кода. А легкий рефакторинг — это путь к хорошему коду. А хороший код — это путь к программерскому счастью, всеобщему благорастворению и шелковистым волосам.

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

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

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

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

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

Делайте тесты понятными

К коду тестов нужно относится как минимум так же строго, как и к “основному” коду. Выше я уже писал, почему это важно: потому что имея качественные тесты, вы можете отрефакторить систему, тесты вам помогут. А вот имея некачественные тесты, двигаться вперед весьма затруднительно. Возможно, вам знакома ситуация, когда стена тестов выглядит столь неприступной, что хочется их вообще не трогать? Когда вроде бы читаешь их, но не можешь толком понять, чем один тест отличается от другого, тут ведь почти все точно так же… Или просто просматриваешь список тестов на модуль и не видишь в нем никакой системы, не понимаешь, какие новые тесты нужно написать для новой функциональности.

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

Чтобы быть чуть более конкретным, вот вам немного типовых рефакторингов, хорошо работающих в тестах:

  • Перенесите общую инициализацию в setUp.
  • Разбейте набор тестов на несколько классов, возможно, с общим базовым классом с общим setUp’ом.
  • Выделите повторяющиеся значения в константы. Не пишите повсюду 42 и “user1”. Пишите ITEM_ID и USER_ID. Иногда вам может показаться, что нужно специально скармливать системе разнообразные входные данные, что не нужно переиспользовать одну и ту же константу. Но на самом деле это не сделает ваши тесты надежнее и не увеличит покрытие, разве что случайно. А мы не должны полагаться на случайности. Если бизнес логика подразумевает ветвление по CurrencyAmount >= Cost, подготовьте 2 случая: Cost и Cost – 1. Не надо делать Cost – 999, Cost + 10354 и черт возьми что еще просто на всякий случай, если только у вас нет убедительных доводов, почему это нужно сделать.
  • Выделите методы создания экземпляров тестовых объектов. В том числе создания SUT.8 Очень часто в тестах нужно подготавливать списки каких-нибудь объектов, пользователей, групп, предметов, чего угодно. Очень часто для этих объектов используется упрощенная форма конструирования, используются одни и те же значения полей, многие поля заполняется значениями по умолчанию. Сделайте отдельный метод для создания объектов. Не надо повторять new User(REGULAR, "user1"), new User(REGULAR, "user2"), вместо этого пишите user("user1"), user("user2"). То же самое применимо к SUT. Почти всегда заменить new на фабричный метод в тесте — хорошая идея, так вы отвязываете тест от деталей реализации.
  • Выделите повторяющиеся проверки в специализированные методы проверки. Вместо
    assertEqual(2, foundMatch.size)
    assertEqual(player1.id, foundMatch.getPlayer(0))
    ...
    пишите
    assertMatchConsistOf(foundMatch, player1, player2);
  • Освойте и используйте структурированные механизмы валидации и утверждений заместо базовых assert*. В Java это hamcrest с assertThat, в .NET’вском NUnit из коробки есть Assert.That, во многих других современных тест-фреймворках есть что-то подобное. Преимущество такого API не только в лучшей читаемости и куче заранее готовых операций (containsString, hasItem, etc.), но и в более понятных сообщениях об ошибках. Согласитесь, ошибка “expected true but was false” звучит слегка издевательски.

Не забывайте, что тесты должны проверять что-то релевантное

Очевидно в теории, на практике же с этим бывают сложности. Например, вы очень любите моки и считаете, что все “юниты” нужно тестировать в изоляции. И вот у вас есть система из кучи классов, есть куча тестов, проверяющих, что каждый класс вызывает какие-то другие классы определенным образом. Но какое отношение порядок вызовов имеет к решаемой задаче? Почему вы уверены, что такой порядок вызовов приводит к правильному ответу?

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

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

Заключение

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

Ну а под конец хочу повторить ключевые моменты.

  • Поймите, зачем вы пишете тесты и определитесь со стратегией. Если вы понятия не имеете, зачем вы это делаете,9 то просто возьмите чью-то готовую стратегию и следуйте ей до тех пор, пока понимание не появится.
  • Автоматизированные тесты — это тоже код, который нужно точно так же проектировать и держать в чистоте, как и “основной” код.
  • Вы можете делать тесты, которые мешают или помогают вам рефакторить код. Не ошибитесь с выбором.
  • Тесты замедляют разработку, только если вы еще не научились их писать. Дайте себе время на обучение и адаптацию.

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

Примечания

  1. Заметьте, до сих пор я еще ни разу не использовал слово юнит-тест. Делаю это намеренно: термин не очень удачный, поскольку все его понимают по-своему. Поэтому я говорю автоматизированные тесты, а уж как именно и к каким частям системы вы их подключаете — вопрос отдельный.
  2. Если тестировать, тоже останутся, но в куда более приемлемом количестве.
  3. Не уверен, можно ли освоить какое-нибудь дело на приличном уровне, не будучи рефлексивным человеком, но оставим это за скобками.
  4. Если вдруг будет изобретен, будьте уверены, вы узнаете в числе первых — вас непременно уволят.
  5. Я имею в виду настоящий рефакторинг, а не любое изменение кода, как некоторые используют это слово.
  6. Ну ведь во всех остальных сферах деятельности legacy — это что-то хорошее, что-то, чем мы гордимся, да?
  7. Больше и лучше об этом читайте у Роба Мартина.
  8. System under test — то есть тестируемый объект.
  9. Респект за честность.

Leave a Reply