Pull to refresh

Comments 502

TDD использую только для сложных алгоритмов и меж-процессорных, меж-аппликационных взаимодействий.
Для простых приложений, сервисов, библиотек TDD будет избыточным.
Хотя по идее сам считаю, что нужно 100% покрывать код, но реалии диктуют совсем другие сценарии поведения.
С TDD нужно же как минимум проверку всех входных и выходных диапазонов делать (черный ящик)
Для белого ящика проверку всех веток и не только на True, но и на False и на грамотный Exception.
Обожаю TDD но использую его, как уже писал, только для сложных алгоритмов.
Есть же план выполнения, что бы модуль корректно работал, а то что он вообще не покрыт тестами или создан по TDD технологиям со 100% проверкой кода, никого в принципе не волнует, если код в 1-м и во 2-м случае работает одинаково.
Критерии оценки только 2, срок и качество.
Тут же на хабре была статья (которую автор удалил), в которой он рассказывал как делать фейковое покрытие тестами, обучал и рассказывал как не свихнуться в фирме в которой заставляют делать тесты на любой код. Потому из под палки TDD врятли приведет к чему то хорошему, тк проверяющему надо на 100% голову включить и на проверку кода алгоритма и на проверку всех вариантов по TDD. Может показаться, что проверка алгоритма и проверки по TDD одно и тоже, но это не так.
С TDD нужно намного больше головой думать.
Грамотно спроектировать приложение или библиотеку по TDD, ну как минимум на 25% нужно больше времени, чаще выходит увеличение времени в разы.
Для сложных алгоритмов это окупается с лихвой, тк последующие внесение изменения алгоритма время затрачивается по экспоненте, для простых приложений и алгоритмов создавать проекты по TDD это чрезмерная трата времени и сил, по крайней мере для меня.
— Тоже бы хотел услышать не просто использовать TDD или не использовать, для меня однозначно использовать, я бы хотел узнать КАК!!! сократить время на проектирование по TDD, потому что часто бывает описание алгоритма в ТЗ 4 строчки и спроектировать алгоритм и разрабатывать дальше по методике экстремального программирования это одно, а разрабатывать алгоритм по TDD технологии с описанием все параметров, проверкой всех веток, ну это абсолютно другое и время затраченное на простых приложениях по TDD может быть и в 10 раз больше, чем при экстремальном программировании.
А правильнее будет как раз наоборот. Ну почти что.

TDD — это про юнит-тесты. То есть тесты для компонентов*, которые написаны Вами, и в которых зависимости** замоканы. В них мы прогоняем большое количество различных случаев, в которых убеждаемся, что требования выполняются и программа работает именно так как это ожидается.
Если компонент написан правильно, очень легко покрыть его юнит-тестами на 100%

Сложные тесты с зависимостями — это уже не юнит. Это — интеграционные тесты. В них мы не проверяем все возможные случаи. В интеграционных тестах главный фокус — это проверка, что разные компоненты взаимоинтегрировались между собой правильно. Поэтому программист описывает минимальное количество случаев, которыми старается затронуть вызов всех зависимостей. Покрывать интеграционными тестами чужие компоненты для программиста смысла нет, они должны быть покрыты в цикле своей собственной разработки, а нами рассматриваются исключительно как черные ящики. Тем не менее, QA тестирует приложение целиком, поэтому его тесты обычно покрывают значительную часть зависимостей.

Типичный сценарий написания программы по TDD выглядит примерно так:
1. Получаем требования, дизайним контракт вызовов. Получаем документ с описанием API.
2. Пишем юнит-тесты согласно контракту. Получаем примеры использования API
3. Пишем имплементацию. Наши юнит-тесты начинают проходить.
4. Делаем интеграционные тесты с реальными зависимостями, убеждаемся что они проходят. Если нет — чиним инициализацию, DI, инфраструктуру и т.д.

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

**зависимость (чужой компонент) — компонент, которому делегируется часть функций компонента, который мы разрабатываем. Может быть частью фреймворка, который мы используем, или написан нами, но вне рамок текущей задачи.
Я соглашусь с вами только в одном: принцип — никогда не доверяй входящим переменным. Только это всегда защищало мой код от всех вмешательств. Да, тесты избегали изменения функции, но также можно поменять и тесты (как и функцию — довольно часто это наблюдаю в бизнес разработке). Если поддерживать принцип «не доверяй входящим переменным» в рамках функции (и возврату из других), то все сразу становится на свои места и код становится нереально чистым и читабельным. И это сразу покрывает тесты и документацию (потому что стараешься чтобы код не был монструозным). Итого код защищен, а тесты чаще для того, чтобы было
fcoder
TDD — это про юнит-тесты

Сорри, надо было более подробно выразится на счет TDD
Классическое TDD — это Test Driven Development, но для себя я его делю на 2 типа:
1. Процедурное — когда нужно разработать функционал процедуры, функции.
2. Архитектурное — когда нужно разработать архитектуру приложения/проекта по этим принципам.
Я имел в виду только эти сложности самого проектирования по TDD.
Юнит тесты, интергационное, интерфейсное, нагрузочное и прочее тестирование для меня не важно, для меня важно сейчас научиться грамотно создавать архитектуру проектов по этому принципу.
Вот что для меня сейчас «больная мозоль» в TDD.

DSL88
никогда не доверяй входящим переменным

У нас с этим попроще, pascal.
Основной фокус на алгоритмах.

* * * * * * * *
* не могу писать в ветках, поэтому напишу тут, кому хотел ответить
* * * * * * * *
HellWalk
Ведь самое главное, в TDD — это на 100% понимать какие публичные методы будет иметь объект(ы)

M1haShvn
Статьи говорят «продумать архитектуру». Но архитектура — это общий принцип, а не конкретные «потроха»

Коллеги, вы понимаете мою «боль».
Да же при наличии спецификации иногда трудно для себя определить не только уровень параноидальности проверок. даже для простой функции умножения.
  1. будут ли ограничения типов? (real/int/int64)
  2. будут ли отрицательные числа?
  3. что делать при переполнении?

И это только для одной операции, где проверки можно сделать по типу «черного ящика»
Для более сложного метода с ветвлениями уже надо продумывать как положительный, так и отрицательный тест для каждой ветки.
Но с «белым ящиком» еще можно и нужно возится по TDD.
А вот как разрабатывать архитектуру целого проекта по TDD?
Определить где нужны моки-эмуояторы, где нет.
Насколько простыми или сложными будут тесты, какие тесты, хватит «черного ящика» или гуляем на полную с «белым»?
Что делаем по TDD, только проверку кода или цепляем интерфейсы, какие проверки окружения будут делаться или забить на них до первых проблем, заложим выявления «узких мест» или будем чесаться когда начнутся проблемы? Ибо расписывать и закладывать полностью все при разработки проекта, легче застрелиться.
Конечно хочется полностью сделать проект расписав все по TDD, но блин голова идет кругом.

DistortNeo
Понятное дело, что если вы пишете высокопроизводительный код, то подобное тестирование может начать вставлять палки в колёса.

Не знаю, не знаю. Если мне надо написать высокопроизводительный код, я 100% его буду разрабатывать по TDD
Druu
Потому что программист между качеством кода и удобством тестирования вынужден в случае TDD выбирать второе в ущерб первому.

Позвольте не согласится. Качество кода с TDD будет лучше однозначно, тк заставляет посмотреть на код более детально и глубже. Скорее всего речь идет о скорости разработки и сдачи кода, с этим соглашусь, но качество кода однозначно будет лучше с TDD.
Докажу это на примере. Если у вас супер-мега-сложная функция, в которой просто ну нельзя вставить ни одного символа для тестирования, в таких случая просто тестируйте ф-ю как «черный ящик». И даже при использовании тестов методом только одного «черного ящика» качество кода гарантированного будет выше, чем вы сделайте это вообще без тестов. Тем более отлаживать то все равно будете, что мешает во время отладки сразу сделать тест и в последствии вместо отладки нажать на кнопку теста, автоматизировав эту проверочную ситуацию?
Как я выше написал, конечно нужен баланс и я сам редко использую «процедурное» TDD, но хочу научиться 100% все делать по TDD и делать это быстро, но увы… если нужна скорость, то TDD первым идет в топку.

netch80
TDD не позволяет разработать реально сложный алгоритм

TDD предназначена не для разработки алгоритмов, я для проверки алгоритма по спецификации.
С пониманием этого, становится понятно, что важно не test-first, а чтобы тесты вообще работали

100% соглашусь, тк в большинстве своем тесты пишутся после кода. TDD — это конечно идеал, когда много времени, четкая спецификация. Как по мне, первым тест-вторым код или первым код-вторым тест не принципиальная разница, если делается полное покрытие, но если писать код сложного метода, я начинаю делать именно по TDD. У меня по TDD сложный код быстрее пишется.
Я вот хочу сейчас архитектуру проектов делать по TDD, тыкаюсь как котенок, попадалось ли на горизонте подобная информация?
Позвольте не согласится. Качество кода с TDD будет лучше однозначно

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


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


в таких случая просто тестируйте ф-ю как «черный ящик»

ЛЮБУЮ функцию следует тестировать как черный ящик. Иначе ваши тесты — это просто горка мусора, которая:


  1. бесполезная
  2. затратная

Кроме того, TDD в принципе явно запрещает тестирование иначе как методом черного ящика.


Тем более отлаживать то все равно будете, что мешает во время отладки сразу сделать тест и в последствии вместо отладки нажать на кнопку теста, автоматизировав эту проверочную ситуацию?

То, что:


  1. в общем случае написание подобного рода тестов обычно требует более чем удесятирения трудозатрат.
  2. юнит-тесты, вообще говоря, очень редко ловят имеющиеся ошибки. т.к. если кейз протестирован — значит, вы его учли, если вы его учли — все работает хорошо. А если вы кейз не учли — вы его и не протестировали, тесты ошибку не поймают. В итоге юниты обычно ловят только банальные ошибки вроде невнимательности и т.п. Собственно, те же самые ошибки, что ловят типы. По-этому если у вас есть типы — то особого смысла нет и в юнит-тестах. И нет смысла удесятерять трудозатраты. Вот интеграционные тесты, с другой стороны, весьма полезны.
юнит-тесты, вообще говоря, очень редко ловят имеющиеся ошибки

Начните утро с волшебным вкусом Property Based Testing.

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

Не понимаю, к сожалению, о чем вы. Вы по ссылке-то сходили?

Не понимаю, к сожалению, о чем вы.

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


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

св-во для тестов задали — значит, вы его не забыли. Значит, вы его учли в коде

А, понял. Это не верно :) То есть, верно наполовину.


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

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

В таких случаях может помочь QuickSpec

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

Мы пробовали… но для него надо специально готовить код, чтобы анализаторы понимали, куда тут чего внедрять. Это ещё хуже, чем TDD: там хотя бы понятные очевидные методы декомпозиции.

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

Что? Это в каком языке такой кошмар?

Erlang, игрались с PropEr.

Ну, может, мы просто что-то не так начали делать, но вот вам пример реальной задачи (честно — именно такое было в живом полезном коде). Есть список пар — двухэлементных кортежей. Сгруппировать его по этим первым элементам, заменив второй элемент пары на список таких из исходного списка. Выходной список должен быть отсортирован по первым элементам (UPD — а по второму должен сохраняться порядок исходного списка).
Например, из [{a,ax},{b,by},{a,aa},{b,bb}] должно получиться [{a,[ax,aa]},{b,[by,bb]}].
Юнит-тесты достаточно банальны. Можете описать, как это формализовать на QuickCheck или PropEr?

Вот щас прям обидно было. Фред Херберт — умнейший чувак, и PropEr — лучшая нехаскельная реализация из мне известных.


  • Генерируете список переменной длины, с повторяющимися первыми элементами кортежей (поэтому лучше использовать кастомный генератор).
  • Для каждого такого списка вызываете проверяемую функцию на N пермутациях (можно даже просто на двух разных).
  • Удостоверяетесь, что результаты совпадают.
  • Voilà.



Но я не говорил, что property testing — серебряная пуля :) Конкретно для этой задачи я бы просто не переизобретал велосипед. Кладете первый список в ets, bag, идете по нему несколькими (по количеству ядер) процессами и перекладываете элементы в ets, ordered_set, с помощью ets:update_element-3. Теперь вычитываете новую таблицу и в один проход сортируете списки значений. Я не на 100% уверен, но, кажется, так и быстрее будет, и тестировать меньше, все уже протестировано.

> Для каждого такого списка вызываете проверяемую функцию на N пермутациях (можно даже просто на двух разных).
[...]
Ну по факту в этом случае никакой разницы с тем, что такой тест будет написан тупо вручную. В чём мы тогда и убедились.
Может, есть какой-то особый подвид задач, в которых подобная автоматизация идёт на пользу, но нам они не встретились.

> Кладете первый список в ets,

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

Шринкинг вы тоже вручную будете делать? Кроме того, оно может поломаться на своеобразных инпутах, типа 'ф́' — их PropEr вам нагенерирует в достатке, а руками — все не переберешь. Посмотрите, что он генерирует.


Тестировать же [ETS] надо будет по любому (и тоже не меньше).

Отнюдь. Полагается доверять тому, что ordered_set ведет себя как ordered, то есть вам не надо будет проверять собственно сортировку.

> Шринкинг вы тоже вручную будете делать?
> типа 'ф́'

Это да, аргумент. Такие вещи оно само за нас пусть автоматизированно делает :)

> Полагается доверять тому, что ordered_set ведет себя как ordered

Но правильность интеракции с ETS уже под вопросом, и её надо подтверждать тестом.

Дык а серебряную пулю никто не обещал, просто я действительно отловил много ошибок именно в подобных вещах; вот, например, тут: Iteraptor, когда оно у меня ломалось на таких внезапных вещах, как атом '' (да, это валидный атом :), списках каких-то трижды вложенных, еще чем-то, не помню уже сейчас.

UFO just landed and posted this here
С ходу уже не помню, но рисовал где-то следующий комплект входных данных:
[]
[{a,1}]
[{a,1},{a,11}]
[{a,1},{b,2}]
[{a,1},{a,11},{b,2},{b,22}]
[{a,1},{b,2}{a,11},{b,22}]
[{a,1},{b,2}{a,11},{a,12},{b,22}]

и ещё несколько в том же духе (в пределе до 3 ключей и 3 элементов к ним, кажется — решили, что больше точно не нужно).
(в пределе до 3 ключей и 3 элементов к ним, кажется — решили, что больше точно не нужно)

А что, если алгоритм ошибочен, но ошибки проявляются только на списках с 4 ключами и элементами?

А вот это уже вопрос анализа сути выполняемого в реализации: так как после сортировки по первому ключу все известные случаи, требующие продумки, это
1) первый элемент списка
2) элемент с тем же ключом, что предыдущий
3) элемент с другим ключом, чем предыдущий
4) конец списка
то реально больше чем 2 ключа и 2 элемента на каждый проверять не нужно, они проблем не дадут. Даже 3 ключа и 3 элемента проверялись не на все случаи, а только для совсем уж оправдания своей совести :)
(А если бы была реализация на mapʼе через дерево — те же самые числа, предполагая, что сама мапа корректно работает.)
UFO just landed and posted this here
для тех языков

Для тех случаев, когда формальные доказательства не существуют в природе. Это во-первых. А во-вторых, я (и куча народу еще) — мы не хотим быть богатыми. Я умею писать код, который годами и десятилетиями работает безо всяких формальных доказательств.


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


Такие дела.

UFO just landed and posted this here
Я тоже. Но я хочу иметь возможность особо важные/фундаментальные вещи доказывать, а не проверять сотней-другой примеров.

Ну наконец-то. Я тоже. Особо важные. Фундаментальные. Я возьму coq (шучу, я слабоват в математике, я пойду в RnD, к умным коллегам) и все будет доказано, либо опровергнуто. А я потом код напишу по этому доказательству, на эффективном для данной задачи языке (и это почти никогда не будет хаскель, кстати, по крайней мере сегодня). Чтобы использовать в магазине умение понять, хватит ли мне еще и на спички, не нужно каждый раз начинать с аксиом Пеано.


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


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


А потом — я стану проверять странные случаи (спойлер: в инверсии списка их нет). Пример из жизни: мы в некоторых случаях позволяем пользователям описывать триггеры по курсам валют в «свободной форме». Например: EUR/USD > 1.2. Парсим их кое-как. И используем, если они верные.


