Pull to refresh

Comments 43

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

Чревато не заметить переход функции от тривиальной к сложной. Вроде функция тривиальна, сделали тривиальное изменение, и вот она уже не тривиальна и косвенно не факт, что получится проверить. Лучше, по-моему, подсуетиться и написать триальный тест сразу.

Аналогично с сеттерами и геттерами. Простейший тест $obj->setProperty(1); $this->assertEquals(1, $obj->getProperty()); написать не сложно (причем он тестирует сразу и геттер и сеттер), зато не забудешь сделать тест, когда решишь, например, в сеттере логирование сделать или исключения бросать для невалидных значений.
Я тоже считаю, что в написании тестов для геттеров и сеттеров есть смысл, ибо тесты — это документация. Тот тривиальный тест, который я сейчас напишу на геттер или сеттер явным образом укажет, что именно такое поведение ожидается от этих геттеров и сеттеров. Если не написать такой\такие тест\тесты, то пришедший на смену меня программист изменит их поведение, усложнив их, и тесты могут и не провалиться (которые, якобы, должны их косвенно тестировать).
В TDD прежде, чем изменять что-то, у вас должен быть сломанный тест и ситуаций «могут и не провалиться» не должно быть.
А если на геттер вы не напишите теста? Теста нет. Откуда вы знаете, что следующий разраб будет писать тест на этот геттер, прежде чем изменит его? Другой разраб может вообще не практиковать TDD или не любить его, но лично моя задача быть профессионалом и передать код хорошего качества, который не может быть поломан на раз каким-нибудь нерадивым программистом. Мои тесты хотя бы должны будут ему указать на то, что не всё так просто как ему кажется.
>>подсуетиться и написать триальный тест сразу.
Чтобы утонуть в громадном кол-ве тривиальных вещей? ;) Задача разработчика не выдать абсолютно безбажный код, его задача в другом в достаточно быстрой правке кода. Если тестировщик нашел багу, он может определить место и если нет юнит-теста, то написать его. Но думать о каждом месте кода: «А вдруг здесь будет бага?» это утопия.

>>Простейший тест $obj->setProperty(1); $this->assertEquals(1, $obj->getProperty())
На мой взгляд геттеры и сеттеры не нужно тестировать. Попробую пояснить мысль.
Разрабатывая продукт объектно ориентированно мы пишем классы, но что такое «классы» мы часто забываем. Прошу простить что напоминаю. Классы это «абстрактный тип данных, т.е. набор данных и действий с ними». Если мы пишем функцию в виде:

def my_super_cool_action():
  value1 = obj.get_value1()
  value2 = obj.get_value2()
  ...
  valueN = obj.get_valueN()
# делаем что-нибудь с этими value1,...,valueN


Если мы написали такой код, то мы написали его не в ОО-виде! Мы неправильно написали метод, мы должны переместить метод в область класса. После этого перемещения мы можем написать более простой тест-код:

def test_my_feature():
   obj.my_super_cool_action()


Более того необходимость в геттерах отпадает и тестировать их не нужно.
К сожалению, геттеры/сеттеры используются о OR-механизмах представления состояния объектов в СУБД, например. Валидность переданных значений в сеттерах можно поймать на объектном уровне, а не на уровне СУБД, использую ассерты.
Ну геттеры\сеттеры тоже бывают разные:
Вариант №1:
def get_value(self):
   return self._internal_field


Вариант №2:
def get_value(self):
   external_api_method1( constant1 )
   return external_api_method2()


Второй вариант это на мой взгляд правильный геттер, т.к. он действительно избавляет пользовательский код от некоторой сложности. Как вывод первый тип геттеров не имеет смысла, а второй очень даже нужно.
Первый вариант геттера нужен, чтобы безболезненно перейти ко второму, не меняя код всего проекта или не вводя магию. Как вариант — если в классе есть геттеры второго типа, то просто ради единообразия интерфейса можно ввести на другие поля геттеры первого типа.
>>Первый вариант геттера нужен, чтобы безболезненно перейти ко второму
Так я и не говорил, что он не нужен. А говорил, что тестировать его не нужно! Он будет вызван при работе других методов. Которые как правило уже более устоявшиеся. Любой такой геттер-аксессор это не оправданое ломание юнит-тестов. А потом сами же и начинаем ныть и скулить «Эти юнит-тесты сложно сопровождать». Просто не надо тестить то что можно тестировать косвенно.
Любой такой геттер-аксессор это не оправданое ломание юнит-тестов.

Нет, это точная локализация регресии. При косвенном тесте мы точно не можем знать что сломалось — сеттер или тестируемый метод. А если вместе с «главным» упавшим тестом, упадет и тест на акцессоры, то проблема ясна.
>> При косвенном тесте мы точно не можем знать что сломалось
Точность не нужна! Нам нужна, как правило, более менее качественная разработка за приемлемое время! Толку то от этих тестов ступид-геттеров, если это же время можно потрать на интеграционный тест?
Они делаются за секунды. А сэкономить могут часы.
Хорошо. Давайте не будем спорить, а поверим практике.
Приведите 2-3 примера из Вашей реальной жизни те случаи когда такой юнит-тест, реально помог Вам! Причем не за всю вашу многолетнюю практику программирования, а за недалекое прошлое. Сможете привести такие примеры?
На днях копипастнул геттер и сделал опечатку перебивая имя переменной.
А Вам компилятор ничего не сказал? Еще, возможно, у Вас в классе было очень много однотипных переменных, что говорит о необходимости вынесения их в один общий класс или структуру. Чтобы не гадать «на кофейной гуще» лучше с подробностями.
Интерпретируемый язык со слабой динамической типизацией (PHP). Вместо $this->adress написал $this->adres
Ну я недавно тоже подобную оплошность, допустил ))) программируя на Python:
Подробнее
было:
	def stderr(self):
		self._toolResult['stderr']

