Если вы слышали об аббревиатуре TDD, но не знаете, что это означает, или даже если вы знаете, что такое TDD, но не знаете, как применить на практике; тогда эта статья для вас.
Давайте сначала объясним, что такое TDD.
TDD означает Разработка через тестирование, и, как следует из названия, это практика, когда мы пишем код, используя тест в качестве руководства, что делать дальше.
Это подводит нас к тому, что является одним из мои основные принципы в TDD:
Не пишите ни строчки кода, если этого не требует тест!
Так что это на самом деле означает?
Будет легче понять, если мы начнем объяснять, на что похож процесс TDD.
Процесс
Процесс TDD довольно прост:
- Напишите неуспешный тест на вашу функциональность.
- Внесите простейшее изменение, чтобы тест прошел
- Выполните рефакторинг кода после тестирования, чтобы защитить его функциональность.
- Повторить
Итак, это все? Это просто?
Да это оно. Но TDD - это бесконечный цикл; вы пишете неудачный тест, заставляете его пройти, а затем проводите рефакторинг; затем выберите следующую функциональность и начните заново.
Чего мы достигаем, следуя этому процессу? Мы достигаем нескольких вещей:
- Простота
Если мы пишем код только тогда, когда этого требует тест, и всегда пишем самое простое изменение, которое позволяет пройти тест, простота нашего решения гарантирована. - Охват
. Руководить разработкой программного обеспечения с помощью тестов и не писать код, который не прошел бы текущий тест. Совершенно невозможно, чтобы какой-либо код, который мы пишем, не охватывался нашими модульными тестами. - Меньше умственных усилий
Еще одно преимущество TDD заключается в том, что нам не нужно слишком много думать и думать о дизайне заранее, решение, которое в большинстве случаев представляется нам самим после добавления правильных тестов.
Так как же это выглядит на практике? Давайте вместе сделаем упражнение!
TDD на практике
Мы собираемся вместе выполнить легкое ката, чтобы показать, как проходит процесс. Ката, о котором идет речь, - это римские цифры, надеюсь, вам понравится.
Как показано в ссылке, нам нужно будет преобразовать следующие числа:
1 ➔ I 2 ➔ II 3 ➔ III 4 ➔ IV 5 ➔ V 9 ➔ IX 21 ➔ XXI 50 ➔ L 100 ➔ C 500 ➔ D 1000 ➔ M
Напишем наш первый тест!
Чтобы сначала скомпилировать этот тест, нам пришлось создать простейшую реализацию нашего класса RomanNumerals.
Если мы запустим этот тест, он окажется ОТКАЗОМ, как и ожидалось.
Итак, что нам делать, чтобы пройти наш первый тест? Помните, что мы должны внести самые простые изменения, чтобы тест прошел; в этом случае проще всего будет вернуть только «Я». Мы запускаем наш тест, и наш первый тест - ЗЕЛЕНЫЙ!
Теперь, когда наш первый тест имеет зеленый цвет, мы должны написать второй тест. В этом конкретном примере у нас есть разные варианты выбора следующего теста. Например, это может быть «2», но это будет двузначная римская цифра; в этом случае я предпочитаю выбирать следующую одну цифру римскими цифрами, поэтому сначала выбираю 5.
Если мы запустим этот тест, он FAILS, как и ожидалось, возвращает «I». Давайте изменим нашу реализацию, чтобы она прошла, но теперь мы должны помнить, что предыдущие тесты также должны пройти после реализации нашего изменения. Опять же, займемся самым простым!
Все просто! Мы просто проверяем число и возвращаем соответствующую эквивалентную римскую цифру.
Я знаю, о чем вы думаете: это уродливо! Я знаю, но не беспокойтесь об этом; мы изменим это позже.
Следует избегать преждевременной оптимизации TDD, которая обычно приводит к чрезмерно усложненным решениям. Мы должны выбрать, когда проводить рефакторинг нашего кода; когда мы чувствуем, что наша реализация становится запутанной или нечеткой, мы займемся рефакторингом.
Чтобы не делать этот пост слишком длинным, я собираюсь добавить тесты и реализацию после охвата всех «однозначных» римских цифр, которые являются простейшими случаями. Тогда мы сможем решать следующие задачи. Посмотрим, как это будет выглядеть:
Все новые тесты будут FAIL, если мы запустим их сейчас, поэтому давайте посмотрим, как мы изменим реализацию самым простым способом, чтобы они прошли:
Большой! Все наши тесты - ЗЕЛЕНЫЙ! Но подождите секунду, наша реализация становится немного запутанной, верно?
Давайте проведем рефакторинг! На данный момент это становится проблемой, и у нас есть все случаи с однозначными числами; давайте изменим его!
Использование условий if для определения эквивалентности - не лучший подход для этого упражнения, поэтому мы собираемся определить структуру для сохранения этих эквивалентностей. Например, использование Java Map для хранения эквивалентностей позволит нам каждый раз извлекать каждую эквивалентность с помощью O (1). Я использую JDK 11, поэтому я буду использовать Map.of для инициализации этой структуры Map, которая доступна с JDK 9.
Эта новая реализация выглядит так:
Теперь намного чище, правда? Я надеюсь тебе это понравится.
После завершения рефакторинга мы снова запускаем все наши тесты, чтобы убедиться, что мы ничего не сломали. Хорошие новости!
Теперь, когда мы протестировали все случаи с однозначными числами и провели рефакторинг реализации, пришло время заняться следующей проблемой. Давайте напишем тест для первой двузначной римской цифры; В этом случае я выберу 2.
Если мы запустим этот тест, он НЕУДАЕТ, потому что у нас нет эквивалента для числа 2 в нашей структуре.
Пора сдавать, но это будет сложнее, чем в первый раз. Если подумать, у нас есть два варианта: добавить эквивалент числа 2 в нашу структуру или внести изменения, чтобы вернуть два сцепленных «I».
Мы могли бы выбрать первый вариант, но в какой-то момент мы осознали, что не можем хранить все числа в нашей структуре; даже в этом упражнении, где мы собираемся поддерживать числа до 1000, это все равно слишком много.
Итак, давайте попробуем что-нибудь сделать, чтобы обработать число 2 как 2 = 1 + 1.
Если подумать, то нам нужно получить ближайший эквивалент, который меньше или равен числу, которое мы хотим обработать.
Итак, для числа 2 мы получим ближайший ключ ниже или равный, что оказывается равным 1; потом берем оставшуюся часть и повторяем.
Давайте посмотрим, как выглядит наш первый подход:
Как видите, мы заменили нашу структуру хранения эквивалентов интерфейсом NavigableMap, который предоставляет метод lowerKey, который сделает всю работу за нас. Однако, хотя эта реализация способна извлекать для нас нижнюю клавишу, она по-прежнему возвращает только однозначные римские числа; К сожалению, наш тест по-прежнему НЕ ПРОДАЕТСЯ.
Мы должны найти способ повторить итерацию еще раз и найти эквивалент для оставшейся части. Думаю, в этом нам поможет рекурсия, давайте реализуем ее.
Если вы новичок в рекурсии, вначале может быть немного утомительно прояснить концепцию в вашей голове, но это проще, чем то, как она выглядит.
Мы сделали, чтобы проверить эквивалентность первый; если нет эквивалента, найдите нижний ключ, получите эквивалент для этого ключа и снова вызовите метод convert для оставшейся части (number - lowerKey).
Если мы запустим наши тесты сейчас, они все ! Это хорошие новости.
Так что же дальше? А как насчет трехзначных римских цифр? Давайте попробуем номер 3.
Сюрприз! Он ЗЕЛЕНЫЙ, нам не нужно ничего делать. А как насчет числа 4? Мы должны иметь в виду, что это число - частный случай, так как он эквивалентен «IV».
Как и ожидалось, это НЕУДАЧИ. Как решить эту проблему? Это особый случай, потому что он вычитает «I» из «V».
Звучит сложно, но давайте подумаем, сколько у нас таких «особых случаев». У нас есть только IV (4), IX (9), XC (90) и CM (900), так что не имеет смысла прилагать большие усилия; давайте сделаем самое простое, добавим для них эквиваленты в нашу структуру.
Итак, наша новая структура эквивалентностей будет следующей:
Если мы добавим тесты для чисел 4 и 9, они будут ЗЕЛЕНЫМИ. То, что осталось? А что насчет числа 6? Это другое, поскольку это эквивалент «VI», который добавляет «I» к «V». Добавим тест:
Если мы запустим тест, он тоже будет ЗЕЛЕНЫМ! Наша реализация творила чудо. Думаю, пора попробовать более сложное число, например число 838. Добавим тест:
Наш тест - ЗЕЛЕНЫЙ! Думаю, мы закончили, у нас есть надежная реализация, поддерживающая римские цифры от 1 до 1000.
Еще одна вещь, которую мы могли бы сделать в конце, - это провести рефакторинг наших тестов, если их слишком много. В нашем случае мы могли бы преобразовать наши тесты JUnit 4 для использования параметризованных, что устранит некоторую избыточность в наших тестах. Однако я оставлю это вам в качестве упражнения; мы достаточно подробно рассмотрели в этом посте, и проведение этого рефакторинга выходит за рамки демонстрации того, что такое TDD.
Итак, это все! Надеюсь, вам понравилось это упражнение, и вы хорошо поняли, что такое процесс TDD.
Если вам понравилось читать эту статью, подпишитесь, чтобы получать уведомления о публикации новой статьи!
Большое спасибо за чтение!
Первоначально опубликовано на https://theboreddev.com 11 июня 2020 г.