Комментарии 10
Юнит. Что это такое?Для меня это всегда функция (или функция-член).
Кстати здесь:
Связь результата со входными параметрами и есть суть функции.И вы тоже проговорились, что юнит — это функция. Или описались.
Явность юнитаИ тогда Subject — это имя функции.
Предикативность теста
Именно поэтому в качестве сказуемого предиката строго настрого запрещены такие паразитные слова как «correctly», «good», «fine», «well»...Хм… Я добавляю суффиксы Success, Fail, Throw, которые отражают ожидаемый результат. Т.е. иногда я пишу тесты на положительный результат, а иногда и на отрицательный. Для арифметических операций смысла в этом мало, но при написании тестов на нетривиальную бизнес-логику или на парсер (мой случай) это может быть полезно.
Состояние и юнитыНе осилил.
Когда мне нужно выполнить нечто подобное, то я делаю вот так.
Не говорю, что я делаю правильно, даже скорее хочу услышать критику.
Статья интересная, спасибо.
Я и не отрицаю, что в частном случае функция может служить примером юнита. Я рассматриваю этот случай в секции «Пустое окружение». Если вдуматься в этот пример, можно заметить, что пустое окружение в моей терминологии эквивалентно чистоте функции в функциональных языках. Т.е. вся семантика этого юнита заключена в проецировании входных данных на выходные. С недавнего времени я сам очень сильно полюбил чистые функции (особенно, после изучения Haskell), и да — немаловажную роль тут играет тестируемость таких чистых функций. Однако, не всегда код можно написать в виде чистых функций. Тот же Haskell имеет понятие IO, которое подразумевает некое состояние. Именно из-за состояния приходится рассматривать юнит как нечто общее нежели простая функция. Если вернуться к парадигме command query separation, то можно заметить, что эти разделенные команды и запросы, хоть и являются функциями, юнитами их назвать нельзя, так как из-за однонаправленности данных (в команды они входят, из запросов — выходят), проверить семантику юнита не представляется возможным; информация оседает в промежуточном хранилище, которое и является состоянием объекта. Именно из-за этого состояния нам приходится расширять понятие юнита до пары функций и общего их состояния. У меня в статье это пример std::list-а и несамодостаточности его однонаправленного (команды) метода remove.
Спасибо за статью! В этой профессии станет намного приятней и интересней работать, если больше программистов осознают необходимость писать читабельный и понятный не только автору код. Для этого нужно больше таких статей, объясняющих что такое читабельный код и как этого добиться. К сожалению пока новый (для меня) проект — по умолчанию авгиевы конюшни и несколько недель чтобы разобраться во всех шарадах и переписать хотя бы самые ужасные участки.
Рад, что понравилось. Лично я вижу в грамотном именовании 80% работы проектировщика и всячески содействую распространению кода, наиболее приближенного к человеческому языку. В ближайшее время собираюсь выложить свои мысли насчет классификации программистов по отношению к именованию и технологической ориентации.
Это ж не habr.com/ru/post/187568 было? Выкладывалось?
Явность тестовых данных
Правило: Все тестовые данные (как входные, так и выходные), ровно как и данные читаемые из окружения или пишущиеся в окружение, должны присутствовать в теле теста.
Если я правильно понял что тут написано, то если у меня в качестве входных данных например огромный многострочный текстовый дамп который годится для нескольких тестов, мне его всё равно копипастить в тело каждого теста? Это сомнительное правило на мой взгляд.
Правило: Все тестовые данные (как входные, так и выходные), ровно как и данные читаемые из окружения или пишущиеся в окружение, должны присутствовать в теле теста.
Если я правильно понял что тут написано, то если у меня в качестве входных данных например огромный многострочный текстовый дамп который годится для нескольких тестов, мне его всё равно копипастить в тело каждого теста? Это сомнительное правило на мой взгляд.
Если входные данные огромные (или даже просто немаленькие), то данный тест — это уже не юнит тест. Это может называться интеграционным тестом, регрессионным тестом или даже автотестом. Как правило, юнит тесты, по крайней мере те, которые написаны в стиле TDD, проверяют маленький кусок функциональности, на который достаточно повлиять парой-тройкой (если вспомнить закон триангуляции) высокогранулированных значений. Большие объемы данных чаще всего пригождаются чтобы прогнать систему или некоторую ее подсистему через серию тест-кейзов для нагрузочного тестирования или просто как некоторые чекпойнты. А это уже что-то более высокомасштабное.
Извиняюсь — огромный ошибочно написал (он огромный визуально по сравнению с численной константой, надо было уточнить что я имел в виду). Я конечно имел в виду небольшой 10-20-ти строчный текстовый дамп не более 80 символов в строку. Так вот я не уверен что копипастить такой кусок в несколько тестов это правильно.
Копипастить данные — неправильно, с этим соглашусь. А вот к необходимости использовать одни и те же данные в разных тестах отнесусь скептически. Если одни и те же данные подаются на вход разным тестам, то возможен один из следующих вариантов:
Все зависит от цели, с которой пишется юнит тест. Если цель — иметь защиту от регрессии и возможность спокойно рефакторить, то уж лучше иметь хоть какие-то тестовые данные, и если возникает необходимость их дублировать в разных тестах, да, лучше выносить, понижая выразительность тестов и не устраняя недостаток низкой повторной используемости юнита (т.е. нарушить обсуждаемое правило Явность тестовых данных). Если же цель проверять не только поведение юнита, но и за счет лаконичности теста удостоверяться в хорошем дизайне продакшен кода, то следует стремиться делать тесты короткими, причем не за счет косвенности данных, а за счет малых объемов получаемых данных в самих юнитах. В конечном итоге, тест — это показательный клиент нашего кода. Чем сложнее нам вызывать код нашего юнита, тем сложнее будет всем будущим клиентам. В этом одно из очарований TDD — проецируй будущих клиентов на себя самого.
- Данные используются тестами неполностью, и каждый выгребает из этого множества то, что интересно именно ему. В данном случае выразительность теста понижается, потому что непонятно, какие именно данные влияют на проверяемое поведение.
- Данные нужны обоим тестам полностью. В данном случае налицо дублирование ответственности между разными юнитами, либо, если проверяются разные аспекты одного и того же юнита, то имеет место быть слишком сложная конфигурация юнита для получения простого функционала. Это говорит о низком реюзабилити (повторной используемости — так оно переводится?) юнита.
Все зависит от цели, с которой пишется юнит тест. Если цель — иметь защиту от регрессии и возможность спокойно рефакторить, то уж лучше иметь хоть какие-то тестовые данные, и если возникает необходимость их дублировать в разных тестах, да, лучше выносить, понижая выразительность тестов и не устраняя недостаток низкой повторной используемости юнита (т.е. нарушить обсуждаемое правило Явность тестовых данных). Если же цель проверять не только поведение юнита, но и за счет лаконичности теста удостоверяться в хорошем дизайне продакшен кода, то следует стремиться делать тесты короткими, причем не за счет косвенности данных, а за счет малых объемов получаемых данных в самих юнитах. В конечном итоге, тест — это показательный клиент нашего кода. Чем сложнее нам вызывать код нашего юнита, тем сложнее будет всем будущим клиентам. В этом одно из очарований TDD — проецируй будущих клиентов на себя самого.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Читабельный тест