надо было:
	def stderr(self):
		return self._toolResult['stderr']

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

Как вывод написал скрипт на питоне же, чтобы проверял меня на наличие таких идиотских ошибок. Но такие ошибки не относятся к геттерам с тривиальным кодом! Я поступил проще, я написал скрипт который проверяет меня на наличие подобных ошибок. Что достиг этим:
1) Выловил еще пару ошибок в других моих скриптах
2) Не написал лишнего и избыточного юнит-теста
Подобные ошибки: ваша и моя, это «общескриптовое» это так сказать «систематическая ошибка рук разработчика» и ее лучше выделять для ВСЕХ ваших работ, а не только для конкретно одной!
Ещё параллельно на Ruby пишите? :)

Мои основные языки это C++(C++03), Python(3.x), Assembler(вставки или небольшие программы с помощью fasm). Ооооочееень лениво посматриваю в сторону C# и Go, но пока мне этих 3х языков вполне хватает. Ruby не впечатлил, его нишу в моем мозгу уверенно занимает Python
У меня почему-то с python не ассоциируется возврат из функции значения последнего выражения, а вот с ruby однозначно. А ruby сильно впечатлил, а python для меня язык шелл-скриптов.
>>А ruby сильно впечатлил, а python для меня язык шелл-скриптов.
Ну простота написания кода и есть один из плюсов для питона. Если почитаете дзен питона, то там так и сказано «чем проще тем лучше» )
Читал. иСпользую даже в PHP. Хотя я не согласен с определениями «проще» в определениях синтаксиса.
Чтобы утонуть в громадном кол-ве тривиальных вещей? ;)

Если не тонем в коде самого класса от акцессоров, то не утонем и коде теста :)
На мой взгляд геттеры и сеттеры не нужно тестировать.

Вы пояснили почему надобности в акцессорах нет в некоторых случаях. Если нет, то и тесты не нужны. Но я как-то привык писать акцессоры только когда в них есть надобность, а тогда и тесты бы неплохо написать.
>>EDD (Eye Driven Development – разработка через зрительное слежение)
По-моему это самый популярный вид программирования. )
Насчет тестирования GUI — бывают случаи, когда там «оказывается» код, связанный с синхронизацией движка и интерфейса и так далее, поэтому случается, что тестирование движка не выявляет все проблемы и приходится-таки писать тесты для GUI
Вот так читаешь перевод и только в конце понимаешь что автор статьи Роберт Мартин — мировой консультант в области разработки ПО.
Реквестирую продолжение. Очень хорошее дело делаете
UFO just landed and posted this here
Вы уверены, что список правильно отформатирован?
Уверен, что неправильно. Напишите, пожалуйста, в личку как исправить положение.
Суперский троллинг прошлой статьи был адски успешен, и Боб Мартин пытается повторить свой успех :)
Или же сказать, что он-де не троллил, а реально за все хорошее и против всего плохого.
Подкинте статейку\книгу по тестированию и TDD. С++.
Я вот, читая все это обсуждение про тривиальные и не очень геттеры/сеттеры, внезапно поймал себя на мысли о том, что последний раз делал тривиальное свойство (в терминах C#) очень давно.

Либо это был DTO/модель данных (и там тривиальные свойства объекта юнит-тестами покрывать бессмысленно, потому что они покрываются интеграционными с БД/сервисами), либо это нетривиальное свойство (хотя бы расчетное, а то и с более сложной логикой), и тогда его надо покрывать тестами с самого начала.
Ну, у меня в проектах часто встречаются тривиальные свойства с public get и private set. Это очень удобно.
Если public get/private set, то оно уже не тривиальное, потому что логика сложнее, чем у публичного поля (и, раз уж значение свойства назначается где-то внутри, то на это «внутри» должен быть тест).
Здесь мы с вами как раз втыкаемся в проблему о которой сказал Марк Симэн в своём «обзоре» этого поста дяди Боба: «что есть тривиальный?»
В целом, я с вами согласен.
Я в своем комменте имел в виду вполне конкретное public int Count {get; set;}.
Я не любитель TDD, т.к. обычно сразу представляю себе как должен выглядеть код, сразу его пишу, сразу рефакторинг, после – тесты.
Но несколько раз возникали ситуации, сложные задачи, когда я абсолютно не представлял себе каким образом эта штука должна работать, где и что я должен поменять, чтобы добиться верного результата во всех случаях. Я написал предварительно несколько интеграционных и модульных тестов, после чего шаг за шагом исправлял баги.
Т.е. для себя вижу пользу TDD в том, что я смогу написать и изменить тот код (при этом качественно и быстро), который бы не смог написать без предварительных тестов (или это было бы чрезвычайно сложно, в несколько этапов проб, ошибок, возвратов к исходному).
Это не TDD. Это лишь его часть в виде Test-First.
Написал тест-фейл-написал код-успех-повтор. Потом рефакторинг. Использование factories, fixtures, kiss, integrational tests и прочее могут применяться независимо от термина TDD. Прочитав вики, я не увидел существенной разницы TDD с тем, о чем я написал. Поясните, пожалуйста?
TDD больше о последовательном изменении тестов с соответствующем изменением кода.  «test»->«simplest implementation»->«refactoring»->«new test»->…
TFD больше о цели. «acceptance test»->«development until test passed»->«refactoring»
Технически они похожи, но отвечают на разные вопросы. В TDD код для тестов, в TFD — тесты для кода.
Sign up to leave a comment.

Articles