- PVSM.RU - https://www.pvsm.ru -
Предлагаю к ознакомлению вольный перевод статьи Джейсона Хатчерса «TDD(1) is ‘canard. Tell it to the Duck» [1] или дословно: «TDD — Утка. Скажи это утке». (2)
Автор критикует «ортодоксальный» TDD подход к разработке и предлагает альтернативный.
И, да, в заголовке статьи нет опечатки (3).
Отчасти, цель жизни — веселье. Обычно, возможность сконцентрироваться на творческой стороне программирования доставляет программистам радость. Поэтому, Ruby создан, чтобы сделать программистов счастливыми.
Юкихиро Мацумото [2] (также известный как «Matz»), 2000.
Объясняя свой код кому-либо, вы должны явно объяснить и те участки кода, которые ранее считали очевидными. Во время этих объяснений вы можете получить новые озарения относительно рассматриваемой задачи.
Программист прагматик: Путь от подмастерья к мастеру [3], 2000.
Я следил за дискуссией [4] на тему "Is TDD dead? [5]" с большим интересом, поскольку долго боролся с идеями, которые были резюмированы в виде "3-х основных правил [6] TDD от дядюшки Боба":
Мне всегда казалось, что эти, навязываемые запрещающие правила, ломают естественное поведение программиста.
Дядюшка Боб демонстрирует три правила TDD, показывая как реализовать механизм подсчёта очков [7] в игре в боулинг. Давайте я вам продемонстрирую подход Дяюдшки Боба, четко следуя трем правилам TTD: (4):
По мне, так только очень странный программист, будет действительно следовать подходу, упомянутому выше.
Несмотря на то, что применение TDD, описанное выше, является чрезмерным, есть много веских причин, чтобы писать тесты для вашего кода:
Большинство из описанных выше причин писать тесты не связаны, явным образом, с тремя правилами TDD и фазой рефакторинга из цикла «красный-зеленый-рефакторинг» (5).
Забавно, что некоторые из причин тестирования, которые я даю выше, являются также вескими причинами для написания технической документации. Фактически…
… часто говорят, что тесты являются наилучшей формой технической документации.
Ведение документации отдельно от кода — плохая практика, потому что, зачастую, документация становится устаревшей сразу после её написания. По этой причине, она (документация) часто записывается в виде комментариев, которые позже могут быть извлечены и переформатированы в документацию [8].
Если тесты лучшая форма документации, и, если, документация должна быть внедрена в код, то… может, и тесты также следует писать рядом с кодом? Конечно следует, если разделить их на две части: валидацию и примеры.
Таким образом, мы убиваем сразу двух зайцев. Во-первых, валидация, несущая описательную нагрузку, находится тут же, в коде, и доступна для всех разработчиков, в контексте, в котором они наиболее полезна. Во-вторых, валидация может быть выполнена и в рабочей среде.
Quack-Driven Development, или QDD, позволяет программисту выразить требования, соглашения и намерения внутри самой реализации.
Это современная методология «водоплавающих» (waterfowl) для разработки программного обеспечения.
Фундаментом QDD является идея переноса метода утенка [9] в код, при помощи замены комментариев в коде на тестируемые утверждения. Она (идея) имеет простую философию, которая не ограничивает, а информирует нас, как делать работу:
Следующая далее реализация QDD в Ruby, является доказательством правильности концепции QDD. Она позволяет программистам «поговорить с уткой» в коде, используя «утиный смайлик» (Q<), и демонстрирует, как я бы использовать QDD подход, чтобы реализовать пример игры в боулинг дядюшки Боба. Приступим.
class Game
def initialize
@frames = []
end
Q< "принимает количество сбитых кегель"
def roll(pins)
Q< "вызывается каждый раз, когда игрок бросает шар"
end
Q< "возвращает итоговый счёт игры"
def score
Q< "вызывается только в конце игры"
end
end
class Game
Q< :roll do
before "принимает количество сбитых кегель" do
expect(pins).to be_a(FixNum)
expect(1..10).to cover(pins)
end
within "вызывается каждый раз, когда игрок бросает шар" do
expect(@frames).to be_a(Array)
expect(1..10).to cover(@frames.length)
end
end
Q< :score do
within "вызывается только в конце игры" do
expect(@frames.length).to eq(10)
end
after "возвращает итоговый счёт игры" do
expect(retval).to be_a(FixNum)
expect(0..300).to cover(retval)
end
end
end
class Game
def initialize
@frames = []
end
Q< "принимает количество сбитых кегель"
def roll(pins)
Q< "вызывается каждый раз, когда игрок бросает шар"
@frames << Frame.new if _start_new_frame?
Q< "фрейм, которому необходим бросок должен быть тут"
Q< "если мы на 10-м фрейме,фрйем может быть завершенным"
@frames.each do |frame|
frame.bonus(pins) if frame.needs_bonus_roll?
end
@frames.last.roll(pins) if @frames.last.needs_roll?
end
Q< "возвращает итоговый счёт игры"
def score
Q< "вызывается только в конце игры"
@frames.map(&:score).reduce(:+)
end
private
def _start_new_frame?
@frames.size == 0 ||
@frames.size < 10 && !@frames.last.needs_roll?
end
end
class Frame
def initialize
@rolls = [] ; @bonus_rolls = []
end
Q< "возвращает, ждет ли этот фрейм броска"
def needs_roll?
!_strike? && @rolls.size < 2
end
Q< "принимает количество сбитых кегель"
def roll(pins)
Q< "вызывается только когда фрейм ждет броска"
@rolls << pins
end
Q< "возврвщает ждёт ли фрейм бонусного броска"
def needs_bonus?
_strike? && @bonus_rolls.size < 2 ||
_spare? && @bonus_rolls.size < 1
end
Q< "принимает количество сбитых кегель"
def bonus_roll(pins)
Q< "вызывается только когда фрейм ждет бонусного броска"
@bonus_roll << pins
end
Q< "возвращает итоговый счёт по фрейму"
def score
Q< "вызывается только когда больше нет бросков"
@rolls.reduce(:+) + @bonus_rolls.reduce(0, :+)
end
private
def _strike?
@rolls.first == 10
end
def _spare?
@rolls.reduce(:+) == 10
end
end
describe Game do
let(:game) { Game.new }
it “возвратит 0 если шар ушёл в желоб” do
20.times { game.roll(0) }
expect(game.score).to eq(0)
end
it "возвратит 20 если все кегли забиты" do
20.times { game.roll(1) }
expect(game.score).to eq(20)
end
it "учитывает спэры" do
game.roll(5)
game.roll(5)
game.roll(3)
17.times { game.roll(0) }
expect(game.score).to eq(16)
end
it "учитывает броски" do
game.roll(10)
game.roll(3)
game.roll(4)
16.times { game.roll(0) }
expect(game.score).to eq(24)
end
it "возвратит 300 в случае идеальной игры" do
12.times { game.roll(10) }
expect(game.score).to eq(300)
end
it “должна работать с игрой, заданной в примере” do
[1,4,4,5,6,4,5,5,10,0,1,7,3,6,4,10,2,8,6].each do |pins|
game.roll(pins)
end
expect(game.score).to eq(133)
end
it “не должна работать, если игра некорректна” do
expect(game.score).to quack("вызывается только в конце игры")
expect(game.roll(11)).to quack("принимает количество сбитых кегель")
expect(game.roll(5.5)).to quack("принимает количество сбитых кегель")
expect(game.roll(nil)).to quack
expect(30.times { game.roll(0) }).to quack
end
end
Глупостью было бы выбрасывать всю эту работу, которяа далась нам нелегко, как только мы запустим продукт в рабочей среде. Может лучше продолжать тестировать наш код в прямом эфире, во время того как пользователи взаимодействуют с нашим программным продуктом?
А почему бы и нет! Поведение «уток» может быть настроено произвольным образом:
«Уток» также легко можно закомментировать, превращая их в мертвых уток, как в следующем примере:
class Game
#< "принимает количество сбитых кегель"
def roll(pins)
#< "вызывается каждый раз, когда игрок бросает шар"
end
#< "возвращает итоговый счёт игры"
def score
#< "вызывается только в конце игры"
end
end
Обратите внимание, что даже «мертвые утки» служат комментариями. Являются ли комментарии, которые вы пишете в коде сегодня, всего лишь «мёртвыми утками», которые ждут своего часа, чтобы «вернуться к жизни» посредством QDD?
Я не думаю, что TDD мертв. Но я думаю, что идея писать тесты для того чтобы управлять ходом разработки является одним из многих инструментов, которые должны использоваться с умом:
QDD является отличной альтернативой TDD. QDD разделяет тесты на валидацию, которая внедрена в реализацию, и примеры, которые предназначены для установки начального состояния и проверки, что реализация правильно изменяет это состояние.
QDD имеет два основных преимущества по сравнению с TDD: он объединяет информацию о требованиях и намерениях программиста прямо в реализации, и продолжает тестировать ваш код в рабочей среде.
И, как бонус, утиный смайлик, просто, очень симпатичный.
Я рекомендую вам сегодня же добавить QDD-подход к вашей методологии «водоплавающих», так как он прекрасно дополняет утиную типизацию [12], избиение утки [13](6) и метод утёнка [9]!
Q< «Тестирование никогда не превратится в „утку“!»
Автор: XupyprMV
Источник [19]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/razrabotka/67210
Ссылки в тексте:
[1] «TDD(1) is ‘canard. Tell it to the Duck»: https://medium.com/@jason_hutchens/tdd-is-canard-tell-it-to-the-duck-207b1d574f23
[2] Юкихиро Мацумото: https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%86%D1%83%D0%BC%D0%BE%D1%82%D0%BE,_%D0%AE%D0%BA%D0%B8%D1%85%D0%B8%D1%80%D0%BE
[3] Программист прагматик: Путь от подмастерья к мастеру: http://www.ozon.ru/context/detail/id/1657382/
[4] дискуссией: https://www.youtube.com/watch?v=z9quxZsLcfo
[5] Is TDD dead?: http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
[6] 3-х основных правил: http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd
[7] механизм подсчёта очков: http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata
[8] переформатированы в документацию: http://www.stack.nl/~dimitri/doxygen/
[9] метода утенка: https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D1%83%D1%82%D1%91%D0%BD%D0%BA%D0%B0
[10] «Are Your Lights On»: http://www.amazon.com/Are-Your-Lights-On-Problem/dp/0932633161
[11] rspec-ожидания: https://github.com/rspec/rspec-expectations
[12] утиную типизацию: https://ru.wikipedia.org/wiki/%D0%A3%D1%82%D0%B8%D0%BD%D0%B0%D1%8F_%D1%82%D0%B8%D0%BF%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F
[13] избиение утки: https://en.wikipedia.org/wiki/Duck_punching
[14] через тестирование: http://ru.wikipedia.org/wiki/TDD
[15] Каскадная модель разработки ПО: https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D1%81%D0%BA%D0%B0%D0%B4%D0%BD%D0%B0%D1%8F_%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C
[16] BowlingGameKata.ppt: http://butunclebob.com/files/downloads/Bowling%20Game%20Kata.ppt
[17] ru.wikipedia.org/wiki/Разработка_через_тестирование: https://ru.wikipedia.org/wiki/Разработка_через_тестирование
[18] обезьяний патч: https://ru.wikipedia.org/wiki/Monkey_patch
[19] Источник: http://habrahabr.ru/post/232927/
Нажмите здесь для печати.