Как работает TDD
2020-09-22 22:36TDD это относительно новая методика разработки программ, получившая распространение в последние десять-пятнадцать лет. Вот презентация, иллюстрирующая метод на примере решения учебной задачи: вычисления счёта игры в боулинг. Задача несложная, но при этом нетривиальная.
Bowling Game Kata.ppt
Краткое изложение подсчета очков для американского боулинга:
Задача написать класс “Game”, у которого есть два метода:
Метод TDD можно уместить в три принципа.
Заметьте: добавлять новую функциональность можно только в красной фазе, в ответ на непроходящий тест. Улучшать и изменять код можно только в зелёной фазе. Тесты помогут сохранять уверенность, что вы ничего не сломали. Третий шаг, рефакторинг - самый трудоёмкий. Именно здесь вы фактически строите, достраиваете и перестраиваете архитектуру вашей системы.
На слайдах по вышеупомянутой ссылке можно видеть, как разработка всей программы для боулинга проходит всего за пять тестов.

Bowling Game Kata.ppt
Краткое изложение подсчета очков для американского боулинга:
- Игра состоит из десяти «фреймов».
- В каждом фрейме игрок может делает по два броска шара, с целью сбить десять кеглей.
- Если с двух бросков не удалось сбить все кегли, счет за этот фрейм равен общему количеству кеглей, сбитых за две попытки.
- Если в двух бросках сбиты все кегли, это называется «спэр», и счет за фрейм составляет десять плюс количество кеглей, сбитых при следующем броске (на следующем ходу).
- Если при первом броске в фрейме сбиты все кегли, это называется «страйк». Ход игрока окончен, и его счет за фрейм - десять плюс сумма кеглей, сбитых в его следующих двух бросках.
- Если игрок выбивает спэр или страйк в последнем (десятом) фрейме, он может бросить еще один или два бонусных шара соответственно. Эти бонусные броски выполняются как часть одного хода. Если бонусные броски сбивают все кегли, процесс не повторяется: бонусные броски используются только для подсчета очков в последнем фрейме.
- Счет игры - это сумма очков за все фреймы.
Задача написать класс “Game”, у которого есть два метода:
- void roll(int pins); -- вызывается, когда игрок бросает шар. Аргумент задаёт количество сбитых кеглей.
- int score(); -- вызывается в конце игры и возвращает общий счёт.
Метод TDD можно уместить в три принципа.
Разработка программы идёт по циклу в три шага: красный/зелёный/рефактор.
- Вы не имеете права написать ни строчки кода, пока вы не напишете тест (который не проходит).
- Вы не имеете права в тесте написать больше, чем нужно, чтобы тест не прошёл. Ошибка компиляции тоже засчитывается за непрошедший тест, скажем, отсутствие нужного класса или метода.
- Вы не имеете права писать больше кода, чем требуется, чтобы сбоящий тест прошёл.
- Добавляем новый тест. Запускаем все тесты, убеждаемся, что новый тест не проходит. Это красная фаза.
- Пишем код, чтобы тест срабатывал как положено. Главное функциональность, элегантность пока не волнует. Опять запускаем все тесты: проверяем, что все они проходят. Это зелёная фаза.
- Функциональность достигнута, теперь переделываем код, чтобы добиться внутренней красоты и элегантности. Меняем представление данных, разбиение по методам, классам и модулям. Снова запускаем все тесты: всё должно проходить.
- Повторяем весь цикл сначала.
Заметьте: добавлять новую функциональность можно только в красной фазе, в ответ на непроходящий тест. Улучшать и изменять код можно только в зелёной фазе. Тесты помогут сохранять уверенность, что вы ничего не сломали. Третий шаг, рефакторинг - самый трудоёмкий. Именно здесь вы фактически строите, достраиваете и перестраиваете архитектуру вашей системы.
На слайдах по вышеупомянутой ссылке можно видеть, как разработка всей программы для боулинга проходит всего за пять тестов.


