vak: (Default)
[personal profile] vak
TDD это относительно новая методика разработки программ, получившая распространение в последние десять-пятнадцать лет. Вот презентация, иллюстрирующая метод на примере решения учебной задачи: вычисления счёта игры в боулинг. Задача несложная, но при этом нетривиальная.

Bowling Game Kata.ppt

Краткое изложение подсчета очков для американского боулинга:
  • Игра состоит из десяти «фреймов».
  • В каждом фрейме игрок может делает по два броска шара, с целью сбить десять кеглей.
  • Если с двух бросков не удалось сбить все кегли, счет за этот фрейм равен общему количеству кеглей, сбитых за две попытки.
  • Если в двух бросках сбиты все кегли, это называется «спэр», и счет за фрейм составляет десять плюс количество кеглей, сбитых при следующем броске (на следующем ходу).
  • Если при первом броске в фрейме сбиты все кегли, это называется «страйк». Ход игрока окончен, и его счет за фрейм - десять плюс сумма кеглей, сбитых в его следующих двух бросках.
  • Если игрок выбивает спэр или страйк в последнем (десятом) фрейме, он может бросить еще один или два бонусных шара соответственно. Эти бонусные броски выполняются как часть одного хода. Если бонусные броски сбивают все кегли, процесс не повторяется: бонусные броски используются только для подсчета очков в последнем фрейме.
  • Счет игры - это сумма очков за все фреймы.

Задача написать класс “Game”, у которого есть два метода:
  • void roll(int pins); -- вызывается, когда игрок бросает шар. Аргумент задаёт количество сбитых кеглей.
  • int score(); -- вызывается в конце игры и возвращает общий счёт.

Метод TDD можно уместить в три принципа.
  1. Вы не имеете права написать ни строчки кода, пока вы не напишете тест (который не проходит).
  2. Вы не имеете права в тесте написать больше, чем нужно, чтобы тест не прошёл. Ошибка компиляции тоже засчитывается за непрошедший тест, скажем, отсутствие нужного класса или метода.
  3. Вы не имеете права писать больше кода, чем требуется, чтобы сбоящий тест прошёл.
Разработка программы идёт по циклу в три шага: красный/зелёный/рефактор.
  • Добавляем новый тест. Запускаем все тесты, убеждаемся, что новый тест не проходит. Это красная фаза.
  • Пишем код, чтобы тест срабатывал как положено. Главное функциональность, элегантность пока не волнует. Опять запускаем все тесты: проверяем, что все они проходят. Это зелёная фаза.
  • Функциональность достигнута, теперь переделываем код, чтобы добиться внутренней красоты и элегантности. Меняем представление данных, разбиение по методам, классам и модулям. Снова запускаем все тесты: всё должно проходить.
  • Повторяем весь цикл сначала.

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

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

Date: 2020-09-23 09:48 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
Это методика для выполнения университетских упражнений.

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

Date: 2020-09-26 03:57 (UTC)
ext_646638: (Default)
From: [identity profile] rdia.livejournal.com
Адепты тестов всюду и везде не учитывают, что тесты - это тоже код со всеми сопутствующими минусами.

Date: 2020-09-26 06:25 (UTC)
vit_r: default (Default)
From: [personal profile] vit_r
В этом смысле, да. Проще написать альтернативное решение задачи и сравнивать результаты. Как, собственно, делают в mission critical системах. (Хотя, есть методы и поэффективнее.)

Но, если писать тесты, кода полчится больше. А писать код интереснее, чем думать над решением.
Edited Date: 2020-09-26 06:26 (UTC)

Date: 2020-09-23 10:45 (UTC)
From: [personal profile] permeakra
Как тестировать тесты?

Date: 2020-09-23 23:18 (UTC)
brmail: (Default)
From: [personal profile] brmail
мои 20+ лет практики подсказывают, что сами по себе юнит тесты в реальной жизни пользы не принесут. Те изначально, может и неплохо их иметь, но в процессе дополнения системы новым кодом они устаревают. Ломается система не потому, что ее сейчас неправильно написали, а потому что в результате изменений что-то не то стало приходить туда, где такого не ждали. И нередко это "не то" настолько специфично, что для такого изначально эти самые юнит тесты и не приспособлены. А вот написали систему несколько лет назад, и эти годы она меняется, растет и фиг кто эти самые юнит тесты будет дополнять, если не меняет непосредственно объект, а оно хлоп и внезапно работает не так как ожидалось. И хорошо, если вернет ошибку, а не молча ее проглотит и пойдет исполнять код дальше.