Критерий верности: курс похож на настоящий, а если не очень похож — то совсем не похож ни на один из других валютных пар, из всех триггеров этого клиента. Например, EUR/USD > 20 мы не примем никогда, а EUR/USD > 2 — примем, только если клиент не следит еще за EUR/NZD и не пытается там выставить триггер на > 1.2.


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

UFO just landed and posted this here
А, кстати, почему?

Я не считаю его production ready по параметру fault tolerance. В том смысле, что я не очень понимаю, как его мониторить, и что будет, если какой-нибудь процесс помрет. Supervision trees очень быстро приучают к хорошему.


Но я и тестировать его не буду, понимаете?

Угу.


Кто мешает его полноценно записать в типе [...]

Отсутствие понимания, что это за свойство. Я использую property based чтобы поэкспериментировать со значениями.

Непонятно, зачем рассматривать только два варианта из четырёх.

Отвечу отдельно, поскольку это совсем про другое.


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


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

>> Позвольте не согласится. Качество кода с TDD будет лучше однозначно
Ну это же очевидно ложное утверждение.

Приведите пожалуйста пример КАК (!!!) TDD может умудриться ухудшить качество кода?
При использовании «черного ящика» я определяю только входные и выходные параметры, внутрь никто не лезет, что там и как, никого не должно волновать. Как черный ящик со своими данными которые проверяют спецификацию может ухудшить качество кода, если он проверят функционал спецификации? Пример пожалуйста приведите ухудшения.
Совсем другое «белый ящик», с ним может снизится скорость и только, но никак не качество. Да и применение «белого ящика» ограниченно, куда его всунешь в MMX/SSE оптимизацию?

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

При чем тут «гипотезы»?
Ответьте пожалуйста, Вы реально применяете TDD или только «проверяете гипотезы»?
Если вы только «проверяете гипотезы», я даже общаться не хочу, я хочу поговорить только с теми кто использует у себя TDD.
Предположу, что Вы применяете у себя TDD, поэтому продолжу.
Кроме того, TDD в принципе явно запрещает тестирование иначе как методом черного ящика.

Если честно, мне по барабану, кто там что запрещает, я делаю как МНЕ удобно, а не то, что кто то где то, так написал. Если на заборе написано «слово», а за ним дрова, то вывод очевиден.
«Белый ящик» в сложных алгоритмах я использую очень часто.
«Белый ящик» -это тестирование внутри метода части/блока алгоритма, который пока не отрефакторили, а возможно и не отрефакторят.
так что на счет Вашего выражения:
ЛЮБУЮ функцию следует тестировать как черный ящик. Иначе ваши тесты — это просто горка мусора

отвечу мемом:
«Может просто вы не умеете их готовить?» (с)

в общем случае написание подобного рода тестов обычно требует более чем удесятирения трудозатрат.

Если мы говорим про «Процедурное TDD» и «черный ящик», то однозначно нет, тк трудозатраты уменьшаются.
я сейчас говорю не о простых методах типа MyEcho который внутри вызывает только одну системную ф-ю Echo, а про более сложные алгоритмические методы
Если говорим про «белый ящик» и «Архитектурное TDD», то тут соглашусь, время увеличивается значительно.

юнит-тесты… А если вы кейз не учли

Я вот это выражение часто вижу, но вообще не понимаю, как такое может быть в процедурном TDD?
Есть спецификация и по ней пишется функционал, если уж пишутся тесты то спецификация должна полностью быть покрыта тестами.
А вот то что вместо бинарных чисел, кто то отправляет символьные числа (игнорируя спецификацию) или затирается память другим потоком, то на все случаи такие кейсы заранее не сделать, я сейчас говорю про кейсы по спецификации, а они должны быть покрыты полностью.
И вот это «кейз не учли», просто не допустимо. Или протестировали или нет функционал, вот и все.
PS:
Если вы используйте TDD (ну или кто то еще), пишите сразу в личку, ждать сутки что бы ответить, ну как то не камильфо, и кто нибудь знает хорошие форумы по TDD?
Если на заборе написано «слово», а за ним дрова, то вывод очевиден.

Ну да, сарай ill-typed.

Приведите пожалуйста пример КАК (!!!) TDD может умудриться ухудшить качество кода?

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


При чем тут «гипотезы»?

А что это, если не гипотеза?


Если честно, мне по барабану, кто там что запрещает, я делаю как МНЕ удобно

Тогда при чем тут TDD, если то, что вы делаете, TDD, по определению, не является? Что мы вообще обсуждаем тогда?


Если мы говорим про «Процедурное TDD» и «черный ящик», то однозначно нет, тк трудозатраты уменьшаются.

С чего бы они уменьшались? Кода пишется больше (существенно больше), на его написание время. Время тратится на "ухудшение" архитектуры (чтобы код был более тестируемым). Время тратится на поддержку тестов и их постоянное переписывание. Все это затраты.
В принципе, затраты, при прочих равных, пропорциональны количеству кода. Причем зависимость нелинейная. Любая практика, которая стабильно увеличивает количество кода, ведет к росту затрат.


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

Ну так спецификация на практике будет неполна.

UFO just landed and posted this here

Вам доводилось когда-нибудь тестировать взаимодействие сотен тысяч процессов (green threads), чтобы выявить возможные race conditions?

UFO just landed and posted this here
код который хорошо тестируется и есть качественный

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

UFO just landed and posted this here
потому что сделать утверждение "качественный или нет" можно только протестировав

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


а если код не поддаётся тестированию

Тестированию поддается вообще любой код, который что-то делает. Мы говорим о том, насколько хорошо он поддается тестированию.

UFO just landed and posted this here

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


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


Лихо.

Да, это плохой код. Вспомните про бэкдоры и уязвимости в реализациях криптографических алгоритмов.

Прекрасно, мы пришли к тому, что почти весь окружающий вас софт чуть сложнее интернет-магазина (да и то не всякого) — написан плохим кодом.

Далее бритва нашего Оккама позволяет нам придти к выводу, что если большая часть кода в мире написана плохим кодом — это работает, и является (в масштабе человечества) наиболее эффективным в данный момент способом разработки. То есть, плохой код = реальный нужный код, а «хороший» (в ваших дефинициях) код = либо фантастика, либо трюизм.
либо фантастика, либо трюизм

Либо интернет-магазин же.

Прекрасно, мы пришли к тому, что почти весь окружающий вас софт чуть сложнее интернет-магазина (да и то не всякого) — написан плохим кодом.

Так и есть.

плохо поддаётся тестированию == плохой код.

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


"плохо поддаётся тестированию" — будут большие трудности вносить изменения.

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

UFO just landed and posted this here
легкотестируемый == изменяемый
труднотестируемый == трудно изменяемый

Вы опять просто повторили гипотезу. Аргументы в ее пользу какие-то у вас есть? С чего бы легкотестируемому коду быть легкоизменяемым?


Не надо, кстати забывать, что код с тестами изменять более затратно, чем тот же код — но без тестов. Ведь кроме когда надо будет изменить еще и тесты.

Не надо, кстати забывать, что код с тестами изменять более затратно, чем тот же код — но без тестов. Ведь кроме когда надо будет изменить еще и тесты.

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

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

При чем тут вообще программист? Решение использовать тесты — решение исключительно менеджмента. Тесты позволяют улучшить качество приложения, увеличив затраты на разработку. Так что это уже заказчик должен решать, готов ли он за качество оплатить х2, или его устроит вариант "быстро и дешево".


Кроме того — непонятно, как ваш тезис относится к процитированному. От того, что программист делает ошибки, тесты не усложняют изменения кода, добавляя работы? Усложняют. И, да, раз программист делает ошибки — он делает их и в коде тестов. Эти ошибки потом тоже надо будет исправлять, а это дополнительное усложнение.

UFO just landed and posted this here
тесты позволяют улучшить качество приложения и уменьшить затраты на разработку.

Конечно же, нет. Не бывает серебряной пули, которая может улучшить качество, снизив при этом затраты.
Тесты строго повышают затраты на разработку — это очевидно. Т.к. затраты на разработку с тестами = затраты на разработку без тестов + затраты на написание тестов. Где затраты на написание тестов > 0.

Тоже неверно.


Тесты могут помочь со сложным алгоритмом, например, потому что запустить → посмотреть чё не так — быстрее, чем вдумчиво изучать неработающий код.


Я всегда пишу пару тестов, которые потом обычно отправляются прямиком в мусорку, чтобы не возиться с реплом.


Хотя проще и быстрее, разумеется, сразу работающий код писать.

Тесты могут помочь со сложным алгоритмом, например, потому что запустить → посмотреть чё не так — быстрее, чем вдумчиво изучать неработающий код.

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

Да, тесты повышают затраты на разработку, но снижают затраты на поддержку и допиливание продукта.

UFO just landed and posted this here
а никто не говорит что это серебрянная пуля.
речь идёт о том что это экспериментально найденный оптимум.

Так оптимум оптимизирует некоторую ф-ю от параметров, f(затраты, качество) (ну, там в реальности еще другие параметры, но мы рассматриваем эти два). При этом в каждом проекте эта ф-я своя. И тесты — повышают качество, повышая затраты. Пока вы "в бюджете" (например), рост затрат ведет к малому росту f, значит может быть выгодно повышать качество — f итоговая будет от тестов расти. Выйдете за бюджет — будет резкое падение f от тестов.


Это все не отменяет общего факта — нельзя повысить качество, не повышая затраты. Это возможно как отдельный какой-то кейс, но стабильно работающего, универсального способа так делать — нет.


DistortNeo


Да, тесты повышают затраты на разработку, но снижают затраты на поддержку и допиливание продукта.

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

UFO just landed and posted this here

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

Тесты повышают качество снижая затраты.

Не бывает серебряной пули, которая бы и снижала затраты и повышала качество одновременно. Либо одно — либо другое.


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

В затраты ничего не уходит, затраты вы сами определяете. Вы либо можете выделить деньги на тесты (понести доп. затраты) и уменьшить количество ошибок. Либо не выделить — и тогда у вас ошибок будет больше. Любое тестирование (в принципе любое) приводит к дополнителным затратам. Если вы к интеграционным, е2е, и ручным тестам добавляете юнит-тесты — затраты растут.


говорить что код с тестами дороже кода без тестов можно только на начальном этапе развития проектов или для маленьких проектов. до 500 (моё имхо) строк кода в проекте.

Наоборот — чем больше у вас проект, тем выше суммарные издержки на тестирование.
Еще раз, стоимость_проекта_с_тестами = стоимость_того_же_самого_проекта_без_тестов + стоимость_тестов


Иными словами, вот вы можете взять некий проект Х с тестами. Допустим он стоит, ну, 1000 (просто число для определенности. Теперь мы берем и удаляем все тесты. А дальше говорим программистам "извольте покрыть проект тестами". И вот, программисты берут и покрывают проект тестами — это вам обходится, допустим, в 300. Тогда у вас стоимость проекта без тестов — 700 (т.е. за 700 вы могли бы получить вот этот продукт с этим функционалом), стоимость тестов — 300. Тесты увеличили стоимость проекта на 300.


Volch


Тут нужно уточнить, что затраты на поодержку и развитие снижают качественные тесты прежде всего.

Затраты не снижают никакие тесты. Т.к. написание теста — это всегда дополнительные затраты, которые можно было бы не нести.

> Не бывает серебряной пули, которая бы и снижала затраты и повышала качество одновременно. Либо одно — либо другое.

Это не серебряная пуля никоим образом, но пока вы не начнёте правильно считать затраты, не поймёте пользу тестов.

Польза тестов получается от того, что суммарные затраты от поиска ошибки на этапе её обнаружения у QA в разы выше, чем если бы она не вышла за пределы авторов-программистов, а у юзера — ещё выше, чем у QA. В ряде источников приводятся соотношения — 10 раз для первого и 10 для второго; сейчас источник не помню (что-то близкое к классикам типа Фаулера и Бека), но даже если они очень приврали и реально там получается 3 и 3, то этого уже достаточно, чтобы подумать.

А вот дальше стоит вопрос, какое реально наблюдается качество кода без тестирования, какая его часть, грубо говоря, изначально корректна. Если он весь некорректен, то мы можем оправдать затраты на тесты в 2 раза больше, чем на сам код.
А если, как для, простите, Боинга те разницы достигают тысяч (оплата страховок погибших плюс репутация), без тестирования всего и вся в тридевять слоёв — даже начинать нельзя…

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

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

Откуда берется большее количество кода при доработках?

Основной код и код тестов же. Это если не учитывать увеличение объёма основного кода ради тестируемости.

UFO just landed and posted this here
это не серебряная пуля, а экспериментально найденный оптимум.

Так оптимум оптимизирует некоторую ф-ю от параметров, f(затраты, качество) (ну, там в реальности еще другие параметры, но мы рассматриваем эти два). При этом в каждом проекте эта ф-я своя. И тесты — повышают качество, повышая затраты. Пока вы "в бюджете" (например), рост затрат ведет к малому росту f, значит может быть выгодно повышать качество — f итоговая будет от тестов расти. Выйдете за бюджет — будет резкое падение f от тестов.


Это все не отменяет общего факта — нельзя повысить качество, не повышая затраты. Это возможно как отдельный какой-то кейс, но стабильно работающего, универсального способа так делать — нет.


если Вы отказываетесь от затрат на автоматические тесты, Вы ВЫНУЖДЕНЫ тратить существенно больше на QA

Почему больше? Какие именно ручные тесты не будут проводиться в том случае, если я добавил юниты?


Кроме того — мы же и не рассматриваем ситуацию, когда в приложении с тестами и без тестов одинаковое количество багов. Допустим, их меньше — это и будет означать, что мы увеличили качество (уменьшили количество багов) за счет роста затрат (написали тесты, хотя могли не писать). При этом без тестов — да, багов было бы больше. Но при этом и затраты были бы меньше — ведь мы не тратились бы на тестирование.


И итого выходит что код с тестами развивается дешевле чем код без тестов.

Вы утверждаете что неравенство в вещественных числах a + b > a ложно при b > 0. То, что это не так, вам без особых проблем докажет практически любой ученик старшей школы. Прекратите спорить с математикой.

UFO just landed and posted this here
эта функция в каждом проекте своя, пока проект не выйдет на число строк в проекте таким, что это число уже становится статистикой.

Нет, эта функция в принципе всегда своя. Вне зависимости от количества строк.


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

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

UFO just landed and posted this here
о том что затраты на то же качество при ручном QA и при использовании автотестов — разные. и при использовании автотестов существенно ниже

Вот это не факт. Вашим языком выражаясь, в разных организациях разные константы в этих затратах.

нет. законы развития у всех проектов одинаковы.

При чем тут законы развития? Мы о требованиях к проекту говорим (функция оценки). А они всегда разные.


о том что затраты на то же качество при ручном QA и при использовании автотестов — разные

Допустим, разное. Какое это имеет отношение к делу-то? Если вы заменяете "дешевыми" юнит-тестами "дорогие" ручные — вы снижаете качество, снижая затраты. Если вы добавляете юнит-тесты к ручным (или к отсутствию тестов) — вы увеличиваете затраты, увеличивая качество. Как видите — невозможно сформулировать случай, при которой можно и на елку сесть, и ж*пу не поцарапать.

UFO just landed and posted this here
требования примерно одинаковые во всех случаях.

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


нет, именно увеличиваю качество снижая затраты.

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

UFO just landed and posted this here
когда внедряли конвееры

При чем тут конвейеры? Это некорректная аналогия.


и с тестами то же самое

Практика показывает обратное.


не отсутствие, а минимизация.

Отлично. Какие именно ручные тесты не проводятся, когда у вас есть юнит-тесты?


и это, когда нормально покрыто тестами, то для релиза и запускать-то необязательно: в тестах запустится и проверит

Так в юнит-тестах приложение не запускается. Это же юнит-тесты.


Вы ж держите контекст — ни автоматические интеграционные, ни е2е-тесты мы тут не обсуждаем.


chapuza


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

Ну мы вроде серьезную разработку обсуждаем, а не "проекты" на 5 экранов.
С пятью экранами, действительно, это все неважно.


И "нареканий нет" — это что значит? Код у вас с нулевым количествам багов? Или что?


И, не поделитесь, сколько у вас в среднем кода юнит-тестов на 1000 строк обычного кода и какое покрытие ими?

Ну мы вроде серьезную разработку обсуждаем [...]

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


И "нареканий нет" — это что значит? Код у вас с нулевым количествам багов?

Выявленных — ноль, да.


И, не поделитесь, сколько у вас в среднем кода юнит-тестов на 1000 строк обычного кода и какое покрытие ими?

Зависит от многих факторов. Покрытие мы не считаем, это бессмысленная трата времени. Покрытие не показывает вообще ничего, это сто раз уже было отмечено и все, вроде, осознали, примерно лет 10 назад.


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

Пять экранов — это в данном контексте — размер PR, очевидно, а не проекта.

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


Я юнит-тесты пишу только тогда, когда это удобнее, чем проверяться в репле.

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

нам с вами, в общем-то, и нечего обсуждать тут

Как раз наоборот. Мы тут обсуждаем, нужны ли тесты вообще, а если да — то в каком виде. Вам какой-то дядя, имя которого вы навряд ли даже знаете, сказал, что тесты — это круто; или наоборот, что не очень. А я думаю своей головой и пишу тесты, когда это нужно.

Мы тут обсуждаем, нужны ли тесты вообще, а если да — то в каком виде.

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


а Вы не упирайтесь в именно юниттесты

Но речь шла про юнит-тесты, остальное мы не обсуждаем.


Для них это тоже по сути юнит

Нет, это не юнит, это вообще уже е2е.


и вот когда любой: бакендщик, фронтендер чета там пушает — CI присылает полный отчёт "не прошло"/"прошло".

Ну так вопрос — сколько на это затратили времени программисты и сколько затратил бы тестировщик? При этом тестировщик сделает работу заведомо более качественно, чем автотесты, так как 90% инвариантов автотестами принципиально не проверяется (за приемлемый срок и с приемлемыми затратами). Пока что человек здесь на десятичные порядки дешевле машины. Вот лет через 10 (100, 1000), когда сделают ИИ — другое дело. А пока что слишком дорого это, да и ненадежно. По-этому везде, где заботятся о качестве, есть и нормальный QA.

UFO just landed and posted this here
речь шла про автоматические тесты вообще.

Нет.

UFO just landed and posted this here
UFO just landed and posted this here
всё тут ок

Не ок то, что вменяемо автоматизируется хорошо если треть работы тестировщика. А юнит-тесты — и вовсе не автоматизируют работу тестировщика ни на сколько. Т.к. они ее никак не заменяют. Это работа в совершенно разных плоскостях с разным результатом.


причины сопротивления — чистая лень.

Лень чего? Для программиста как раз обмазывание юнит-тестами очень выгодно — можно смело сидеть и разрабатывать проект 4 года вместо 1, потребовать себе повышенную зп за дополнительные компетенции и знания популярных баззвордов, ну и потом выдать говно вместо результата т.к. заказчик надеясь на то, что "у нас тут юнит-тесты" сэкономит на нормальном QA и, значит, сам себе злобный буратина, который получил за вчетверо больший срок и бюджет кусок какашки. А программист-то в малине — оставил за собой тысячи строк неподдерживаемых какашек в виде тестов, получил за это деньги и добавил в резюме строчку о завершении такого-то проекта с таким-то стеком и такими-то модными технологиями.


Именно ленивый программист пишет тесты, потому что ему лениво потом разгребать проблемы и просыпаться по звонкам в 6 утра.

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


то есть TDD — не самоцель, а средство прийти к разработке автоматических тестов одновременно с кодом.

Не, если вы определяете какое-то свое особое ТДД, то так везед и говорите "TDD по rsync'у", чтобы собеседники понимали- с общепринятым понятием это ничего общего не имеет.


Но мы тут не обсуждаем TDD rsync'a, мы обсуждаем классическое ТДД с размазыванием 100% покрытия по волосатой груди разработчиков. Если же вы хотите обсудить TDD rsync'а, то это не ко мне, я здесь содержательной темы для обсуждения не вижу.

UFO just landed and posted this here
UFO just landed and posted this here
> когда внедряли конвееры, то многие так говорили про качество.

Конвейеры никогда не работают сами по себе. Над ними ещё один или больше уровней контроля качества, от самого конвейера (не сломан, крутится в нужную сторону, скорость оптимальна и не завышена, на ленте то, что надо, а не останки прошлогодних бегемотов) и до контроля качества выходного результата.
Это хорошая аналогия в том смысле, что юнит-тест полезен только тогда, когда у вас этот юнит выполняет нужное по ТЗ и правильно подключен, а эти два условия вы никак юнит-тестами не проверите — только всяческими интеграционными и вплоть до финальных ручных проверок.
А ещё надо убедить заказчика — который, если у него голова на плечах, поверит только при наличии успеха тестов верхнего уровня и он сам всё прощупает, на что хватило времени и духу. Это и для внутреннего заказчика (product owner, или как он там будет у вас зваться).

> не отсутствие, а минимизация.

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

> PS и это, когда нормально покрыто тестами, то для релиза и запускать-то необязательно: в тестах запустится и проверит. В том числе интерфейс GUI

Если 1) можно покрыть автотестами весь GUI (практика показывает, что возможно не всегда), 2) вы ещё и 100% доверяете тестирующим средствам — а в случае GUI как раз их доля ошибок максимальна: они не люди, и видят иное. Реально приходится заметно выгибаться под облегчение такого тестирования — например, расставлять метки типа div id, a name и т.п., чтобы они надёжно опознавали конкретные элементы. Но уже этой расстановкой можно создать false positives, так что её тоже надо проверять.

