Допустим, вы пишете в стиле ЯХДЗИ в стиле TDD. Вы хотите проверить часть кода, которая определяет, является ли набор из пяти бросков кубика фулл-хаусом. Насколько я знаю, при выполнении TDD вы следуете следующим принципам:
- Сначала напишите тесты
- Напишите простейшую вещь, которая работает
- Уточнение и рефакторинг
Таким образом, первоначальный тест может выглядеть примерно так:
public void Returns_true_when_roll_is_full_house()
{
FullHouseTester sut = new FullHouseTester();
var actual = sut.IsFullHouse(1, 1, 1, 2, 2);
Assert.IsTrue(actual);
}
Следуя инструкциям «Напишите самую простую вещь, которая работает», вы должны написать следующий IsFullHouse
метод:
public bool IsFullHouse(int roll1, int roll2, int roll3, int roll4, int roll5)
{
if (roll1 == 1 && roll2 == 1 && roll3 == 1 && roll4 == 2 && roll5 == 2)
{
return true;
}
return false;
}
Это приводит к зеленому тесту, но реализация не завершена.
Следует ли вам проводить модульное тестирование каждой возможной действительной комбинации (как значений, так и позиций) для фулл-хауса? Это выглядит как единственный способ быть абсолютно уверенным в том, что ваш IsFullHouse
код полностью протестирован и исправлен, но это также звучит совершенно безумно.
Как бы вы протестировали что-то подобное?
Обновить
Эрик и Килиан отмечают, что использование литералов в начальной реализации для получения зеленого теста может быть не самой лучшей идеей. Я хотел бы объяснить, почему я это сделал, и это объяснение не помещается в комментарии.
Мой практический опыт с модульным тестированием (особенно с использованием подхода TDD) очень ограничен. Я помню, как смотрел запись мастер-класса Роя Ошерова TDD на Tekpub. В одном из эпизодов он создает стиль String Calculator TDD. Полная спецификация калькулятора строк может быть найдена здесь: http://osherove.com/tdd-kata-1/
Он начинает с такого теста:
public void Add_with_empty_string_should_return_zero()
{
StringCalculator sut = new StringCalculator();
int result = sut.Add("");
Assert.AreEqual(0, result);
}
Это приводит к этой первой реализации Add
метода:
public int Add(string input)
{
return 0;
}
Затем этот тест добавляется:
public void Add_with_one_number_string_should_return_number()
{
StringCalculator sut = new StringCalculator();
int result = sut.Add("1");
Assert.AreEqual(1, result);
}
И Add
метод подвергается рефакторингу:
public int Add(string input)
{
if (input.Length == 0)
{
return 0;
}
return 1;
}
После каждого шага Рой говорит: «Напиши самую простую вещь, которая будет работать».
Поэтому я подумал, что попробую этот подход, когда попытаюсь сделать игру Yahtzee в стиле TDD.
источник
if (roll1 == 1 && roll2 == 1 && roll3 == 1 && roll4 == 2 && roll5 == 2)
Ответы:
На этот вопрос уже есть много хороших ответов, и я прокомментировал и проголосовал за некоторые из них. Тем не менее, я хотел бы добавить некоторые мысли.
Гибкость не для новичков
ОП четко заявляет, что у него нет опыта работы с TDD, и я думаю, что хороший ответ должен учитывать это. В терминологии модели приобретения навыков Дрейфуса он, вероятно, новичок . Нет ничего плохого в том, чтобы быть новичком - мы все новички, когда начинаем изучать что-то новое. Тем не менее, модель Дрейфуса объясняет, что новички характеризуются
Это не описание недостатка личности, поэтому нет причин стыдиться этого - это этап, который нам всем нужно пройти, чтобы узнать что-то новое.
Это также верно для TDD.
Хотя я согласен со многими другими ответами здесь, что TDD не должен быть догматичным, и что иногда может быть более выгодно работать альтернативным способом, это не помогает никому просто начинать. Как вы можете действовать по своему усмотрению, когда у вас нет опыта?
Если новичок принимает совет о том, что иногда можно не использовать TDD, как он может определить, когда можно пропустить TDD?
Без опыта или руководства только новичок может пропустить TDD каждый раз, когда он становится слишком трудным. Это человеческая природа, но не хороший способ учиться.
Слушай тесты
Отказ от TDD в любое время, когда становится трудно, - упустить одно из самых важных преимуществ TDD. Тесты обеспечивают раннюю обратную связь об API SUT. Если тест трудно написать, это важный признак того, что SUT сложно использовать.
Вот почему одним из наиболее важных сообщений ГСНО является: слушайте свои тесты!
В случае с этим вопросом моей первой реакцией, когда я увидел предлагаемый API игры Yahtzee и обсуждение комбинаторики, которое можно найти на этой странице, было то, что это важный отзыв об API.
Должен ли API представлять броски костей в виде упорядоченной последовательности целых чисел? Для меня это запах Примитивной Одержимости . Вот почему я был рад видеть ответ Талсета, предлагающий введение
Roll
класса. Я думаю, что это отличное предложение.Тем не менее, я думаю, что некоторые комментарии к этому ответу ошибаются. Затем TDD предлагает следующее: как только вы поймете, что
Roll
класс будет хорошей идеей, вы приостанавливаете работу над исходным SUT и начинаете работу над TDDRoll
.Хотя я согласен с тем, что TDD больше нацелен на «счастливый путь», чем на всестороннее тестирование, он все же помогает разбить систему на управляемые блоки. А
Roll
звуки класса , как то , что вы могли бы TDD к завершению гораздо легче.Затем, когда
Roll
класс будет достаточно развит, вернитесь ли вы к исходному SUT и уточните его с точки зренияRoll
входных данных.Предложение помощника по тестированию не обязательно подразумевает случайность - это просто способ сделать тест более читабельным.
Другим способом подхода и моделирования входных данных с точки зрения
Roll
экземпляров было бы введение в построитель тестовых данных .Красный / Зеленый / Рефакторинг - это трехступенчатый процесс
Хотя я согласен с общим мнением, что (если вы достаточно опытны в TDD), вам не нужно строго придерживаться TDD, я думаю, что это довольно плохой совет в случае с упражнением Yahtzee. Хотя я не знаю деталей правил Яхтзе, я не вижу здесь убедительного аргумента о том, что вы не можете строго придерживаться процесса «Красный / Зеленый / Рефакторинг» и все же достичь надлежащего результата.
Кажется, что большинство людей здесь забывают, это третья стадия процесса Red / Green / Refactor. Сначала вы пишете тест. Затем вы пишете простейшую реализацию, которая проходит все тесты. Тогда вы рефакторинг.
Именно здесь, в этом третьем штате, вы можете использовать все свои профессиональные навыки. Здесь вы можете размышлять над кодом.
Тем не менее, я думаю, что отговорка состоит в том, чтобы заявить, что вы должны только «написать простейшую вещь, которая не является полностью мозговой и явно неправильной, которая работает». Если вы (думаете, что) достаточно знаете о реализации заранее, то все, кроме полного решения, будет явно неверным . Что касается советов, то это довольно бесполезно для новичка.
Что в действительности должно произойти, так это то, что если вы можете выполнить все тесты с явно некорректной реализацией, то вам следует написать еще один тест .
Удивительно, как часто это приводит к совершенно иной реализации, нежели та, которую вы имели в виду вначале. Иногда такая альтернатива может оказаться лучше, чем ваш первоначальный план.
Rigor - это инструмент обучения
Имеет смысл придерживаться строгих процессов, таких как Red / Green / Refactor, пока вы учитесь. Это заставляет ученика приобретать опыт работы с TDD не только тогда, когда это легко, но и когда это сложно.
Только после того, как вы освоите все сложные части, вы сможете принять осознанное решение о том, когда следует отклониться от «истинного» пути. Вот когда вы начинаете формировать свой собственный путь.
источник
Как заявление об отказе от ответственности, это - TDD, поскольку я практикую это, и, как метко указывает Килиан, я бы остерегался любого, кто предположил, что есть один правильный способ практиковать это. Но, может быть, это поможет вам ...
Прежде всего, самое простое, что вы можете сделать, чтобы сдать тест, это:
Это важно, потому что не из-за некоторой практики TDD, а потому, что жесткое связывание во всех этих литералах не очень хорошая идея. Одной из самых сложных вещей, которую стоит обдумать с помощью TDD, является то, что это не комплексная стратегия тестирования - это способ защиты от регрессий и оценки прогресса при сохранении простого кода. Это стратегия развития , а не стратегия тестирования.
Причина, по которой я упоминаю это различие, заключается в том, что он помогает определить, какие тесты следует писать. Ответ на вопрос «какие тесты мне писать?» «все тесты, которые вам нужны, чтобы получить код так, как вы хотите». Думайте о TDD как о способе помочь вам выявить алгоритмы и рассуждать о вашем коде. Итак, учитывая ваш тест и мою «простую зеленую» реализацию, какой тест будет дальше? Ну, вы создали что-то, что является полным домом, так когда же это не полный дом?
Теперь вам нужно найти какой-то способ различения двух тестовых случаев, который имеет смысл . Лично я бы добавил немного поясняющей информации, чтобы «сделать самое простое, чтобы пройти тест», и сказать: «сделать самое простое, чтобы пройти тест, который способствует вашей реализации». Написание неудачных тестов - это ваш предлог для изменения кода, поэтому, когда вы собираетесь писать каждый тест, вы должны задаться вопросом: «Что мой код делает, чего я от него не хочу, и как я могу выявить этот недостаток?» Это также может помочь вам сделать ваш код надежным и обрабатывать крайние случаи. Что делать, если звонящий вводит ерунду?
Подводя итог, если вы тестируете каждую комбинацию значений, вы почти наверняка делаете это неправильно (и, скорее всего, получите комбинаторный взрыв условных выражений). Когда дело доходит до TDD, вы должны написать минимальное количество тестовых случаев, необходимых для получения желаемого алгоритма. Любые дальнейшие тесты, которые вы пишете, начнут выделяться зеленым цветом и, следовательно, станут документацией, по сути, а не строго частью процесса TDD. Вы будете писать дальнейшие тестовые сценарии TDD только в том случае, если изменятся требования или обнаружена ошибка, и в этом случае вы зарегистрируете дефект с помощью теста, а затем сделаете его успешным.
Обновить:
Я начал это как комментарий в ответ на ваше обновление, но оно стало довольно длинным ...
Я бы сказал, что проблема не в существовании литералов, точка, а в том, что «самая простая» вещь состоит из пяти частей. Когда вы думаете об этом, условное условие из 5 частей на самом деле довольно сложно. Обычно будет использоваться литералы во время этапа с красного на зеленый, а затем абстрагировать их до констант на этапе рефакторинга или обобщить их в более позднем тесте.
Во время моего собственного путешествия с TDD я осознал, что необходимо провести важное различие - нехорошо смешивать «простое» и «тупое». То есть, когда я начинал, я смотрел, как люди делают TDD, и я думал, что «они просто делают самую глупую вещь, чтобы сделать тесты успешными», и я подражал этому некоторое время, пока не понял, что «простое» немного отличается чем "тупой". Иногда они перекрываются, но часто нет.
Таким образом, извинения, если у меня создалось впечатление, что существование литералов было проблемой - это не так. Я бы сказал, что сложность условного с 5 пунктами является проблемой. Ваш первый «красный-зеленый» может быть просто «вернуть истину», потому что это действительно просто (и, по совпадению, тупо). Следующий тестовый пример с (1, 2, 3, 4, 5) должен будет вернуть false, и именно здесь вы начнете оставлять «тупые» позади. Вы должны спросить себя: «Почему (1, 1, 1, 2, 2) аншлаг, а (1, 2, 3, 4, 5) нет?» Самая простая вещь, которую вы могли бы придумать, это то, что у одного есть последний элемент последовательности 5 или второй элемент последовательности 2, а у другого его нет. Это просто, но они также (без необходимости) тупые. Что вы действительно хотите, чтобы ездить на это "сколько из того же числа у них есть?" Таким образом, вы можете пройти второй тест, проверив, есть ли повторение. В одном с повтором у вас есть фулл-хаус, а в другом - нет. Теперь тест пройден, и вы пишете еще один тест, который повторяется, но не является полным аншлагом для дальнейшего совершенствования вашего алгоритма.
Вы можете или не можете сделать это с литералами, как вы идете, и это нормально, если вы делаете. Но общая идея заключается в том, что ваш алгоритм «органично» растет по мере добавления новых случаев.
источник
Тестирование на пять конкретных буквальных значений в определенной комбинации не является "самым простым" для моего воспаленного мозга. Если решение проблемы действительно очевидно (считайте, есть ли у вас ровно три и ровно два любых значения), тогда непременно продолжайте кодировать это решение и напишите некоторые тесты, которые очень, очень маловероятно будут случайно удовлетворить количество написанного вами кода (т. е. разные литералы и разные порядки троек и двойников).
Принципы TDD действительно являются инструментами, а не религиозными убеждениями. Их цель - помочь вам быстро написать правильный, хорошо продуманный код. Если максима явно стоит на пути этого, просто прыгните вперед и переходите к следующему шагу. В вашем проекте будет много неочевидных фрагментов, где вы сможете их применить.
источник
Ответ Эрика великолепен, но я подумал, что могу поделиться трюком в тестовом письме.
Начните с этого теста:
Этот тест становится еще лучше, если вы создадите
Roll
класс вместо 5 параметров:Это дает эту реализацию:
Затем напишите этот тест:
Как только это пройдет, напишите это:
После этого, держу пари, вам больше не нужно писать (может быть, две пары, или, может быть, yahtzee, если вы думаете, что это не фулл-хаус).
Очевидно, реализовать ваши методы Any для возврата случайных бросков, которые соответствуют вашим критериям.
У этого подхода есть несколько преимуществ:
источник
IsFullHouse
действительно вернуться,true
еслиpairNum == trioNum
?Я могу думать о двух основных способах, которые я бы рассмотрел при тестировании этого;
Добавьте «несколько» больше тестовых случаев (~ 5) действительных наборов фулл-хауса, и такое же количество ожидаемых ложных срабатываний ({1, 1, 2, 3, 3} является хорошим. Помните, что, например, может быть 5 из них распознается как «3 одинаковых плюс пара» неправильной реализацией). Этот метод предполагает, что разработчик не просто пытается пройти тесты, но на самом деле правильно его реализовать.
Протестируйте все возможные наборы кубиков (всего 252 разных). Это, конечно, предполагает, что у вас есть какой-то способ узнать ожидаемый ответ (при тестировании это называется
oracle
.) Это может быть эталонная реализация той же функции или человека. Если вы хотите быть очень строгим, возможно, стоит вручную написать каждый ожидаемый результат.Так получилось, что однажды я написал AI Яхтзе, который, конечно, должен был знать правила. Вы можете найти код для части оценки счета здесь , пожалуйста, обратите внимание, что реализация предназначена для скандинавской версии (Yatzy), и наша реализация предполагает, что кости даны в отсортированном порядке.
источник
Этот пример действительно не соответствует сути. Мы говорим об одной простой функции, а не о разработке программного обеспечения. Это немного сложно? да, так что ты сломаешь это. И вы абсолютно не проверяете все возможные входные данные от 1, 1, 1, 1, 1 до 6, 6, 6, 6, 6, 6. Рассматриваемая функция не требует порядка, только комбинация, а именно AAABB.
Вам не нужно 200 отдельных логических тестов. Вы можете использовать набор для примера. Почти любой язык программирования имеет один встроенный:
И если вы получаете ввод, который не является действительным броском Яхтзе, вы должны бросить, как будто завтра нет.
источник