Date: 2020-09-26 03:58 (UTC)
ext_646638: (Default)
From: [identity profile] rdia.livejournal.com
Фактически вам нужны просто требования, а не тесты. Но в случае бардака вы можете понять какие были требования из кода, тестов и описания системы.

Date: 2020-09-24 01:44 (UTC)
From: [personal profile] permeakra
>Вопрос на самом деле серьёзный, и стоит гораздо шире.

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

Ваш пост на этот вопрос не отвечает никак =).

Date: 2020-09-24 04:51 (UTC)
From: [personal profile] permeakra
>Узко поставленный вопрос не так узок, как кажется.

Как любой заслуживающий постановки узкий вопрос, это само собой. Но тем не менее.

>acceptance тесты

Acceptance, Regression и Load testing пока оставим за кадром, разберемся пока с тем, что работает уровнем ниже.

Date: 2020-09-24 05:20 (UTC)
From: [personal profile] permeakra
TDD использует тесты как способ записи локальной спецификации. Это, безусловно, единственно корректный подход во всяком шроте типа питона и JS. Но понятие сигнатуры (типа) функции придумано очень давно, и при нормальной политике именования и минимальной сопутствующей документации этого достаточно.

Вот с тем же успехом можно записать требования к новому куску кода и сличать глазами то, что делает кусок кода с тем, что записано в спецификации. Так зачем нужны юнит тесты? Особенно учитывая, что качество кода с покрытием юнит-тестами не очень-то коррелирует.
Edited Date: 2020-09-24 05:22 (UTC)

Date: 2020-09-26 04:01 (UTC)
ext_646638: (Default)
From: [identity profile] rdia.livejournal.com
+ 100% покрытие кода функции юнит-тестами далеко не означает полное тестирование этой функции из-за взаимовлияния разных условных выражений.

Date: 2020-09-29 13:40 (UTC)
From: [personal profile] permeakra
Вот это вот
>при нормальной политике именования и минимальной сопутствующей документации
мы специально проигнорировали?

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

Date: 2020-09-23 11:34 (UTC)
ircicq: (Default)
From: [personal profile] ircicq
Расчёт, что в скором будущем реализацию будет писать AI
Программисту останутся только тесты

Date: 2020-09-26 04:03 (UTC)
ext_646638: (Default)
From: [identity profile] rdia.livejournal.com
> Программисту останутся только тесты

Не тесты, а спецификации. Но формальная спецификация для программы на низкоуровневом языке - это программа на высокоуровневом. И дальше возникает вопрос - а почему не написать компилятор для этого высокоуровневого?

Вот так примерно появляются gprolog, ghc, функция compile в Wolfram Mathematica и т.д.

Date: 2020-09-23 18:08 (UTC)
fizzik: (Default)
From: [personal profile] fizzik
То есть предлагается сначала писать "как нибудь", лишь бы заработало, а потом уже все переделывать, чтобы код был логичным, "красивым", использовал ресурысы эффективно и т.п.
Как-то сомнительно. Почему сразу не писать нормально?

Date: 2020-09-23 22:06 (UTC)
fizzik: (Default)
From: [personal profile] fizzik
Ну ясное дело, что разработка - процесс итерационный.
Но я все же думаю, что "красиво" должно быть с самого начала. Понятно, что на следующих итерациях с большой вероятностью придется многое переделывать, но "красивый" код и переделывать проще, меньше шансов насажать ошибок.

Date: 2020-09-27 04:39 (UTC)
x86128: (Default)
From: [personal profile] x86128
Ну вот и чем процесс программирования, при таком подходе отличается от работы скульптора или художника? Постепенно/итеративно из грубых форм(цветовых пятен) рождается шедевр - красивая программа. :)

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

Насколько я понял это впервые появилось при разработке Ruby. Смотреть в код языка Crystal, который продолжает идеи руби но с выводом типов и компиляциией ллвм в машинный код, очень приятно - красиво и понятно всё.