UFO just landed and posted this here
UFO just landed and posted this here

Я скоро как пять лет очень плотно участвую в разработке одного веб-приложения. У него достаточно сложная и разветвленная витрина.


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


Не нужно обобщать.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Например, примером кода компаратора для небанального случая (как 3-элементный кортеж, для старта).
UFO just landed and posted this here
> Из этого всё равно не следует, что компаратор должен удовлетворять тем или иным условиям.

Да. Но это условие прописано явно в спеке на саму сортировку, и без средств типа Idris мы возлагаем корректность его проверки на человека. И, естественно, получаем соответствующие сайд-эффекты: когда именно в таком компараторе у меня как-то подопечный облажался в обеспечении транзитивности, сортировка стала крэшиться. Надеюсь, урок был запомнен.

> следует, что от компаратора для этого алгоритма сортировки требуется только тотальность

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

Тестируемость кода — необходимое условие, но не достаточное

UFO just landed and posted this here
Хм, попробуйте описать построение, например, стандартной quick sort с подобными требованиями TDD. Поэтапно, в последовательности «написали тест, уронили, сделали код, который лечит только этот тест и ничего больше… пошли к следующему тесту».
Мне честно интересно: я до сих пор считал, что это невозможно. Но, может, вы осилите это невозможное? ;)
UFO just landed and posted this here
> а зачем описывать примитивы, сорт или реверс списка.

Ну а что, qsort останется нетестированной? Как вы тогда докажете её корректность?
И почему так сразу «примитив»? Это вполне серьёзная сложная задача с кучей тонкостей и особенностей реализации. И да, даже реверс списка можно тестировать (можно грустно и скучно, а можно так).

> а если уж надо quick-sort то на начальном этапе — взять пропустить через sort

Нее, так не пойдёт. Полноценная реализация должна как минимум быть разделена на код разделения, код рекурсии, код сортировки финальных коротких отрезков в отдельных функциях… и всё это по науке, а не после того, как оно написано.
UFO just landed and posted this here
> пойдёт.

Может, и пойдёт. Но всё это не TDD, а обычное тестирование, даже если с test-first.
Как раз такого я и ожидал (хотя надеялся на лучшее). Спасибо.
UFO just landed and posted this here
UFO just landed and posted this here

custom sort не надо тестировать; если мы не доверяем себе — зовем человека, который способен это сделать без тестов, а потом берем любую существующую медленную сортировку и сравниваем результаты.


custom sort надо гонять через бенчмарки, это в стопиццот раз важнее любых тестов. Потому что custom sort в 2020 пишут тогда, когда существующие не устраивают именно по производительности.

А почему сразу речь про custom? Пусть в библиотеке штатной сортировки вообще нет. Если TDD универсален, то он должен работать и в таких случаях.

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


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

Приведите пожалуйста пример КАК (!!!) TDD может умудриться ухудшить качество кода?

Навскидку:


  • делать публичными свойства и методы, чтобы проще было защищенные/приватные тестировать, без рефлексий и т. п.
  • вводить абстракции только чтобы проще было тестировать, например есть у sut зависимость в виде простого класса, для тестирования создаём интерфейс из класса и имплементируем его в классе. Плюс вторая реализация — мок. Формально реализовали DI, на практике интерфейс добавился в коде, который используется только один раз.
  • организуем проброс зависимостей, чтобы их можно было мокать

Понятно, что это не TDD конкретно виновато, а общий подход к написанию тестов, но выглядит это так "мы внедряеем TDD", после чего качество кода портится

Вы передёргиваете и возводите 100% покрытия кода в абсолют. Не нужно писать тесты ради покрытия. Лично я не вижу смысла тестировать приватные методы. Они реально будут только мешать, а не помогать в поддержке.

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

Метод методу рознь. Метод может быть весьма сложным, но никому не нужным за пределами класса и как-то работающим с приватными переменными — и тогда его надо и тестировать, и оставлять приватным.
Ну а как это делать — уже от языка зависит. Начиная с `#define private public` :)
организуем проброс зависимостей, чтобы их можно было мокать

Э-э-э-э… А что, на последнем пленуме dependency injection развенчали и объявили гадостью?

Формально реализовали DI, на практике интерфейс добавился в коде, который используется только один раз.

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

Как TDD ложится на изменение требование в процессе самой разработки? Переписывать и код и тесты сразу?
А сейчас что вы делаете?
Ну, наверно, сейчас он разрабатывает, в процессе требования меняются, к концу разработки более-менее устаканиваются и после этого пишутся тесты. Или у вас всегда сразу есть полное и чёткое ТЗ?
Или у вас всегда сразу есть полное и чёткое ТЗ?

В большинстве случаев да (возможно, специфика отрасли). А если ТЗ неполное или нечеткое, то нужно идти к автору этого ТЗ и уточнять. А то напишешь одно, потом выяснится другое, и получится примерно вот так. Себе дороже.

Я вообще плохо понимаю, как можно что-то писать без четкого. Сказали «напиши программу» и пишешь? А потом выясняется, что нужна была программа для расчета траектории, а не графический редактор.
Я вообще плохо понимаю, как можно что-то писать без четкого. Сказали «напиши программу» и пишешь?
Не «напиши программу», а «хотим, чтобы можно было делать то-то и то-то». И всё, берёшь и пишешь.
Ниже верно ответили. Тесты после того как требования станут стабильными.
А что же проверит код на соответствие новым требованиям, как не переписанные под эти требования тесты?
Переписывать код же легче, если есть тесты (юнит-тесты как минимум).
Когда речь идет о чистом программирование — TDD — отличная методика. Как только дело касается программирования с «привлечением оборудования», становится гораздо труднее.
Например, функция рендеринга через какой нибудь OpenGLES. Результат рендеринга оказывается на экране, и прямого доступа процессором в ту область видеопамяти просто нет (или чрезвычайно затруднен).
Как написать тестирующую функцию?
Что касается самого рендеринга, то он делается не процессором, а GPU и со стороны процессора очень трудно контролировать работу GPU. Даже измерить время работы команды, отданной в GPU не просто, так как команды просто складываются в FIFO GPU на исполнение.
Жирный плюс этому комментарию.

Приходится на работе писать код под аппаратные комплексы, которые мы делаем. Ситуация та же с точностью до 99,(9)%. Большая часть написанных алгоритмов (которые как раз и хотелось бы тестировать) не имеют единственно правильного ответа от слова совсем, а оставшаяся обвязка (которая не алгоритмы), работает с железом…
Тоже работаю с железом (программирую под МК), тоже раньше так думал.
Пока не написал свои первые тесты. Нужно изменить подход к написанию такого софта, убрать так любимое эмбеддерами повсеместное прибитие кода гвоздями к железу.

Да, написать тест к модулю modbus, реализующему L1/L2 уровень, где нужно вычислять по аппаратным прерываниям аппаратными таймерами задержки в миллисекунды — скорее всего сложнее чем один раз это все отладить осциллографом и потом не трогать никогда.

Но написать тест к модулю, реализующему транспортный уровень modbus уже проще простого: вы работаете только с данными и нужно проверять что модуль правильно инкапсулирует/деинкапсулирует нужные данные. Здесь будет работа только с программными буферами, если модуль написан правильно.

Всю работу с uart/usb/светодиодами/eeprom нужно полностью абстрагировать и предоставлять возможность эмулировать их работу программно.
Проще говоря — побольше функционального программирования и все получится.
У TDD нет никаких преимуществ перед обычной разработкой

Не могли бы Вы развернуто ответить про обычную разработку? Что это за разработка, которая не включает, как минимум manual-testing? А если она включает, то это, конечно, неполноценное TDD, но какой-то упрощённый вариант. Или я что-то не понимаю…?
А если она включает, то это, конечно, неполноценное TDD,

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

Не совсем. TDD про сам итеративный процесс и некоторые ограничения в виде "сначала пишется код который проходит один тест, потом пишется следующий тест".


Просто написав все тесты до кода вы не получите TDD, это просто test first подход

Ну и что в этом хорошего? Почему вместо того, чтобы сразу нормально спроектировать систему и зафиксировать её поведение в виде тестов, а уже потом писать код под тесты, предлагается движение маленькими шажками?


сначала пишется код который проходит один тест, потом пишется следующий тес

Более того, TDD не разрешает писать код, который имеет функционал сверх написанного теста. То есть нельзя держать в голове сразу 2-3 теста. Нельзя писать сразу оптимльный код. Нужно реализовывать код так, как если бы об остальных тестах вообще не было ничего известно. Это очень неудобно.

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

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

Насколько выразительной должна быть система типов, чтобы полностью избавится от юнит-тестов? Боюсь такого языка еще не изобрели.

Хаскель изобрели весьма давно, и выразительности там только добавляется. А есть еще и менее зрелые (зато более лаконичные) языки в эту же сторону.
UFO just landed and posted this here
UFO just landed and posted this here

Знал что вы тут появитесь :)
Про языки с зависимыми типами я знаю, и трудно утверждать, что их можно широко использовать "для замены юнит-тестов".
А уж "более коротким и удобным путем" их использование назвать точно нельзя. Надеюсь, в светлом будущем все будет иначе.

По вашему мнению, кроме ошибок связанных с динамической типизацией, больше ничего не существует и с помощью TDD не проверяется? Ну там, ошибки в алгоритмах, например? Или я что-то неверно понял?
UFO just landed and posted this here
Вы меня немного не поняли. Все эти правильный типы это конечно хорошо. Но я вот, из своего опыта, с трудом могу вспомнить хотя бы пару проблем (как следствие => ошибок в продакшене, как следствие — желание/необходимости писать в будущем тесты) возникших из-за «а не передали ли диаметр вместо длины в функцию». Это если не говорить о том, что, ИМХО, в вашем выдуманном примере корректнее делать эту функцию методом класса или передавать в нее объект целиком.
Поэтому я и пытаюсь понять — у меня такой опыт нерелевантный, и во всем мире TDD используют для того чтобы ловить ошибки с неверным типом переменных, или вы с автором комментария немного лукавите, или у нас предметные области настолько разные.
UFO just landed and posted this here
В питон включили строгую типизацию, почему?

Что за выдумки? Никакой строгой типизации там не включали. Type hints != Строгая типизация

Блин, это я распарсил плохо предолжение и сам же себя запутал.