no subject
Date: 2020-09-23 09:48 (UTC)В реальных задачах просто невозможно таскать весь объём и не запутаться.
no subject
Date: 2020-09-26 03:57 (UTC)no subject
Date: 2020-09-26 06:25 (UTC)Но, если писать тесты, кода полчится больше. А писать код интереснее, чем думать над решением.
no subject
Date: 2020-09-23 10:45 (UTC)no subject
Date: 2020-09-23 18:29 (UTC)Вопрос на самом деле серьёзный, и стоит гораздо шире. Правильные ли юнит-тесты? На самом деле: делает ли программа то, что нужно?
Юнит-тесты фактически являются спецификацией нижнего уровня. У этой спецификации есть риск не соответствовать бизнес-требованиям, то есть спецификации верхнего уровня. Эта проблема решается другим методом, не связанным с TDD. Не суть, разрабатываете ли вы программу посредством TDD или по другому, с юнит-тестами или без, вам придётся как-то обеспечить соответствие требованиям.
Тут вступают в дело другие методики. Самая известная - это acceptance-тесты. Их разрабатывают архитекторы или бизнес-аналитики, другая
команда, отдельная от программистов.
К примеру, когда я работал в MIPS, у нас был программный пакет под названием Architectural Verification Suite. Это несколько тысяч тестов, каждый от нескольких тысяч до миллионов строк ассемблера. Тесты разрабатывались архитектурной командой и проверяли поведение каждой машинной команды и каждого бита каждого регистра на соответствие спецификации MIPS, во всех возможных ситуациях. Для других команд, разрабатывавших конкретный процессор (или симулятор, как я), эти тесты служили "критерием истины".
Для юнит-тестов проблема правильности обычно не возникает. Каждый тест довольно простой, и однозначно соответствует одному из требований задачи. Если требование слишком сложное, оно разбивается на несколько простых требований нижнего уровня.
no subject
Date: 2020-09-23 23:18 (UTC)no subject
Date: 2020-09-24 05:35 (UTC)По моему опыту, правильный набор юнит-тестов может оказаться ценнее собственно кода системы. Если система пришла в состояние неисправимого бардака (а я видел реальный случай), её можно выкинуть и нанять команду написать всё с нуля. Но - только если у вас есть юнит тесты с хорошим покрытием. Тогда работа делается за предсказуемое время с гарантированным результатом.
Если же у вас на руках глючная система без тестов, тут дело табак. То ли чинить - неизвестно, столько времени уйдёт, то ли выкинуть и переписать - опять сроки непонятные.
no subject
Date: 2020-09-26 03:58 (UTC)no subject
Date: 2020-09-24 01:44 (UTC)Нет, в данном случае он был поставлен достаточно узко - как тестировать тесты. Подразумевается, что объем осмысленного кода в тестах оказывается как минимум таким же, если не больше, чем в основном коде. К примеру, решение квадратного уравнения можно уложить в одну строчку, но существенно различных случаев, которые надо тестировать - минимум три при наивном подходе и минимум шесть при чуть менее наивном.
Ваш пост на этот вопрос не отвечает никак =).
no subject
Date: 2020-09-24 02:41 (UTC)И всё это уводит в сторону от темы TDD, заявленной в этом посте. TDD это про разработку, не про тестирование.
no subject
Date: 2020-09-24 04:51 (UTC)Как любой заслуживающий постановки узкий вопрос, это само собой. Но тем не менее.
>acceptance тесты
Acceptance, Regression и Load testing пока оставим за кадром, разберемся пока с тем, что работает уровнем ниже.
no subject
Date: 2020-09-24 04:59 (UTC)TDD не отвечает на вопрос _тестирования_. TDD организует процесс _разработки_ эффективным образом. Тесты образуются в качестве полезного побочного продукта.
К теме квадратного уравнения. Скажем, есть требование: появление на входе NaN должно выдавать NaN в качестве результата. Добавляем простой юнит-тест. Как проверить, правильный ли этот тест? Прочитать глазами тест и прочитать требования.
no subject
Date: 2020-09-24 05:20 (UTC)Вот с тем же успехом можно записать требования к новому куску кода и сличать глазами то, что делает кусок кода с тем, что записано в спецификации. Так зачем нужны юнит тесты? Особенно учитывая, что качество кода с покрытием юнит-тестами не очень-то коррелирует.
no subject
Date: 2020-09-26 04:01 (UTC)no subject
Date: 2020-09-29 07:45 (UTC)no subject
Date: 2020-09-29 07:43 (UTC)Спецификации сами по себе вещь довольно бесполезная для разработки. Не будете же вы после каждого коммита вручную перечитывать все спецификации и сличать с исходниками. Запуск же юнит-тестов через несколько секунд даст вам вопрос, не сломалось ли что-нибудь.
Качество кода зависит не от тестов, верно. Оно зависит от код ревью. Это отдельная тема, не связанная ни с TDD, ни с тестированием.
no subject
Date: 2020-09-29 13:40 (UTC)>при нормальной политике именования и минимальной сопутствующей документации
мы специально проигнорировали?
>Куча вопросов, и ответом могут быть только юнит-тесты.
Нет. Юнит-тесты вообще очень мало говорят об ожидаемом поведении кода, они лишь требуют определенного выхлопа при заданном входе в заданном окружении. Всегда остается вопрос, как код должен себя вести при других входах и в другом окружении. Можно, конечно, пойти по пути наименьшего сопротивления и везде таскать свою среду - но это так себе решение в долгосрочной перспективе.
no subject
Date: 2020-09-23 11:34 (UTC)Программисту останутся только тесты
no subject
Date: 2020-09-23 19:14 (UTC)no subject
Date: 2020-09-26 04:03 (UTC)Не тесты, а спецификации. Но формальная спецификация для программы на низкоуровневом языке - это программа на высокоуровневом. И дальше возникает вопрос - а почему не написать компилятор для этого высокоуровневого?
Вот так примерно появляются gprolog, ghc, функция compile в Wolfram Mathematica и т.д.
no subject
Date: 2020-09-23 18:08 (UTC)Как-то сомнительно. Почему сразу не писать нормально?
no subject
Date: 2020-09-23 19:17 (UTC)no subject
Date: 2020-09-23 22:06 (UTC)Но я все же думаю, что "красиво" должно быть с самого начала. Понятно, что на следующих итерациях с большой вероятностью придется многое переделывать, но "красивый" код и переделывать проще, меньше шансов насажать ошибок.
no subject
Date: 2020-09-24 02:42 (UTC)no subject
Date: 2020-09-27 04:39 (UTC)От большинства ораторов ускользает главный посыл TDD - это процесс ваяния ПО, а не процесс написания формальной спецификации или чего либо еще - процесс который приводит к красивому ПО.
Насколько я понял это впервые появилось при разработке Ruby. Смотреть в код языка Crystal, который продолжает идеи руби но с выводом типов и компиляциией ллвм в машинный код, очень приятно - красиво и понятно всё.
no subject
Date: 2020-09-29 07:30 (UTC)TDD вроде изобрёл Кент Бек, и там была Java, судя по его книжке. Но это не так важно, какой язык. Методика работает с любым языком программирования.