я теперь категорически против интроспеции / рефлексии, динамических импортов, динамического порождения кода типа new Function() или eval(), и стараюсь статически импортировать все модули
Я давно это коллегам говорю, а на меня как на припадашного смотрят… видимо, это потому что коллеги — джависты(( сам язык весьма неплох, инфраструктура довольно удобна, но вот люди, занявшие эту сферу… вызывают непреодолимое желание возродить святую инквизицию (ну как минимум ту часть, где в одностороннем порядке приговаривали к публичной медленной и мучительной смерти)

P.S. а что вы именно под «динамическим порождением кода» подразумеваете? мне вот очень не хватает этой функции в яве/го — зачем использовать рефлексию там, где можно на этапе компиляции нагенерить всё что нужно и сразу собрать в «никак не изменяемый в процессе выполнения» бинарь?!((

P.P.S. в яве это вроде как можно, но я так и не смог понять как это правильно использовать… идеология настолько кривая, что никто и не пытается этим пользоваться — все юзают рефлекшины(( В го прикрутили сторонний *как-его-там-который-на-сях-используют*, но это выглядит малость убого: ИМХО кодогенерация должна являться частью языка (и, следовательно, компилятора), чтобы лаконично списываться в основной код и не нарушать «бритву оккама» (аналогия: сделать кофеварку с функцией капучинатора рациональнее, чем делать капучинатор отделльно ввиду того, что больше половины функционала у них дублируются)

P.P.P.S. если не секрет — зачем вам понадобилось пилить свою СУБД? вопрос дружеский — я сам член гильдии велосипедостроителей))
UFO just landed and posted this here
Ааа, вы про генерацию кода в рантайме… а я-то имел в виду на этапе компиляции :)
UFO just landed and posted this here
и зачем тогда кодоген?
В яве? Ну как минимум чтобы отлаживать было проще. Спринговая депенденси инжешин вызывает у меня изжогу (не от хорошей жизни я перешёл на яву)
А вообще я больше имел в виду что оно нужно языкам без VM, типо Go/D — я это, скорее, как саму идею рассматриваю, нежели конкретную реализацию, которая «нужна здесь и сейчас»
UFO just landed and posted this here
А как типизацией проверить правильность функции вычисления площади круга?
Сформулировать в типах зависимость входных данных от выходных. Да, это де-факто означает запись всё той же формулы в типах.
UFO just landed and posted this here
Мы это уже обсуждали в более других статьях (и я только что заметил, что в комменте выше я еще и переставил местами вход и выход, ох не надо писать комментарии бегом).

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

Возьмите какой-нибудь действительно нетривиальный для тестирования случай, скажем, ГСЧ — и попробуйте обосновать, что написание тестов для этого как-то отлично от «программу два раза писать». Вернее, даже две разные программы, что еще сложнее.
UFO just landed and posted this here
нет. писать тесты — это писать две программы: основную и тестирующую

а не один и тот же код два раза

Это еще хуже с точки зрения временных затрат.

типами можно заменить самые примитивные тесты

«Примитивность» зависит исключительно от выразительности системы типов. Типами явы, действительно, много не покроешь. Типами тайпскрипта покроешь гораздо больше ситуаций. Типами идриса — еще больше.

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

В общем виде юниттестов практически не бывает в реально жизни. Большинство тестов в той или иной мере интеграционные.

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

ГСЧ — редкая задача.

Http-сервер — редкая задача.
UFO just landed and posted this here
как только в проекте становится более 500 строк кода — временные затраты на его развитие с TDD остаются теми же, без TDD продолжают расти экспоненциально

Мой практический опыт работы с очень большими проектами, существовавшими без единого теста — говорит о том, что вы заблуждаетесь.
Проект с автотестами развивать действительно дешевле (экономия на QA), но «экспоненциальные затраты», тем более временные — сказки апологетов незыблемости тестов.

http сервер — простая задача.

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

просто термин «юнит тест» очень размытый.

Да нет, крайне конкретный. Как и большинство терминов. Размытый он только в вашей картине мироздания, другие люди делают моки и не называют тесты без моков юнит-тестами.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Функция принимает ленивый список [...]

О, кстати, давно хотел спросить, а тут такая оказия. А как человек, не понаслышке знакомый с миром C/C++ — мирится с полностью ленивыми вычислениями? Это же в некоторых случаях прямо паровым молотом бьет по производительности?


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

UFO just landed and posted this here

интересно, а с выразительной системой типов, практично проверить что такая функция работает правильно без юнит тестов? Мне кажется что некоторые сущности проще описать типами а некоторые проще тестами (возможно без покрытия всех вариантов). Буду рад если ошибаюсь.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
типами можно заменить самые примитивные тесты — тесты функций никаких других функций не вызывающих и на одних и тех же данных возвращающих одно и то же значение.
Смотря как вы применяете типизацию. Если у вас например цикл производства какого-то агрегата, и операции объединены в цепочку / граф — нужно из каждой операции возвращать свой тип, например «кузов», «кузов грунтованый», «кузов покрашеный», а не просто «полуфабрикат». С такой системой типов можно частично отследить и ошибки композиции функций / операций.
UFO just landed and posted this here
Я согласен, но в вашем случае проверка, чтобы на покраску попадали только грунтованные кузова — делается в рантайме, а в моем случае — на стадии компиляции.
UFO just landed and posted this here
Если проверка делается в компайл-тайме, то тесты ненужны, а если в рантайме — привет TDD. Компилятор + статический анализатор (в некоторых языках это два в одном) — это бесплатный и очень быстрый инструмент поиска ошибок (в том числе и логических), и грех таким дешевым инструментом не воспользоваться. Тесты — это дорогой инструмент, требующий соизмеримых с разработкой трудозатрат, поэтому я выбираю компилятор. Возможно, кто-то выберет динамизм + тесты, не знаю.
UFO just landed and posted this here
OK, останемся каждый при своем мнении.
PS
Как правильно расшифровывается аббревиатура TDD :)
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
на мой взгляд, тесты должны иметь вид «отрывочной» таблицы истинности, проверяющей некие «экстремумы» того, что делает метод: с считаешь на бумажке и хардкодишь эти константы.
А в контексте РЧТ писать код и тесты должны два разных человека — это, своего рода, код-ревью: да, это всё ещё избыточно, но зато эффективно ввиду того, что разные люди допускают разные ошибки. ну это ИМХО лучше чем код-ревью, потому что когда просто читаешь чужую писанину — мозг иногда сам подменяет деготь сахаром просто потому что так мы (некоторые из нас) устроены.
ГСЧ — редкая задача.
Но вполне решаемая. У него какие параметры можно проверить? Тип распределения, дисперсию и т.п. — набираем массив значений и анализируем их. Код явно будет отличаться от кода самаго генератора ИМХО
А насчёт «тестирования типами» — я тоже считаю, что идея малость бредовая ;) этож каким неудачником надо быть, чтобы «совать не то и не туда»?!))))
UFO just landed and posted this here
стречный вопрос — как надежно протестировать функцию с особыми точками, например 1/x, или дискретное Фурье?

А очень просто. Что должно по ТЗ должно быть на выходе из первой функции, если в нее подать 0? Вот это и тестируйте.

Чтобы написать такой тест, нужно быть математиком, а тогда зачем тестирование?

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

А догадается тестер или не догадается это уже совсем другой вопрос.

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

Какое «такое»?
UFO just landed and posted this here
А как у вас вообще формулируются ТЗ? Как набор точек и значений функции в этих точках?

У нас есть набор разных документов и стандартов. Вот и на их основе и формулируются. А если ТЗ представляет собой функцию, то для функции видимо есть формула.

Если математик пишет доказательство, и это доказательство верифицировано машиной, например, то не нужно.

Вы же не проверяете теорему Пифагора для каких-то частных случаев так, «на всякий случай»?

Мы же не о доказательстве теоремы говорим, а о проверке правильности функции, которая эту теорему считает. Две сотни доказательств теоремы Пифагора совсем не гарантируют, что ваша функция считает ее правильно.
UFO just landed and posted this here
И как вы тестируете правильность реализации этой формулы согласно ТЗ?

По значению в разных точках.
Так это можно доказать формально

Что можно доказать формально? Что функция, которую написал Иван Ивано верно считает?

По значению в разных точках.

Что мешает просто захардкодить функцию для этих значений?

Почему код-ревью не мешает неправильной реализации формулы (и вам нужны тесты), но мешает хардкоду?
По постановке задачи код должен реализовывать функцию, а не хардкод нескольких тестовых значений. Всё, этого достаточно.
(Ну, можно вспомнить, что в embedded и не только — бывает, что функция задана таблично. Но там тогда вопрос в проверке корректности значений таблицы, тесты могут сверять её с реализацией реальной функции.)
По постановке задачи код должен реализовывать функцию, а не хардкод нескольких тестовых значений. Всё, этого достаточно.

Ну так и с формулой то же самое. По постановке код сложения (пример, который обожает rsync) не должен делать вычитание. Всё, этого достаточно. Рубим неправильный код на ревью, а тесты не нужны.
Вы совсем не о том.
Пример со сложением слишком туповат — не знаю, специально так он был выбран, или от балды. Но чуть-чуть усложним: представим себе код сложения не просто двух чисел, а в длинной арифметике. Уже вариант типа

int /* overflow is seen */
add(unsigned result[2], unsigned a[2], unsigned b[2]) {
   int t = a[0] + b[0];
   int icarry = t < a[0];
   result[0] = t;
   t = a[1] + icarry;
   int t2 = t + b[1];
   t = t < a[1];
   result[1] = t2;
   return t || t2 < b[1];
}


потребовал задуматься над тем, чтобы не перепутать переменные; и тестирования он таки требует (на комбинацию событий переноса из младшей части в старшую, и двух источников переноса из старшей во внешний признак переполнения — то есть минимум 6 тестов только по маргинальным случаям).

Логика выполнения достаточно проста. Вычитка должна подтвердить, что используется именно нужный алгоритм, что хотя бы все входные значения применены и нужным образом. А тесты — что он алгоритм передан правильно и тут нет каких-то тупых ошибок, которые не заметили ни автор, ни ревьюер. Например, я вначале поставил строку «t = t < a[1];» на одну выше — и тем самым затирал нужное целевое значение. (Да, выбор имён неидеальный. Это уже вопрос стиля.)

И ни вычитки/верификации, ни тестов самих по себе исключить нельзя.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Везет вам… у вас тех задание есть…
UFO just landed and posted this here
UFO just landed and posted this here

Так тесты и не гарантируют правильность кода.
Они лишь фиксируют его поведение при определённых входных данных.

А кто или что гарантирует правильность тестов?
UFO just landed and posted this here
UFO just landed and posted this here
Cовершенно неправильно считать, что TDD хоть как-то заменяет QA.

QA должен тестировать требования к приложению, а не код отдельно взятых компонентов. К исходному коду у тестировшика (в идеальном случае) вообще доступа быть не должно.
UFO just landed and posted this here

Конечно же можно автоматизировать QA, но к TDD это отношения не имеет.

UFO just landed and posted this here

Это называется регрессионный тест.


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


И всё это не имеет отношения к ТДД. ТДД — это не про тесты, ТДД — это про процесс написания кода

UFO just landed and posted this here

Еще раз. ТДД — это способ написания кода


Когда код уже написан, нет никакого тдд.


ТДД и тестировшики не связаны и тем более не взаимозаменяемы, потому что тесты, которые пишут разработчик и QA — разные и предназначены для разных целей.

UFO just landed and posted this here

В цикле TDD рефакторят ДО того, как фича попадёт к QA

UFO just landed and posted this here

Это в какой вселенной QA доверяет разработчику?

UFO just landed and posted this here

Нафиг тогда в такой вселенной нужен тогда QA вообще?


В реальном мире QA не верит разработчику и покрывает требования (а не код) независимо.

UFO just landed and posted this here

А я вот понял, что мы просто из разных вселенных.

Это про BDD больше чем про TDD. QA юнит-тестами обычно не занимаются вообще или только косвенно, запуская их перед тем как приступить к тестированию поведения.

У TDD на выходе два артефакта: работающий код и зелёные регрессионные тесты. Запуск этих тестов после "релиза" уже QA

UFO just landed and posted this here
Ну, навскидку:

1. QA запускает другие тесты, не те, что писали программисты — у нас именно так. Они нам не доверяют, и это правильно. (В наших реалиях они к тому же проверяют такие вещи, до которых у нас руки, к сожалению, не дошли.)

2. QA проверяет не только формальное выполнение тестов, но и их адекватность. Они могут напускать, например, мутационное тестирование — с проверкой, что случайные правки кода (как основного, так и тестов) что-то ломают.

3. QA делает обязательный круг ручного тестирования верхнего уровня на каждый релиз (и регулярно таки что-то находит).

4. QA меряет производительность, делает пробы типа восстановления всей системы после отказа отдельных компонентов…

TDD всему этому ортогонален.
UFO just landed and posted this here

QA "пишет" тесты используя свой инструментарий. Ну например SOA Test.


А в коде разработчика QA делать нечего. Как можно аудировать код, в котором ничего не понимаешь?

но я такого не встречал даже в больших компаниях

От размер компании слабо зависит, наверное. Позиции типа QA Automation engineer или даже lead не единичны. Я уже воспринимаю как норму, если на десяток разработчиков есть хотя бы один QA, основная обязанность которго писать тесты, поведенческие, приемочные, e2e и т. п. (разница расплівчатая).


И вот ту проверяется, честно работают по TDD или нет. Если на найденный QA баг (неважно вручную или автотестами высокого уровня), разработчики сначала пишут падающий юнит-тест (воспроизводят баг по сути на низком уровне), а лишь потом баг фиксят так, чтобы юнит-тесты проходили, то честно работают — сначала дополняют или изменяют требования зафиксированные в тестах, а потом их реализуют в коде.

Увы, это толком не проверяется — может, назначеный программист вначале сделал исправление, но описал это как затраты времени на investigation, потом нарисовал тест якобы без исправления кода, а потом закоммитил их в обратном порядке. Вы не узнаете это со стороны, если не будете мониторить активность по минутам (что неподъёмно для любой реальной конторы).
Поэтому, test-first требование хорошо выполнимо на бумаге, но никак — в практике, и для ревью приходится смотреть на суть предложенных изменений как в рабочем коде, так и в тестах.

А это не требование должно быть, а идеология что ли, которую команда разделяет.

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


Это не считая кучи тестов самих QA, как интеграционные и более высокоуровневые, так и юнит. Например, по TDD на функцию сложения пары чисел без особых требований, будет только 1-2 теста "положительных", QA могут добавить юнит-тестов на какие-то едж кейсы, зафиксировать поведение при переполнении например.

UFO just landed and posted this here

Изначально "релиз" был в кавычках у меня, то есть разработчки подготовили релиз-кандидат и отдали на растерзание QA. Я знаю, что есть компании где QA проверяют релиз уже на проде, или вообще QA нет, но сам в таких не работал. А так QA запускают на CI полный набор тестов, а не на каждый коммит они гоняются.

UFO just landed and posted this here

Слишком много интеграционных тестов вот и гоняются по 2 часа.

TDD оно до "релизнули", вот как релизнули начинается QA.

UFO just landed and posted this here

Нет, новая итерация начинается. Не очевидно?

UFO just landed and posted this here
UFO just landed and posted this here

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

UFO just landed and posted this here

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

UFO just landed and posted this here
В любом тесте невозможно перебрать все комбинации исходных данных, а значит такое тестирование ничего не гарантирует.

Мне кажется, тут будет в тему упомянуть property-based testing, который конечно не поможет перебрать бесконечное количество комбинаций, но попробовать рандомные значения из конечного набора ограничений (например, 1/x и все остальные) и отловить связанные с этим косяки вполне позволяет.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
А как это доказательсво проверит, ну незнаю, что pi изменилось. Допустим это не константа из библиотеки была, и я опечатался и ввел 31.4, а не 3.14.
Ну т.е. перед тем как говорить что формальная верификация спасет мир, надо доказать что верифицировать можно абсолютно все и что самое главное писать код, который поддается этой верификации. Ошибся ты где-то не верифировал одно из условий, в данном случае пи=4.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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


не знаю, я недостаточно знаю о пи

Клиент сказал: «хочу π = 5». И для этого клиента — π ≡ 5. И все. Больше знаний о π нет во всем целом мире.

UFO just landed and posted this here
UFO just landed and posted this here
типы — более длинные и менее удобные

Это, очевидно, не так. Чтобы проверить тестом столько же случаев, сколько проверяется статическим типом — вам в лучшем случае нужно написать примерно столько же кода (один тест). Скажем, чтоб проверить, что константа возвращает ожидаемое вами константное значение. Или другие экзотические ситуации, сводящиеся к единственному случаю.
В худшем же случае, читай, в большинстве реальных ситуаций — вам нужно написать бесконечное множество тестов, или же удовлетвориться тем, что вы написали что-то, что спасёт вас от проблем с некоторой (не стопроцентной) вероятностью. И даже так оно скорее всего будет не короче описания типа.

Ну а удобство — и вовсе субъективный параметр. Вам неудобно. Другим удобно.

кстати с типами вносить изменения значительно дороже.

Это, очевидно, точно так же не так, как и тезис выше.
UFO just landed and posted this here

Если мы говорим о TDD то там у тестов нет задачи покрыть все или почти все случаи, только, грубо говоря, все случаи, ради которых изменялся код. Вот в случае сложения return a + b будет только один тест типа 2+2===4, пускай и два 1+1===2. Какой будет тип чтобы проверить это и только это?

А зачем вам такие тесты? Вернее, только такие тесты? Чтоб реализовать их, строго по TDD, кодом
function sum(a,b) {
  if (a === 2 && b === 2) return 4;
  if (a === 1 && b === 1) return 2;
}

и радоваться жизни? Мы всё еще про программирование говорим?
UFO just landed and posted this here

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

UFO just landed and posted this here

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

UFO just landed and posted this here

Да ну? А потом к нам придет индонезийский магнат, разменять пару рупий — чисто вечерком в ресторане пообедать, а мы будем их складывать без увеличения итога? Хороший вариант, годный.


Но довольно опасный для бизнеса, к сожалению. А MAX_INT рупий — это всего 603 триллиона евро, вообще не сумма.

UFO just landed and posted this here
> а мы будем их складывать без увеличения итога? Хороший вариант, годный.

Не понял, к чему это. Можете расшифровать?

В JS, например, сколько не добавляй к Number.MAX_SAFE_INTEGER, результат не изменится.


const x = Number.MAX_SAFE_INTEGER + 1
console.log(x === x + 1)
//⇒ true
> В JS, например, сколько не добавляй к Number.MAX_SAFE_INTEGER, результат не изменится.

Формулировка некорректная: если добавить не 1, а 2, то уже будет изменение:

$ node                    
> x = Number.MAX_SAFE_INTEGER + 1
9007199254740992
> x+1
9007199254740992
> x+2
9007199254740994
> x+3
9007199254740996
> x+4
9007199254740996


но проблема понятна и известна, спасибо. Вообще, все финансово-бухгалтерские расчёты должны вестись в фиксированной точке с контролем переполнения; если JS это напрямую не предоставляет — использовать библиотеку; если не поможет — ну что ж, менять JS на что-то другое…

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


Ну вот просто целое какое-нибудь представьте себе. Ключ в базе, которой сорок лет, количество секунд с Большого Взрыва, не знаю.

В свете вашего уточнения, что проверяем реализацию под Javascript, получается, что тут надо вообще менять постановку вопроса, не только что проверять, но и как реализовывать.

Если мы говорим о проверке именно нижнего уровня реализации (сама машина JS), то надо убедиться, что сложение делает что надо с числами именно как double — тут будут несколько базовых тестов вроде 0+0, 1+0, 2+3, 3.14+(-2.71)… и тесты на поведение с особыми значениями (INF, NaN).

Если же мы предполагаем какую-то надёжную целочисленную арифметику в определённых пределах (заданных), то проверять надо корректность операций в этих пределах, и реакцию на выход за них. Если мы говорим, что 603 триллиона должно ещё представляться… значит, именно на это и должны быть тесты (обязательно — на поведение на крайних значениях). А как именно это будет реализовано в конкретном движке — уже его внутренние проблемы.
Пока человек специально не напишет тесты для всех возможных нумериков, с их пограничными значениями, такое сложение будет ломаться на неожиданных числах.

В задачу TDD не входит выявление багов в коде, который написан по TDD. Написали тесты по требованиям (в меру своего их понимания), написали код, чтобы тесты проходили, отрефакторили, если надо, так чтобы тесты не падали и новые тесты в рамках TDD пишутся только с новыми требованиями, которые могут появиться с выявленными QA багами.


Если разработчик, считает, что в требованиях есть предсказуемое поведение на неожиданных числах — он напишет для них тесты и, возможно, изменит код, чтобы, например сложение бросало исключение при переполнении, а не возвращало "случайное" число. Если не считает, то и хоть с TDD, хоть без, он проверку не сделает и результат будет "случайным"

В задачу TDD не входит выявление багов в коде, который написан по TDD.

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

TDD гарантирует, что а) зафиксированные в тестах требования исполняются на момент "релиза" б) не перестают исполняться при дальнейшем изменении кодовой базы


И TDD не потворствует внесению проблемных мест в кодовую базу. Если разработчик подумал о крайних случаях, то он их добавит сначала в тесты, а потом их обработку в код. Если нет, то в коде они не появятся никогда.

б) не перестают исполняться при дальнейшем изменении кодовой базы

В общем случае это неверно, потому что после рефакторинга половина тестов превратится в тыкву. Просто для примера: Elixir явно запрещает документирование и тестирование приватных функций.


Если нет, то в коде они не появятся никогда.

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

Elixir явно запрещает документирование и тестирование приватных функций.

А почему, кстати? Разве приватный функционал не нужно тестировать?

Нет. Тестировать нужно интерфейс, а не детали реализации.

Ну так вы и будете тестировать интерфейс, только этот интерфейс приватный.

Приватный интерфейс — даже чисто семантически — оксюморон.

Ну почему же?

Пусть у нас есть модуль с реализацией кучи/пирамиды поверх массива. В функциях, изменяющих кучу, есть повторяющийся код, а именно — восстановление heap order. Этот код приватный, пользователю им воспользоваться нельзя. По вашему, из-за приватности этот код тестировать нельзя?
восстановление heap order

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


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

> Никого не интересует, как оно там внутри кучу восстанавливает.

В том числе если делает это некорректно?

> Тестировать надо интерфейс, иначе изменение имплементации на лучшую — раскрасит ваш CI в цвета камбоджийского флага

Такие тесты меняются, естественно, вместе с реализацией.

Если оно это делает некорректно, но на интерфейсе это не сказывается, значит оно это делает корректно. По-моему, это очевидно. Если черный ящик всегда отвечает «4» на вопрос «сколько будет 2×2», то даже если внутри ведьма жжет хвою, и считает кучки пепла — все в порядке.


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

> Если оно это делает некорректно, но на интерфейсе это не сказывается, значит оно это делает корректно.

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

> По-моему, это очевидно.

Формально — да. Практически же такая очевидность бесполезна, на уровне пытаться искать пропитание методом «если долго держать рот открытым, в него залетят мухи» — мухи залетят, но слишком поздно и редко.

> Тесты, которые меняются вместе с реализацией никому не нужны, кроме, может быть, разработчика этой конкретной реализации.

А потом ещё много лет — того, кто её сопровождает.

Единственный способ сопровождения работающего приватного метода – это полностью его переписать. Иначе у вас с архитектурой беда.

Единственный способ сопровождения работающего приватного метода – это полностью его переписать. Иначе у вас с архитектурой беда.

А если работает, то зачем переписывать? Потому что приватный? Или потому что в тех языках программирования, которые вы используете, нельзя без костылей приватный функционал тестировать?

Ни с костылями, ни без костылей тестировать приватные методы нельзя. Это очевидный антипаттерн.

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

косвенным образом

Разумеется. Это называется «тестировать публичный интерфейс».

Конечно, можно тестировать приватные методы через публичный интерфейс. Вот только польза от таких тестов сомнительна: тесты получаются сложные и плохо сопровождаемые.


А чтобы проблем с тестированием приватных методов не было, достаточно следовать принципам SOLID. В классе присутствует какая-то логика, которую нужно тестировать отдельно? Значит, нужно выделить ещё один класс.

Проблем с тестированием приватных методов никаких нет: приватные методы адекватные люди не тестируют.


Существование сущности, которая не используется никем, кроме публичного интерфейса — тоже очевидный антипаттерн. Я не знаю, что такое SOLID, и знать не хочу, но я точно знаю, что если что-то снаружи не видно, они ни в отдельном тестировании не нуждается, ни ушами торчать ниоткуда не должно, иначе любой рефакторинг — превратится в му́ку.


Ну и да, что такое «класс» вообще? Я с таким понятием уже лет десять не сталкивался.

В общем случае это неверно, потому что после рефакторинга половина тестов превратится в тыкву.

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


Дык вот же оно: добавлении кода общего назначения, в отсутствие кода, работающего с граничными случаями, — это и есть «внесение проблемных мест»

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

От рефакторинга зависит.

Не зависит. Если не называть рефакторингом переименование параметров методов, конечно. Рефакторинг влечет за собой изменение структуры кода, и, как следствие, удаление методов, которые волокут за собой тесты. По TDD тестировать только интерфейс (что является единственным разумным способом тестирования вообще) — очевидно, не вариант.


Ну то есть, я-то как раз так тестирую: написал пару тестов с отладочным выводом для несуществующей пока реализации алгоритма на несколько тысяч строк, написал пару тысяч строк, прогнал тесты, дописал. Но это и близко не TDD (и слава богу).

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

> не изменяющее его поведение

Для внешнего наблюдателя, не для внутреннего.

Тесты модуля (они же юнит-тесты) и есть такой внешний по отношению к модулю наблюдатель.

Нет, потому что внешний наблюдатель тут — заказчик (или его представитель) интерфейса компонента, или целой программы. А внутренний знает внутреннюю специфику.

Про целую программу разговор отдельный, TDD не про это.


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

UFO just landed and posted this here

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

UFO just landed and posted this here

Но к рефакторингу это уже отношение не имеет. Для изменившихся требований естественно меняем тесты. Для новых требований пишем новые тесты.

UFO just landed and posted this here

Ну так, приходит новая задача, видим, что без рефакторинга никак. Делаем его, убеждаемся, что тесты всё ещё зелёные? коммитимся, может отдаём в QAили даже на прод. И начинаем менять тесты под новые требования. Потом даем их зелеными, ещё немного рефакторим и отдаём QA

А вы попробуйте понимать пример не буквально, потому что буквально «кто-то может неправильно написать функцию сложения» не работает. Буквально это никто не пишет, но это ж не мешает вам приводить это как пример для разбора полетов о тестах и типах.

В реальности же есть задачи, которые были плохо поставлены, плохо квалифицированы, или же вообще из разряда «нужно что-то сделать, сделаем — узнаем, насколько мы ошиблись». Например, проверка прав:
it('must be editable by admin', () => {
  const admin = UserAPI.getAdmin(); // это всё замокано, понятно дело
  isEditable(admin).should.be.true;
})

И вы радостно написали код:
isEditable(user) {
  return user.name === 'Administrator';
}

Ну а чё? В песочнице всё так, и администратор, действительно, только тот, кто так и называется. А в реальности — неа. В реальности оно вообще всё должно проходить через подсистему вычисления прав, потому что и админы не всесильны, и все остальные не бесправны.

Вы, конечно, можете сказать, что задача была поставлена неправильно — ну так придумайте, как вы будете рассказывать об этом условному программисту №2, который придёт на проект, увидит тесты и код, но никак не увидит, почему тесты именно такие, какие они есть. Равно как и программист, пытающийся разобраться в условной функции returnValue() не увидит из тестов «2, 2 should return 4» и «1, 1 should return 2» что там вообще-то подразумевалось сложение.
UFO just landed and posted this here
в описанном случае проблема растёт из области «это всё замокано понятно дело»

Нет конечно. Это вообще не имеет значения.
Проблема растёт из того, что если у вас TDD, то как только кто-то написал плохой тест (или несколько) — неважно по каким причинам, но едва ли относящимся к «намеренному вредительству», так потом полностью в рамках TDD по этим плохим тестам будет писаться плохой код. А чтоб выйти из этого порочного круга — нужно будет выйти за пределы TDD и пойти заново разобраться с тестами: найти плохие, заменить их на хорошие, и так далее.

Поэтому каждый случай, когда «я просто пока что протестирую это через 2+2=4 и 1+1=2» — это случай, который в будущем может дать по голове. Из этого «пока что» вытекает «я просто пока что напишу код, который возвращает 4 и 2 в этих ситуациях», а потом много позже оказывается, что всё на самом деле должно быть совсем не таким.
UFO just landed and posted this here
и тест и код пишет один и тот же человек.

Разумеется нет. Это будет верно только для нового кода.
Для уже существующего — тесты и код будут уже написаны кем-то, необязательно тем же, кто пришел с попытками всё исправить. И вот он приходит в код с проблемой «этот код на аргументы 3 и 3 должен возвращать 6, а он не возвращает!». Видит тесты. Видит код. Напишет ли он сложение нормально, или добавит еще один тест (возвращать 6 на 3, 3) и еще один иф в код?

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

А если не разобрался — то разбираться надо следующему программисту, и так далее. В реальных ситуациях я видел, когда разбирался только четырнадцатый программист (по истории коммитов), а 13 перед ним просто втыкали очередной костыль, исправляющий конкретную проблему, которую поставили перед конкретно ними.

Тесты там тоже были, условно говоря (через public static main() в яве, потому что древние времена и такие же нравы), но помогало ли это? Да нифига.
UFO just landed and posted this here
с типами ситуация ровно такая же

Если вы пойдете вверх по ветке до изначального комментария, то обнаружите, что именно что не такая же: выразить в типах «1 и 1 должно возвращать 2» и «2 и 2 должно возвращать 4» — это однозначно длиннее, чем выразить сложение через его свойства. Типы описывают происходящее в категориях, а не в случаях.

PS: Разумеется, есть и случаи, где рассуждать о предмете в категориях будет менее удобно, чем другими способами (да всё тот же ГСЧ, например, о котором легко рассуждать статистически, и куда тяжелее — в терминах конкретных тестов или категорий). Ну да никто и не говорит, по крайней мере здесь, что другой TDD (Type Driven Development) — это серебрянная пуля для любого кода.
UFO just landed and posted this here
Вы специально избирательно не прочли, что в моем комментарии написано юнит-тесты, да?

Только не надо сейчас про «размытую грань».
UFO just landed and posted this here
например при рефакторинге типы бесполезны

Из чего это вытекает? У вас какое-то особое представление о рефакторинге (как оно у вас о тестах, о http-серверах, и многом другом)?

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

И даже отложив в сторону зависимые типы и всё, что можно с ними сделать — даже банальные статические типы а-ля Java при рефакторинге радикально сокращают объем тестов, который у вас что-то там с хорошей вероятностью проверит о контракте. С невыразительными типами вам нужно будет написать тесты только на случай алгоритмических проблем, без типов вам придётся вдобавок к этому писать тесты на всевозможные невалидные входные данные.
> или например типы не выявят что порядок действий нарушен.

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

Когда это доберётся до мейнстрима, однако, неясно.
UFO just landed and posted this here
выше habr.com/ru/company/ruvds/blog/486684/#comment_21235570
был пример с формальной верификацией формулы площади круга, если мы говорим о вредительстве, а я не понимаю его смысла когда тесты пишутся для себя, то верефикация и типы тоже не спасут

circleArea 1 = pi
circleArea x = x*x

формально такая функция удовлетворит доказательству
unitCircle: circleArea 1 = π

quadraticScaling: { x₁ x₂ k: Radius }
-> (x₁ = x₂ * k)
-> circleArea x₁ = circleArea x₂ * k ** 2

квадратичное увеличение есть, pi для единичной окружности есть. только вот хрень функция считает

UPD:
На самом деле я ошибся, и не пройдет такая функция верификацию.
circleArea 2 != circleArea 1 * 4

Но не суть, можно при желании и верификацию поправить, мы ж вредители.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Вот в случае сложения return a + b [...]

Это же JS, не? Тогда вот.


const x = Number.MAX_SAFE_INTEGER + 1
console.log(x === x + 1)
//⇒ true

Так, эти тесты поломались, несите следующие.

TDD — это инструмент для бедных

Я бы посмотрел на это немного с другой стороны: со статической типизацией TDD ещё богаче! :)

Мне вот что интересно — почему в подобных случаях "доказательств" в качестве примера обычно берут какю-то фигню в постановке задачи, которая пишется на 5-10 строк и на этом примере показывают "как всё клёво". И, типа, доказано! Вот вам и тесты и код и рефакторинг. Красота! Кушайте! Ну почему вы не кушаете?
Тогда как в реальном проекте бизнес зачастую требует фичу "чтобы это и это и ещё вот это было" и когда начинаешь писать эти 5-10-20 функций, а потом думать, что на эти 5-10-20 функций надо написать ещё 10-20-50 для тестирования — то TDD уже становится не столь привлекательным. Потому что бизнес требует ВЧЕРА, а на аргументы "тестирование", "чистый код", "рефакторинг" — смотрит на тебя как на идиота, которому нужны шашечки, а не ехать. Как будто ты фигнёй занимаешься для своего удовольствия, а единственные кто работает — это они. И они очень занятые, а у тебя так дохрена времеи, что ты в бирюльки играешь тут.
Хорошо если начальник/тим лидер — адекватный и сможет им доказать, что "это нужно", а если нет? (и один хрен — они будут на эти аргументы смотреть скептически, так что работает только начальниковское — мы будем делать так или идите в задницу и нанимайте других ПОГРОМистов).

Потому что бизнес требует ВЧЕРА

Каким образом?
Возможно стоит поговорить с тем кто требует, объяснить что вчера прошло, расставить приоритеты.

а на аргументы «тестирование», «чистый код», «рефакторинг» — смотрит на тебя как на идиота, которому нужны шашечки, а не ехать

Это часть процесса разработки, а не что-то, что нужно постоянно согласовать в с бизнесом.
Это часть процесса разработки, а не что-то, что нужно постоянно согласовать в с бизнесом.

С точки зрения пока ещё большинства людей из бизнеса всё, что не относится к внедрению новой функциональности или изменению старой для следования новым тендециям бизнеса или починки багов. Увы.
И казалось бы — да какое им дело до внутренностей проекта, если это им не видно — взял да поменял тихонечко. Да, это работает, но проблема возникает если требуемые изменения слишком глобальны, чтобы это можно было починить парой фиксов за один спринт. Приходится выбивать время из бизнеса.
Взять средний Enterprise продукт — куча кода с техническим долгом, legacy и сотнями и тысячами todo:"надо отрефакторить впоследствии" и todo:"вставил дамми-функцию — надо переписать". Порой (особенно если ПО писать начинали ещё в 90-тых) — сильно связанные. Любой decoupling — затрагивает сотни файлов, а те в свою очередь — другие сотни файлов. А значит — компиляция не проканает пока ты все концы не "подвяжешь" хотя бы на дамми функции.
И в подобном продукте — TDD мог бы помочь… Если бы он был! Но писать его с нуля обходится в статье и кроме того означает не писать полезные для бизнеса функции какое-то время. То бишь вообще.
Добавим сюда — усилия QA, которые вы обязаны предупредить и которые должны порой вручную перетестировать дофига функциональности…
Вообщем почувствуйте на своём горле массаж руками уже двух подразделений (Бизнес и QA) и поймите, что разработчики "не любят" TDD по весьма очевидным и уважительным причинам.

UFO just landed and posted this here
аргумент про ВЧЕРА работает только для маленьких проектов либо для проектов на начальном этапе

Нифига подобного. Аргумент "ВЧЕРА" работает со всеми видами проектов при низкой культуре знания бизнеса о процессах IT.


это ВСЕГДА рефакторинг и ВСЕГДА

Если имеется в виду постройки ПО по процессу "ВЧЕРА" — имеет место громадный технический долг и отсутствие выделения времени хотя бы на какой-то ГРАМОТНЫЙ рефакторинг в принципе, поэтому рефекторинг рефакторингом, но убирание старого костыля и вставка нового — рефакторингом считаться не может.

UFO just landed and posted this here
UFO just landed and posted this here
Ну вот вы сами и ответили — некомпетентность бизнеса ведет к нарушениям процессов в угоду скорости.

Бухгалтерия же не считает только дебет, чтобы быстрее было.
UFO just landed and posted this here
можно смело раскладывать в продакшен в пятницы, перед праздниками

Если нет надёжной процедуры отката, у вас проблемы посерьёзнее скорости разработки или обеспечения качества фич


с типами в разы дороже и качество хуже или результат вовсе не достигнут

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


Как гарантировать, что пользователь библиотеки проинициализировал её правильно, заполнив все необходимые поля?


Тесты на код пользователя писать? Заставить его самого писать себе тесты по документации?


Можно конечно в рантайме проверять и бросать ошибки в рантайме вида: "Возраст должен быть числом", "Не знаю поля Nme, возможно вы имели ввиду Name?" Отладка может быть долгой.


А можно просто сделать типизированный конфиг, который просто не позволит собрать билд с неправильными параметрами или опечататься в значении поля.

UFO just landed and posted this here

И сколько же раз надо запустить программу, чтобы понять что конфиг корректный?


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

UFO just landed and posted this here

Будет ошибка десериализации очевидно, а не стектрейс в случайном месте.

UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

Так у бухгалтерии все сроки расписаны и процессы налажены уже десятилетиями: сдача квартального отчёта, годового, выплаты зарплат и т.д. Ситуации дополнения задач в этот пул имеются, но в целом — все критические дедлайны устоявшиеся.
Сравним это со взаимоотношения бизнеса и IT (т.е. уже ситуация, что кто-то от кого-то требует сроки), а именно ситуацию, когда бизнес уже "что-то кому-то продал" и сидят довольные, празднуя с шампанским удачную сделку, но вот теперь IT должен сделать из говна конфетку ровно к сроку Х (который определили даже не проконсультировавшись с IT) или внедрить новую фичу опять же к сроку Х, которую уже продали, чтобы бизнес не выглядели жуликами. И при этом надеяться, что внедряемая фича не рухнет хотя бы на презентации.

когда бизнес уже "что-то кому-то продал" и сидят довольные, празднуя с шампанским удачную сделку, но вот теперь IT должен сделать из говна конфетку ровно к сроку Х (который определили даже не проконсультировавшись с IT) или внедрить новую фичу опять же к сроку Х, которую уже продали, чтобы бизнес не выглядели жуликами.

Скрам существует с середины 80х и позволяет с цифрами в руках наглядно показать какое количество функционала может быть готово к определенному сроку. Или наоборот — когда будут готовы все требования.


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

У меня лет 10-12 назад была ситуация с международной компанией (казалось бы — педантичные и пунктуальные немцы), которая "продала красивую обёртку" — т.е. набор фич, которые были в принципе ещё не реализованы.


Сноска

Был продукт, который завоевал свой место на рынке и готовил силы на следующее завоевание. Предыдущее — позволило нанять топ-менеджмент со страшными зарплатами в год, которую я, боюсь, не заработаю за всю жизнь (ну да я пессимист). ХЗ зачем боссам компании понадобилось это, но… увы произошло.
В свою очередь нанятые топ-менеджмент — нананимал "себе помощников", что тоже прошло без внутреннего сопротивления боссов компании. И они начали продавать. Что делали, кстати, профессионально — ибо продали то, чего не существовало, что вполне логично, ибо продавали Программный Продукт.


Когда стало ясно, что "это они поторопились", бизнесу были озвучены риски и был задан вопрос — что важнее "поставить вовремя" или "поставить качественный продукт"? Бизнес ответил "поставить вовремя"!
"ОК" — сказали программисты и реализовали вовремя.
Знаете что потом случилось?
На версию с набором проданных фич — спустя три месяца пришлось выпускать патч, после чего, спустя полгода, ещё один, но страшно было не это. Страшно то, что с трудом заработанное доверие потребителей — было подорвано. Было даже несколько отказов, а продукт своё место на рынке подрастерял настолько, что пришлось ему закрыть как минимум наше подразделение, чтобы сохранить своё (немецкое) без разгона. Англичан, кстати — тоже разогнали.
Самое смешное, что нанятое бизнес подразделение — не разогнали. Оно постепенно разошлось само спустя пару-тройку лет. Самая главная нанятая тётка — ушла в другую компанию "продвигать новые горизонты".


Ну и выводы:


  1. Фразой "озвучить риски бизнесу" — нельзя обольщаться. Потому что у бизнеса какое-то "своё мышление" и интерпретация ответов IT-команд. Вплоть до того, что твоя фраза "мы только на ресёрч потратим две недели" может быть истолкована как "через две недели — будет готов прототип". Сегодняшнее "впарить" — для них может быть важнее "репутационных потерь" потом. Они могут думать что-то типа "ай, выпустим дополнительный фик или патч" или "ай, подсадим клиента на сервис поддержки и будем потом регулярно доить" — не суть. Важно то, что они не программисты, не IT-шники. Разгребать за ними — будут совсем другие люди. Исполнители головой и руками того, что они всего лишь пообещали языком.
  2. Нанятый бизнес (даже те, которые ранее работали в крутых компанях), которые "ищут новые челленджи" и приходят, чтобы "сделать хорошо" или "сделать ещё лучше" — увы ЗЛО. Да, список компаний, где они "раздвигали горизонты" может выглядить очень солидно и красиво, но важнее то, что случалось в этих компаниях во время и после того, как они оттуда ушли. Да, ты мог/ла порешать одну проблему/"раздвинуть горизонт", но этим — породить ещё десять. Но ты про них даже не узнал/а, потому что сделал/ла и ушёл/ла, а все остальные остались разгребать то, во что ты даже не вник/ла. Поэтому — свои кадры лучше всего готовить самому. Да это дорого, но зато специалист будет "в теме" и бесценен (в позитивном смысле).
  3. Ну и то, что это был очень ценный опыт на самом деле. Опыт разработки в стрессовой ситуации, опыт увольнения после того, как тебе вещали "компания это семья" (бесценно!!! Рекомендую каждому! Потому что это меняет мировоззрение кардинально), опыт взаимодействия с бизнесом (ты сказал "а", за тебя додумали — весь остальной алфавит, а потом вменили тебе это в вину), ликвидация иллюзий, срыв шаблонов и куча ещё всего разного.

ну вот опять — некомпетентность бизнеса привела к нарушениям процессов в угоду скорости и отсюда повылезали все проблемы.


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


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


"Горе-бизнесмены", пообещавшие звездолёт за неделю сдаются сразу в топ-менеджмент (было 1 раз всего)

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


Я потому и писал — надо растить своих специалистов. Дорого вначале и есть риски потерять, но зато — твой. Так не хотят.

Пусть жулики выглядят жуликами. Это им полезно

Если вы сначала пишите код, а потом думаете покрывать ли его тестами, то к TDD это не имеет никакого отношения. Написали один тест для одной функции (без ветвлений и часто циклов) написали минимальную реализацию, подумали что следующее, написали или изменили тест, написали реализацию и т. д.

UFO just landed and posted this here

А потом отрефакторили реализацию, когда стало понятно, как правильно, и 99% тестов пришлось выбросить, потому что они внезапно тестируют больше несуществующие функции.


Продуктивненько.

UFO just landed and posted this here

Это хорошо, если TDD вводится с самого начала проекта, а если нет? Если есть проект и хочется перевести его на TDD разработку?


Но даже в классической схеме — когда начинаем TDD тоже есть большой такой косяк о который бьются.
Вот есть приличных видов фича. Вы её получаете и садитесь писать для неё тесты. Для одной функции, второй… двадцатой.
Проходит неделя. Вторая. Спринт заканчивается, а конечного функционала — нету. То бишь — есть набор разрозненных функций, которые работают и условно оттестированы вашими же искусственными тестами. Но всё в одно работающее решение — не собрано (напомню, что фича большая). Кроме того в процессе собирания — есть шанс, что прийдётся часть функционала менять (кто не ошибается? Допустил неточность в проектировании и вуаля — изменения), а в процессе этого изменения — уже надо менять тестовые функции постфактум. Причём — каскадно. Одно меняет другое (с тестовыми функциями), захватывает третье (с ними же родимыми)… И понеслось. Добавление одного параметра — заставляет менять 5 функций. Немного, а если параметров 5? А если меняется интерфейс? В целом — работа ведётся, но функционала готового нет. Бизнес на написанные строчки кода — глазами лупает, истерит на тему "всех уволю!" и требуя "чтобы работало". Ведь с точки зрения бизнеса — продавать можно работающий функционал, необходимый потребителю, а не сопутствующую ему внутреннюю инфраструктуру, которая необходима только вам, чтобы удостоверять, что код "условно работает".
Апологеты TDD — подобные случаи сложного проектов обычно не рассматривают. Им ведь надо — кратенько показать, чтобы все вдохновились и ломанулись использовать и настанет дзен. Да и читать слишком большие портянки текста — мало кто будет.


Поэтому и "не любят" разрабы TDD — из-за банальной рутины, которая увеличивается каскадно. Любая смена интерфейса — ведёт переписку не пары-тройки, а минимум десятка функций (в том числе и тестовых).

Второе главное преимущество TDD заключается в том, что эта методология разработки заставляет программистов, ещё до написания рабочего кода, задумываться о том, что именно они собираются писать, и о том, как они собираются это писать.

Обдумывание кода перед его написанием приводит к тому, что разработчик по-настоящему погружается в суть нужд бизнеса и заранее учитывает пограничные случаи и возможные трудности при реализации соответствующих механизмов. В результате при написании рабочего кода усилия тратятся именно на то, что нужно. Кроме того, применение TDD приводит к тому, что разработчики планируют будущее устройство систем в плане их структуры и архитектуры

А без TDD разработчики перед написанием кода:
— разве не задумываются что именно они собираются писать?
— разве не погружаются в суть нужд бизнеса?
— разве не учитывают пограничные случаи и возможные трудности при реализации соответствующих механизмов?
— разве не планируют будущее устройство систем в плане их структуры и архитектуры?

При чём здесь TDD? Как можно начать писать код, без выполнения перечисленных выше пунктов?
UFO just landed and posted this here
А без TDD разработчики перед написанием кода:
— разве не задумываются что именно они собираются писать?
— разве не погружаются в суть нужд бизнеса?
— разве не учитывают пограничные случаи и возможные трудности при реализации соответствующих механизмов?
— разве не планируют будущее устройство систем в плане их структуры и архитектуры?
При чём здесь TDD?

TDD — инструмент который позволяет делать это эффективнее.

Эффективнее — в сравнении с чем? На элементарном примере представленным автором — я никакого повышения эффективности не заметил. Мало того — не видно существенного улучшения ни одного из вышеперечисленных пунктов. Как распланировалась будущая архитектура приложения применительно к примеру?
Формально, для адекватного сравнения нужно взять сработавшуюся команду, разбить случайным образом на две и предложить сделать 10-ток проектов регулярно меняясь: сначала команда 1 делает с TDD, а команда 2 без, а потом меняться. И замерять эффективность и время. Только тогда можно будет говорить о корректном сравнении и повышении эффективности. А в том случае как вы это предлагаете — это эмоции и демагогия. Вам помогло — вы молодцы. Но есть команды, которые справляются без TDD — и у них тоже всё хорошо.

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

Противники TDD тут наступают с двух сторон:
  • Нам/вам не платят за размышления, надо код писать, а не мозг выносить!
  • Мы и так думаем, нам эти процессные костыли не нужны.

Мне кажется, как раз в первом случае TDD нужен и может принести пользу.
UFO just landed and posted this here
Например имеем какой-то объект. В методе этого объекта производится сохранение каких-то данных в БД.

Ну мы вручную же биты не отправляем по сети, не пишем посекторно сырые данные на диск, верно? Мы ведь не драйвер пишем?


Значит есть какой-то апи для работы с базой данных. Это апи можно (нужно) замокать.
И тестировать исключительно наш код, а не компоненты инфраструктуры.

UFO just landed and posted this here

Ну будет лапша, которая не соответствует SOLID, тормозит разработку (билд занимает часы) и протестирована на единственной версии постгрис.


"а не отправили ли в постгрис ошибочный SQL"
Попробуйте ORM. Даже легковесные ORM предоставляют гарантии на уровне типов, что будет сгенерирован корректный SQL который вызывается на подходящей схеме БД. Остальные редкие случаи поймает приёмочное тестирование
лапша, которая не соответствует SOLID

Само по себе «соответствие SOLID» не означает вообще ничего. Первосортное дерьмо может полностью соответствовать SOLID и наоборот.

Возможность легко понять что делает код, возможность его модифицировать и легко локализовать ошибку — довольно ценные в разработке, не так ли?


Для того, чтобы достичь этого, существуют определенные практики. Самые базовые — это SOLID.


Несомненно, даже используя лучшие практики при определенном старании можно сделать дерьмо. Но когда им не следуешь, то даже стараться не надо.

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

А лего, значит, вы собираете с помощью паяльника?


Как тестировать фичу, когда ответственности смешаны?
DataAccess layer размазан по куче классов, как вы их будете тестировать? Базу в докере запускать?
Кстати, а почему вы уверены, что смена пароля профиля не приведет к увеличению налога? У вас нет теста на это!


Это только по букве "S" вопросы.

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


Ответственности обычно лучше не смешивать. Но если их придется смешать (а мир не идеален, увы) — лучше бы понимать, как это правильно сделать, а не упасть на спину с криком «а-а-а-а-а, не-солид-но, у меня лапки». Вот вам пример, чтобы два раза не вставать: про смену пароля как раз.


Авторизация 2FA, сторонний провайдер (пусть будет Okta). Этап внедрения. Фича? — Несомненно. SRP? — ну, удачи, чё.

А в чём проблема смены пароля при многофакторной авторизации? В консистенции смены на всех провайдерах? В рассылке уведомлений всем провайдерам об инвалидации? В том что будет многофазный процесс?


Вроде всё это выглядит решаемо. Я не очень знаком с этой конкретной предметной областью, но в своём опыте не припомню задач, где SRP был бы прямо явным антипаттерном.

UFO just landed and posted this here

Если код не соответствует общепринятым базовым паттернам, то это и не шашечки и не ехать. Это велосипед на костылях и замыкание процессов на себя. Потом проще выкинуть, чем внести изменения.


получается фигня, которая не держит нагрузку, с трудом удерживается DBA в рамках

А откуда гарантия, что ваши кривые SQL-запросы не уложат базу? Неумение пользоваться инструментом — не оправдание. При грамотном использовании оверхед на орм — на уровне статистической погрешности. А сколько оверхед на дублирование одного и того же кода?

не соответствует SOLID

Если код не соответствует общепринятым базовым паттернам, то это и не шашечки и не ехать. Это велосипед на костылях и замыкание процессов на себя. Потом проще выкинуть, чем внести изменения.

Честно говоря, когда кто-то говорит что у него код «по SOLID», возникают лишь сомнения, потому что, ну, не в SOLID счастье, а в чуть более базовых принципах.

Не могли бы Вы раскрыть свою мысль?


О каких более базовых принципах идёт речь с которыми можно пренебречь принципом единой ответственности например?

О каких более базовых принципах идёт речь

Связи и зависимости. Coupling/Cohesion.

с которыми можно пренебречь принципом единой ответственности например?

Пренебрегать SRP не нужно, просто он:
  • Как и вся аббревиатура SOLID, сам по себе не отвечает на вопрос, как сделать и какой код считать качественным;
  • Неоднозначен, часто трактуется по разному(да и виденье принципа у Мартина, собственно, чуть усовершенствовалось с годами, если глянуть старые и последние определения), и по причине озвученной в предыдущем тезисе, часто приводит к необоснованному догматизму.

Абсолютно согласен с каждой буквой.

Так в примере выше, rsync и говорил про high-coupling. Якобы метод который и меняет бизнес-состояние и сам себя в базу пишет (вполне конкретную) — это хороший дизайн.


То есть это не просто Active-Record, а ещё и прибитая гвоздями к конкретной схеме конкретной базе конкретного драйвера.

UFO just landed and posted this here
я с сомнением смотрю на статьи "мы заменили Pg на Mysql (на Монго, etc) и получили профит"

Ну и зря. Вы неверно транслируете заголовок с авторского на русский. Это просто-напросто означает «мы заменили легаси, подкостыленный долгими годами развития на новый код, построенный с учетом всех набитых шишек и получили профит».


:)

UFO just landed and posted this here

Так да, просто авторы статей не очень это понимают.

Вообще-то я имел в виду, что иногда даже просто проапгрейдить мажорную версию драйвера монги может стать той ещё занозой в…


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


Думаю, мы не первые, кому решение спускают сверху. Всего несколько лет назад проприеритарные реляционные базы (читай оракл) массово меняли на открытые и NoSql решения.


Ну и какое решение будет перевести проще — с выделеным слоем доступа к данным или то где каждый класс очень по креативному работает с хранилищем?


Коробочные решения, например, часто расширяют требования до поддержки ещё одной базы данных. А всякие кеши и очереди сообщений и вовсе меняются как перчатки. Мне сложно представить, как это всё отслеживать без слоя доступа к данным в модели ActiveRecord

Вообще-то я имел в виду [...]

Ну, я и отвечал не вам, так-то :)


какое решение будет перевести проще

Проще будет перевести хорошо спроектированное решение. Тут вопрос не в наличии, или отсутствии AR. Иногда со слоем данных проще, да. Иногда — сложнее. Иногда — невозможно вовсе.


У нас, к примеру, есть микросервис, полностью работающий на постгресовских ивентах и механизме LISTEN. Его, если бы нашему CEO втемяшилось лезть в разработку и выбирать БД, пришлось бы нафиг выбросить и переписать с нуля.


Несколько лет назад я приценивался к RethinkDB и Riak для другого микросервиса. Ваш AR в них умеет? Сомневаюсь. А я на коленке написал небольшой wrapper, который прикидывался mysql для приложения и общался с этой экзотикой.


Разные бывают случаи, разные.

UFO just landed and posted this here

Если там в цикле перебирается все строки таблицы вместо запроса с группировкой и/или аггрегацией, то DBA не спасёт.

UFO just landed and posted this here
плюс ORM дистанцируют программиста от необходимости знать и понимать что он делает и в итоге получается фигня, которая не держит нагрузку

Если всё-таки разобраться, что такое ORM и для чего нужны ORM-библиотеки, никаких проблем с производительностью не будет.

Эта фигня на поддержание в языке SQL-лайк синтаксиса

«Эта фигня» к ORM не относится. Не используйте Query Build если не хочется.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
А меня заголовок ввел в заблуждение. TDD — это еще и Time Division Duplex.
Пожалуй, краткое описание нужно было начать с «Что такое TDD?»

Спасибо
UFO just landed and posted this here
TDD дает избыточное перекрытие хрупкими некачественными тестами

Вы же помните что качество и хрупкость тестов прямо зависит от качества кода?)

Вот скажите мне, сторонники TDD, зачем мне тестировать юнит-тестами публичный метод репозитория, в котором вызывается десяток методов query-builder'а и собирается результат?

Ничего не даст. Изолируйте сборку запроса от любой логики, чтобы весь метод сборки покрывался единственным интеграционным тестом, а на логику есть юниты.
UFO just landed and posted this here
А по TDD мы должны бы были написать некачественные бесполезные юнит-тесты на это.

Нет, писать юнит-тесты на это могут советовать лишь отдельные фанатики такого же бесполезного 100%-го покрытия юнитами.
UFO just landed and posted this here

Ну вот, например, "используем TDD при написании бизнес-логики" — это, конечно, не "используем TDD везде и всегда", но и не "не используем TDD"

Любой высокочастотный пизг о безусловной пользе TDD — это лишь неоформленная рационализация прибивания липкого стикера острым гвоздем к белому вайтборду красивым молотком в навязчиво-авторитарной манере.
Тут много правильного в статье, но всё же останусь при своём мнении, что а) в основном тесты для разработки нужны в языках без нормального repl б) тесты для поддержки нужны авторам либ и фреймворков, остальных со временем они начнут лишь замедлять и будут закоменчены
Люблю тихих минусил.
Забыл добавить! Мне отлично зашли approval tests на одном проекте. Весьма рекомендую, это такое «TDD наоборот» при котором код становятся источником истины тащащим за собой тесты. Очень помогли при написании сложных расчётов.
UFO just landed and posted this here
Проблема TDD, что ему не учат с самого начала.
А потом, перестроить «мозг», на TDD очень сложно.
Вот и придумывают различные оправдания, чтобы не делать, то что не привычно. :-)

Проблема TDD (как и всех остальных «серебряных пуль», называющихся аббревиатурами) в том, что адепты из него (как и из всех остальных «серебряных пуль», называющихся аббревиатурами) — делают панацею и возводят в ранг «абсолютного блага».


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


А с кодом посложнее — все не так радужно; я зачастую понятия не имею, в какого монстра вот это все трансформируется в результате, какие функции вообще выживут, поэтому и TDD — в принципе использовать не могу. Но для CRUD — да, годится (вот только разработкой все это называть — язык не поворачивается).

Вот видите. Вы придумывайте очередное оправдание, чтобы не писать тесты.
Писать тесты, это как регулярно чистить зубы.
Оно как бы особо и не надо, но иногда бывают не приятные последствия.
Причем не приятные последствия могут быть если регулярно чистить зубы.
Вопрос только в вероятности возникновения неприятных последствий.
:-)
Вы придумывайте очередное оправдание, чтобы не писать тесты.

Простите, но ваши предположения о моих действиях абсолютно иррациональны (для изучавших французский: они идиотские).


Я о тестах вообще ничего не говорил. Мы тут обсуждаем TDD, а не тесты в принципе. Это методология. К существованию тестов отношения не имеющая вовсе.

За последний год делал несколько попыток писать по TDD (т.е. вначале тесты) — и каждый раз ничего не получалось.

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

Ведь самое главное, в TDD — это на 100% понимать какие публичные методы будет иметь объект(ы) (и, соответственно, взаимодействовать с другими объектами), а это, при разработке больших модулей весьма сложно. Нет, можно конечно пару недель просидеть исключительно за листочками бумаги, и детально расписывать объекты и их взаимодействие, делать детализацию до каждого метода, и конкретного функционала… Но, извините меня, кто на работе такое может себе позволить — за пару недель не написать ни строчки кода?

Я вот не могу. Соответственно мой единственный путь — прикинуть примерную структуру — начать писать код, и следом на него тесты. Постепенно добавляя новый функционал, расширяя/изменяя существующий код и дописывая тесты.

P.S. Ну и на практике наибольшая сложность — в принципе доказать, что тесты нужны, когда руководство хочет только «быстрей-быстрей-срочно-срочно»
Cогласен, как раз сейчас пишу сложный прототип, и раз в неделю вынужден серьезно переделывать внутреннее API. Поэтому тесты — только интеграционные, в пограничных точках, иначе разработка вместо месяца займет год. А вот когда приходилось вносить точечные изменения в чужой коммерческий продукт — здесь да, и подробное ТЗ, и автотесты, и т.д.

Всё верно. Тесты пишутся не для себя, а для других. Их польза раскрывается при сопровождении продукта и работе в больших командах.

Точно. Но в этом случае мы приходим к классической технологии, по которой тесты пишут не сами разработчики, а специально обученные люди в тесном взаимодействии с аналитиками / заказчиками. У меня очень успешный опыт именно такого разделения — разработчик думает о будущих фичах, тестировщик о качестве существующих фич, и оба получают з/п. Ну и старое правило — одна голова хорошо, а две лучше.
UFO just landed and posted this here

Мне кажется тут наоборот с TDD проще, TDD дробит все на небольшие шаги, необходимо добавить новый случай/функцию, написали тест (как раз момент когда requirements понятнее всего что, клиент то функции уже есть), реализовали случай.

Вы пробовали когда-нибудь писать библиотеки общего назначения с нуля?

Да, TDD заставляет сразу писать нормальный код. И поначалу это тяжело, потому что ломает мозг. При использовании TDD не получится написать монолит, а потом плеваться от сложности написания тестов.

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

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

Ну так в современном мире "качественный" означает не эффективный, а "легко сопровождаемый".


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

Ну так в современном мире "качественный" означает не эффективный, а "легко сопровождаемый".

Я про это и говорю. Между легко сопровождаемым кодом и кодом, который легко тестировать — программист вынужден выбирать второе в ущерб первому.

в современном мире "качественный" означает не эффективный, а "легко сопровождаемый"

Оп-па, вот это поворот. Если кучка среднего ума людей так считает, это еще не значит, что так обстоят дела «в современном мире».

UFO just landed and posted this here
писать модуль, который будет состоять из десятков объектов, сотен публичных методов — все становится совсем непросто.

Ведь самое главное, в TDD — это на 100% понимать какие публичные методы будет иметь объект(ы)

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

И лично мне, сильно проще накидать 10 объектов сразу в коде, как-то их связать по-быстрому, и уже из получившегося — думать и развивать дальше.

Видимо я человек которому важно оперировать реальными сущностями. И сразу получать фактический результат. А не так, что «я в уме прикинул, все будет работать»
В том то и дело — что где-то их держать так и так нужно — либо в голове, либо на бумаге, либо в коде.

И лично мне, сильно проще накидать 10 объектов сразу в коде

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

И ни одного ответа по сути. Интересно, они были намеренно выбраны такие? Или это чапековское Imago?

> применение TDD означает, что код работает именно так, как нужно программисту.

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

> Это — замечательный факт, так как он даёт серьёзную уверенность в устойчивости кода к каким-либо неблагоприятным изменением. Применение TDD позволяет программисту мгновенно узнавать о том, не испортилось ли что-то в кодовой базе в результате рефакторинга или расширения кода

Во-первых (от кэпа), никакие тесты не дают гарантию: они ловят свои частные случаи, и всегда надо считать, что есть гарантия нахождения поломки ну может на 95%, на 99, но никак не на 100. Во-вторых, описанное обеспечивается (в большинстве случаев) собственно наличием тестов, тем, что их запускают и на результаты — смотрят, а не тем, был тест написан до или после кода.

> Самотестирующийся код позволяет командам разработчиков по-настоящему пользоваться преимуществами, которые даёт им применение систем непрерывной интеграции и развёртывания кода.

Отличная фраза для рекламного проспекта. И опять — ничего из этого не требует ни TDD, ни отдельного test-first; оно требует достаточного покрытия кода.

> Второе главное преимущество TDD заключается в том, что эта методология разработки заставляет программистов, ещё до написания рабочего кода, задумываться о том, что именно они собираются писать, и о том, как они собираются это писать.

То же самое: это обеспечивается тестируемостью, а не TDD.

В статье ни слова не сказано про прямые недостатки TDD:

1. Test-first (как принципиальная компонента TDD) не позволяет разработать реально сложный алгоритм; с его помощью не получится, например, быстрая сортировка (да, пример радикальный, но позволяет увидеть границы). Пример вычисления по Пифагору в статье — великолепный тем, что он именно банален до ужаса — именно на такой банальщине принято восхвалять TDD.

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

2. «Вначале тест должен упасть» ничего не говорит, повторюсь, о том, почему тест упал — может, тестируемой функции вообще не было? И дальше это ничего не говорит об ответственности за то, что тест должен продолжать проверяться на осмысленность.

В классическом TDD объявляется, что
1) пишутся тесты до кода
2) тесты должны быть проверены на то, что они не проходят
3) пишется код для их удовлетворения.

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

Чтобы допустить в принципе ситуацию, когда код меняется, надо допустить, если использовать правило «test first», что новые тесты соответствуют TDD, но старые — нет, часть тестов может работать и до новой разработки, и после. А это значит, что они уже нарушают этот принцип — их нельзя проверить на корректность той проверкой, что «тест до написания упал => он, вроде, правильный».

Поэтому, или вы доверяете корректности уже существующих тестов согласно их коду, но тогда правило «сначала тест должен упасть» вообще теряет смысл, или вы вводите другие методы контроля корректности тестов (в моём случае это инверсии тестов, когда проверяется нарушение выходных данных при заведомых отклонениях входных данных). А в этом случае уже пофиг, были написаны тесты до или после: это вопрос административного контроля качества покрытия тестами, но не
собственно их выполнения.

Основное преимущество TDD совсем в другом. После того, как убедил начальство верхнего уровня постановить «мы работаем по TDD», никакой читер-халявщик типа начальника отдела не сможет сказать «плюньте на тесты, мы не успеваем». Ибо его тогда вжучат не за нарушение принципа программирования, а за значительно более серьёзное в его лестнице — нарушение приказа.

Но: как Вы думаете, какая часть из тех, кто собственно пишет код, строго соблюдает эти принципы, а какая — только делает вид, а на самом деле перемешивает код с тестами в любом порядке, лишь бы после этого были соблюдены приличия в репозитории, тикетнице и аналогичных местах? ;)

Грамотный подход к выбору любых средств и методологий тестирования начинается не с размахивания баззвордами вроде TDD, а с понимания базового принципа:

Для внутреннего контролёра (то есть самого разработчика, как человека, так и организации), тесты не обеспечивают проверку кода на корректность. Они обеспечивают условия для корректности верификации кода.

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

С пониманием этого, становится понятно, что важно не test-first, а чтобы тесты вообще работали (причём не только до момента, когда изменения приняты! они должны проверяться всегда); чтобы они реально проверяли нужную функциональность кода; чтобы они отлавливали те проблемы, которые потенциально вызваны именно конкретикой реализации (краевые эффекты); чтобы сама проверка тестами могла быть проверена и оценено её качество. После этого, test-first или test-after, падал у автора тест вначале или нет, писал он тесты по одному или все сразу — личное дело автора, как ему удобнее. Иногда надо написать тест заранее, чтобы подтолкнуть себя. Иногда это не получается, и надо писать код, чтобы понять, как его вообще проверять. Смотреть надо на финальные результаты разработки.

И с самого начала в качестве примера притянутый за уши кейз. Почему никто не рассказывает про ТДД на каких-то более реальных примерах? Ну, допустим — показать в рамках TDD реализацию 2048.

Давно покрываю код тестами. По TDD удобно фиксить баги и писать простые модули. Но делать что-то более сложное — мне не понятно как. Пока вопросов у меня больше, чем ответов.


  1. Легко покрыть тестом hello world из тьюториала. Но в реальном мире задачи сложнее в 90% случаев. Исходя из логики тдд, я должен до написания кода написать тесты и моки. Но как мокать то, чего ещё нет? Статьи говорят "продумать архитектуру". Но архитектура — это общий принцип, а не конкретные "потроха". А моки требуют конкретики. Я не знаю заранее, какими средствами я буду реализовывать нужный метод. Я не знаю, какие методы и библиотеки я применю для реализации и как они будут вызываться. Если я буду при проектировании об этом думать, это уже не TDD на мой взгляд, потому что продумать в деталях код и написать его — немного разницы.
  2. Если писать моки на все "потроха" это мешает одному из основополагающих принципов тестов — тестировать внешнее поведение, а не реализацию. И приводит к необходимости модифицировать кучу моков, например, при рефакторинге. Или оптимизации, не меняющей внешнее поведение метода.
  3. Unit-тесты предполагают, что под mock попадает все — БД, вызовы других методов и т.д. Но любая "высокоуровневая" функция на 99% состоит из вызова других функций. Особенно при использовании библиотек и фреймворков. Если все их подменить, в чем смысл теста? Проверить порядок их вызова? Но порядок ещё не гарантирует корректного результата.
UFO just landed and posted this here
TDD дает огромные преимущества в средне и дальнесрочной перспективе.

TDD по своей природе не может дать никаких преимуществ в перспективе длиннее пары часов. Почитайте, что ли, что такое TDD.

У меня противоположное мнение: разработка на основе тестов может быть способом максимального разделения труда между постановщиком задачи и конечным разработчиком.

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

Когда разработчик пишет тест сам для себя (а в материалах по TDD я иногда вижу именно это) — это напоминает старую добрую «разработку в одно лицо», к которой вместо постановщика задач (аналитика, функционального архитектора) добавляется лишняя бюрократия.

Может быть, конечно, я говорю это из-за того, что я не видел TDD во всей красоте и могуществе.
Когда разрыв между аналитиком и программистом в понимании сути задачи (и предметной области) настолько большой, что объяснить ее обычным языком достаточно системно становится невозможно, аналитику остается написать набор тестов — формальных ограничений, которым должен соответствовать фрагмент программы.

Юнит тесты не про бизнес и не про требования, а про процесс разработки.

То о чём вы говорите это приёмочные / end-to-end
Согласен.

Вот я не понимаю, зачем мне «грузить» разработчика необходимостью писать внутренние (не приемочные) тесты. И разработчики не понимают.

Аргументация «ну попробуй, а если не понравится — попробуй еще много раз до тех пор, пока не понравится» (которая приводится в статье) мне не подходит, поскольку я и так добиваюсь достаточного понимания разработчиками задачи за счет своей постановки и/или, в крайнем случае, написания для них таких тестов самостоятельно. Поэтому хотелось бы, например, увидеть описание сфер, в которых такой подход необходим.
А как ваши разработчики понимают, что их функции правильно делают свою работу? Все равно же как-то тестируют?
Ну кнопку Run, конечно, они жмут после программирования)
Ну это не тестирование конечно же никакое.
Вот я не понимаю, зачем мне «грузить» разработчика необходимостью писать внутренние (не приемочные) тесты. И разработчики не понимают.

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

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

Вы вот вроде руководитель проектов, мне вот интересно, как вы без этого обходитесь, риалли.
Вот я не понимаю, зачем мне «грузить» разработчика необходимостью писать внутренние (не приемочные) тесты. И разработчики не понимают.

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

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

На мой взгляд, некоторые практики из XP, такие как TDD, парное программирование или рефакторинг, невероятно полезны для обучения новичков основам профессионального (промышленного) программирования. Люди приходят после ВУЗов, где обычно критерий правильности решения в том, что программа выдает правильный результат в каком-то очень узком диапазоне входных значений. Качество кода мало кого волнует.


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

Вспоминается старая шутка: «некоторые программисты настолько ленивы, что сразу пишут код без ошибок».
UFO just landed and posted this here
А ещё, эта методика, скорее, философия, чем практика. До идиотизма можно довести любую нормальную идею. Разработка кода изначально подразумевает его анализ в плане будущего функционала — построение алгоритма уже даёт понятие, где могут быть ошибки и, обычно, это выход значений за допустимый диапазон, а это, в свою очередь, не будет видно на локальных тестах.
построение алгоритма уже даёт понятие, где могут быть ошибки и, обычно, это выход значений за допустимый диапазон

Ничего себе. Ну вот я пишу на языках, где нет такого понятия «допустимый диапазон». Зато код, который я разрабатываю, отличается тем, что никогда не ясно, какой будет там в светлом будущем алгоритм. Иначе, скорее всего, можно взять существующую библиотеку, или джуна посадить буковки на клавиатуре печатать по алгоритму. Я занимаюсь чем-то, что не имеет права называться «разработка»?

Ага, перечитал свой коммент — незаметно перешёл от общего к частному. Алгоритм, имеется ввиду, внутри тестируемых методов. Допустимый диапазон — это те данные, которые метод призван обработать и на выходе выдать результат. Он не может быть безграничным — чем больше диапазон, тем сложней алгоритм обработки данных.
И из этого вытекает то, что иногда надежней разбить один большой кусок кода на несколько маленьких — не писать тест для сложной функции, а создать несколько маленьких, в которых нереально ошибиться.
UFO just landed and posted this here
Допустимый диапазон — это те данные, которые метод призван обработать и на выходе выдать результат. Он не может быть безграничным [...]

const is_true = any => any === true

Диапазон принимаемых значений — безграничен.


Ну ладно, пора перестать придираться :) Так-то, в принципе, я согласен. Но не со всем.




Алгоритм, имеется ввиду, внутри тестируемых методов.

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

Когда тестировщик, молящийся на TDD, начал говорить мне, что без него я не напишу правильный код и напишу неправильный тест, который всегда будет зеленым, я понял, что он дурак. Потому что считает программиста «кодинговой обезьяной», которой обязательно нужны внешние правила, чтобы не схалявить и не облажаться. Еще бы этот человек свою работу делал хорошо

Я недавно набрел на прекрасный набор личных принципов одного человека: gist.github.com/stettix/5bb2d99e50fdbbd15dd9622837d14e2b

Цитата главки про качество:

Quality
I don’t care if you write the tests first, last, or in the middle, but all code must have good tests.

Tests should be performed at different levels of the system. Don’t get hung up on what these different levels of tests are called.

Absolutely all tests should be automated.

Test code should be written and maintained as carefully as production code.

Developers should write the tests.

Run tests on the production system too, to check it’s doing the right thing.


Вот под этим, скорее, подпишусь.

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

️ [тут есть эмоджик с перстом, указывающим вверх, но парсер хабра такая хабра] абсолютно. А еще иногда просто задача формализована как «имплементировать вон тот алгоритм из диссертации» и структура кода меняется 200 раз в час из-за неудачных подходов.

Дада, все аколиты TDD обычно не разработчики. Ну или может они из крупных контор, где все заформализовано по уши и есть возможность держать техписателей. Но так работают просто напросто не все. Умничать перед средней величины программистской группой, что надо иметь ту или иную методологию, которая НАМ ЗАПОВЕДАНА ПРЕДКАМИ, смысла нет — эти люди тащат проекты и им не до оторванных от реальности теорий. )
Дада, все аколиты TDD обычно не разработчики.

Недавно начала использовать TDD подход в проекте на Java, это помогает декомпозировать задачу и тут же верефицировать решение. На тренинге по TDD для меня стало большим откровением то, что для одной функции можно и нужно писать несколько юнит тестов, каждый со своим assert. Техписателей у нас нет, по крайней мере в моём отделе.

О чём и речь. Следование TDD принуждает сразу писать нормальный код, а не плохо тестируемый и сопровождаемый монолит. А потом приходит опыт, и необходимость в TDD для декомпозиции задачи отпадает — оно как-то само получается.

Оно. Как-то. Само… — вдумайтесь в эти слова. Это вот не тот уровень понимания, который я бы назвал профессиональным, извините.

Есть code monkey, которые (возможно!) через всякие там TDD приходят к мышлению software architect.

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

Мне вот одно интересно — ну написали вы юнит-тесты, прошла у вас итерация, выяснились поправки к архитектуре, вам кто-то дает время снова с нуля писать эти тесты? Потому что если вы agile, у вас все построено на итерациях, и если они медленные — то проект конечно идет куда-то, но оооочень медленно. На этом этапе легко словить прессинг от бизнеса и все ваши тдд, скрамы, DoD и прочие правильные штуки начинает сдувать ветром изо рта босса)
UFO just landed and posted this here
при разработке код пишется одновременно с тестом на него (иногда вперёд тест иногда вперёд код, но к git commit написано и то и другое)


ну так это разве TDD? =) это как раз более практичный и здравый подход — сначала как угодно (в том числе через прямое накоживание и рефакторинг) пришли к архитектуре, выдебажили на реальных кейсах (убрав проблемы с _настоящими данными_ на чужих апишках), закрыли что получилось тестами.

мы отказались от моков как класс. работаем в парадигме моки == зло.
соответственно в докерах подымается всё что есть в проекте и тестится.


ну, так тоже можно, конечно. Но зато надо мейнтейнить этот ваш докер и добавлять туда новые версии зависимостей. Думаю, вам стоит написать статью, если вы умеете это делать удобно.
когда код пишется без тестов, то огромное количество времени разработчика идёт на фиксинг багов которые возвращают QA и юзеры.
если часть этого времени тратить на написание тестов, то другая часть остаётся на дополнительную разработку.


я думаю вы все же путаете TDD и «код закрыт тестами». Естественно, все что написано, должно быть покрыто, иначе как ваши же коллеги примут ваш код в мастер?

Просто TDD — это «тесты вперед всего остального». Согласитесь, это очень сильно нишевый подход.
UFO just landed and posted this here
Согласно Вики, ваше определение неверно:

Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам. Кент Бек, считающийся изобретателем этой техники, утверждал в 2003 году, что разработка через тестирование поощряет простой дизайн и внушает уверенность (англ. inspires confidence)[1].

В 1999 году при своём появлении разработка через тестирование была тесно связана с концепцией «сначала тест» (англ. test-first), применяемой в экстремальном программировании[2], однако позже выделилась как независимая методология.[3].


UFO just landed and posted this here
Вообще в норме архитектор или кто-то похожий сначала должен переложить сведения заказчика на техязык в рамках всего проекта (или его большой части), а потом уже раздать на разработку отдельные блоки отдельным людям.

потом, если вы полагаете, что архитектор — грубо говоря просто ставит палки в колеса, то кто же обеспечивает «общую картину»?
UFO just landed and posted this here

Если на проекте 5-10 и более команд, они голосовать все должны или как?

UFO just landed and posted this here

10-40+ человек будут выбирать фреймворк или базу данных для системы путём голосования?

Вы путаете solution architect и software architect. Но вообще я за то, чтобы не было четких границ, кто что решает и где чье дело. Прозрачность десу.
UFO just landed and posted this here
Зачем всё сразу перекладывать? А как же CI/CD?

Вы о чем вообще? Я о том, что заказчик приходит и говорит — сделай мне, скажем, торговую площадку с такими-то параметрами по бизнесу.
UFO just landed and posted this here
На тренинге по TDD для меня стало большим откровением то, что для одной функции можно и нужно писать несколько юнит тестов, каждый со своим assert.

я бы не ограничивал тестирование лишь юнит тестами. Я например люблю запустить контекст спринга и вдоволь погонять сценарии через внешние интерфейсы, замокировав бекенд wiremock-ом, например — все в рамках «юнит»-тестов, без внешних зависимостей.
Отлично работает. А еще есть нагрузочные тесты на пропускную способность и так далее, чтобы не было мучительно больно, когда ваш O(n^2) отправится в прод ))
я бы не ограничивал тестирование лишь юнит тестами.

Конечно, помимо юнит тестирования нужны ещё интеграционные тесты, а также нефункциональные виды тестирования не будут лишними, а ещё все тесты должны быть автоматизированы ^^

> На тренинге по TDD для меня стало большим откровением то, что для одной функции можно и нужно писать несколько юнит тестов, каждый со своим assert.

Безусловно поздравляю с тем, что открыли в общем-то банальность, хоть и очень важную на практике. У нас всех есть какие-то заскоки сознания, что не видим простых вещей в упор; я их у себя сотнями насчитывал. Но это не аргумент принимать от этого весь TDD в целом:) потому что он — некорректная комбинация в общем корректных и полезных принципов.

> Техписателей у нас нет, по крайней мере в моём отделе.

Хм, а при чём техписатели? Вы про QA?

А в чём некорректность этой комбинации по-вашему?

В TDD, если следовать дословно, объявляется, что
1) пишутся тесты до кода;
2) тесты должны быть проверены на то, что они не проходят;
3) пишется код для их удовлетворения.

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

Чтобы допустить в принципе ситуацию, когда код меняется, надо допустить, если использовать правило «test first», что новые тесты соответствуют TDD, но старые — нет, часть тестов может работать и до новой разработки, и после. А это значит, что они уже нарушают этот принцип — их нельзя проверить на корректность той проверкой, что «тест до написания упал => он, вроде, правильный».

Поэтому, или вы доверяете корректности уже существующих тестов согласно их коду, но тогда правило «сначала тест должен упасть» вообще теряет смысл, или вы вводите другие методы контроля корректности тестов (в моём случае это инверсии тестов, когда проверяется нарушение выходных данных при заведомых отклонениях входных данных; может применяться мутационное тестирование, или другие методы со сходным результатом). А в этом случае уже пофиг, были написаны тесты до или после: это вопрос административного контроля качества покрытия тестами, но не собственно порядка их написания.

Отсутствие прямого указания, что код в первую очередь должен выполнять задачу по ТЗ, и лишь при выполнении этого — удовлетворять тест, приводит к прямому желанию сделать ровно удовлетворение теста, даже просто в варианте «вот такие входные данные — возвращаем вот такой результат», и ничего более — и само по себе TDD это не запрещает. Это уже тут говорили, но надо подчеркнуть.

В результате, дословное следование TDD позволяет делать только один вид кода: одноразовый неинтеллектуальный, без любых планов на развитие — как это назвали в другом треде на эту же тему, «сайтики-визитки». Любая осмысленная работа сверх этого приводит к какому-нибудь отклонению от суммарного принципа. Поэтому — лучше вообще не рассматривать его всерьёз, а выбрать другую базу.
UFO just landed and posted this here
> если вот так абсолютизировать/идеализировать то сам TDD невозможен:

Конечно, возможен. Там, где до написания всё 100% продумывается без необходимости исправления. Думаю, есть и такие места. Я даже помню, как студентом написал 200-строчную программу без единой ошибки на бумаге для другого человека и тот её с ходу сдал:)

> TDD — это когда тест разрабатывается ВМЕСТЕ с кодом.

Это вы уже сделали первый шаг по его переводу в применимый вариант, отказом от наиболее радикальных постулатов. Да, мы все именно так и делаем — работать же надо…

Как по мне, то и первое, и второе подходит для изменения существующего кода, как покрытого до того тестами, так и не покрытого. СОбственно не просто считаю а активно практикую.


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


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

Хм, а при чём техписатели? Вы про QA?

Техписатели упоминались в комментарии, на которой я отвечала:


Дада, все аколиты TDD обычно не разработчики. Ну или может они из крупных контор, где все заформализовано по уши и есть возможность держать техписателей.
UFO just landed and posted this here