TDD против производительности


131

В своем текущем проекте (игра на C ++) я решил, что я буду использовать Test Driven Development на 100% во время разработки.

С точки зрения качества кода это было здорово. Мой код никогда не был так хорошо спроектирован или так без ошибок. Я не смущаюсь при просмотре кода, который я написал год назад в начале проекта, и у меня появилось гораздо лучшее понимание того, как структурировать вещи, чтобы не только быть более легко тестируемыми, но и быть проще в реализации и использовании ,

Однако ... прошел год с тех пор, как я начал проект. Конечно, я могу работать над этим только в свободное время, но TDD все еще значительно замедляет меня по сравнению с тем, к чему я привык. Я читал, что медленная скорость разработки со временем становится лучше, и я определенно думаю, что тесты гораздо проще, чем раньше, но я занимался этим уже год, и все еще работаю в темпе улитки.

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

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

Дошло до того, что, глядя на прогресс прошлого года, я рассматриваю возможность отказаться от TDD ради «завершения чертового проекта». Тем не менее, отказ от качества кода, который пришел с ним, не то, чего я с нетерпением жду. Боюсь, что если я перестану писать тесты, я избавлюсь от привычки делать код таким модульным и тестируемым.

Возможно, я делаю что-то не так, чтобы все еще быть таким медленным в этом? Существуют ли альтернативы, которые ускоряют производительность без полной потери преимуществ? TAD? Меньше тестового покрытия? Как другие люди переживают TDD, не убивая всю производительность и мотивацию?


@Nairou: Вы всегда можете попробовать "завершить проект"! Сделай ветку сейчас. Просто напишите код там. Но ограничьте то, что вы делаете, либо по времени, либо по количеству игровых сущностей, и посмотрите, быстрее ли вы. Затем вы можете проигнорировать эту ветку, вернуться к транку и TDD оттуда и посмотреть, в чем разница.
Quamrana

9
Для меня написание тестов слишком рано - все равно, что оптимизировать слишком рано. Возможно, вы в любом случае усердно работаете над тестированием кода, который вы удалите в будущем.
LennyProgrammers

Я немного обеспокоен тем, что вы часами думаете о том, как разработать свой код, чтобы он был более тестируемым. Тестируемость - вероятный атрибут хорошо продуманного дизайна, но он не должен быть главной целью проекта .
Джереми

2
Когда я учился, у нас была хитрость, когда мы должны были предоставить проектную документацию. Сначала мы написали код, а затем написали документы для описания кода. Возможно, вам нужно выучить умеренное количество этого прагматизма для вашего TDD. Если у вас уже есть план, возможно, лучше написать большую часть кода, прежде чем писать тесты. Что бы ни подсказывал идеализм, иногда лучше делать то, что ты уже готов сделать, чем отвлекать себя чем-то другим, а потом возвращаться, когда ты уже не новичок.
Steve314

4
Я иду против общественного мнения и скажу, что TDD не всегда может быть правильным выбором, если вы создаете игры. Поскольку кто-то из gamedev.stackexchange уже ответил на этот вопрос эффектно, я просто сошлюсь на это здесь .
l46kok

Ответы:


77

Позвольте мне вначале поблагодарить вас за то, что вы поделились своим опытом и высказать свои опасения ... которые, я должен сказать, не редкость

  • Время / производительность: написание тестов медленнее, чем написание тестов. Если вы согласитесь с этим, я бы согласился. Однако, если вы выполняете параллельную работу, когда применяете подход, не относящийся к TDD, есть вероятность, что время, которое вы потратите на обнаружение, отладку и исправление существующего кода, приведет к отрицательному результату. Для меня TDD - это самое быстрое, что я могу сделать, не ставя под угрозу мою уверенность в коде. Если в вашем методе вы найдете вещи, которые не приносят пользы, устраните их.
  • Количество тестов: если вы кодируете N вещей, вам нужно протестировать N вещей. Перефразируя одну из строк Кента Бека: « Тестируйте, только если вы хотите, чтобы это сработало ».
  • Застревать на несколько часов: я тоже так делаю (иногда и не> 20 минут до того, как я прекращаю свою работу). Это всего лишь ваш код, который говорит вам, что дизайн требует некоторой работы Тест - это просто еще один клиент для вашего класса SUT. Если тестирование затрудняет использование вашего типа, скорее всего, ваши производственные клиенты тоже.
  • Подобные тесты надоели: мне нужно больше контекста, чтобы написать контраргумент. Тем не менее, остановись и подумай о сходстве. Можете ли вы как-то управлять этими тестами? Можно ли написать тесты по базовому типу? Тогда вам просто нужно запустить один и тот же набор тестов для каждого деривации. Слушай свои тесты. Будьте ленивы и посмотрите, сможете ли вы найти способ избежать скуки.
  • Остановка, чтобы подумать о том, что вам нужно делать дальше (тест / спецификация), не плохая вещь. Наоборот, рекомендуется, чтобы вы строили «правильную вещь». Обычно, если я не могу придумать, как это проверить, я обычно не могу думать и о реализации. Это хорошая идея, чтобы отбросить идеи реализации, пока вы не доберетесь до этого ... возможно, более простое решение омрачено упреждающим дизайном YAGNI-ih.

И это подводит меня к последнему запросу: как мне стать лучше? Мой (или An) ответ: « Читай, размышляй и практикуйся».

Например, в последнее время я продолжаю следить за

  • отражает ли мой ритм RG [Ref] RG [Ref] RG [Ref] или RRRRGRRef.
  • % времени, проведенного в состоянии Red / Compile Error.
  • Я застрял в состоянии сборки Red / Broken?

1
Мне очень интересно ваше мнение о тестировании данных. Вы имеете в виду только один набор тестов, которые обрабатывают внешние данные (например, из файла), а не повторно тестируют подобный код? В моей игре есть несколько сущностей, и каждая из них в значительной степени отличается, но есть некоторые общие вещи, которые с ними делают (сериализация их по сети, проверка того, что они не будут отправлены несуществующим игрокам и т. Д.) До сих пор я не нашел способа объединить это, и поэтому у меня есть наборы тестов для каждого, которые почти идентичны, отличаются только тем, какую сущность они используют и какие данные в ней содержатся.

@Nairoi - Не уверен, какой тестовый бегун вы используете. Я только что узнал имя для того, что я хотел передать. Абстрактный паттерн [ goo.gl/dWp3k] . Это все еще требует, чтобы вы написали столько приборов, сколько есть конкретных типов SUT. Если вы хотите быть еще более кратким, посмотрите на документы вашего бегуна. Например, NUnit поддерживает тестовые настройки Parameterized и Generic (теперь, когда я их искал) goo.gl/c1eEQ Похоже, именно то, что вам нужно.
Гишу

Интересно, я никогда не слышал об абстрактных приспособлениях. В настоящее время я использую UnitTest ++, который имеет приспособления, но не абстрактные. Все это очень буквально, это просто способ объединения тестового кода, который вы в противном случае повторяли бы в каждом тесте для данной группы тестов.

@asgeo - не может изменить этот комментарий .. ссылка подобрала хвостовые bracket.This должны работать - goo.gl/dWp3k
Gishu

+1 за то, что «застрять - это признак того, что дизайн требует больше работы», хотя ... что происходит, когда вы застряли (как я) в дизайне?
Люшер

32

Вам не нужно 100% тестовое покрытие. Будьте прагматичны.


2
Если у вас нет 100% тестового покрытия, у вас нет 100% уверенности.
Кристофер Махан

60
Вы не уверены на 100% даже при 100% тестовом покрытии. Это тестирование 101. Тесты не могут продемонстрировать, что код не содержит дефектов; напротив, они могут только продемонстрировать, что он содержит дефекты.
CesarGon

7
Как бы то ни было, один из самых страстных защитников TDD, Боб Мартин, не рекомендует 100% охват - blog.objectmentor.com/articles/2009/01/31/… . В обрабатывающей промышленности (разумеется, во многом отличной от программного обеспечения) никто не претендует на 100% уверенность, потому что они могут потратить часть усилий, чтобы быть уверенными на 99%.
Шанс

Также (по крайней мере, в прошлый раз, когда я проверял имеющиеся у нас инструменты) отчеты о покрытии кода касаются того, были ли выполнены строки, но не включают в себя покрытие значений - например, сегодня я получил сообщение об ошибке, в которой были указаны все пути через код, выполненный в тестах, но поскольку была похожая строка, a = x + yи хотя все строки в коде выполнялись в тестах, тесты тестировались только для случая, когда y = 0, поэтому ошибка (так и должно было быть a = x - y) никогда не обнаруживалась в тестах.
Пит Киркхам

@ Шанс - я прочитал книгу Роберта Мартина "Чистый кодер ..." с некоторым длинным именем. В этой книге сказано, что тесты должны быть асимптотически покрыты на 100%, что близко к 100%. И ссылка на блог больше не работает.
Дариус. В

22

TDD все еще сильно меня тормозит

Это на самом деле неверно.

Без TDD вы тратите несколько недель на написание кода, который в основном работает, а в следующем году «тестируете» и исправляете многие (но не все) ошибки.

С TDD вы тратите год на написание кода, который действительно работает. Затем вы проводите окончательное интеграционное тестирование в течение нескольких недель.

Прошедшее время, вероятно, будет таким же. Программное обеспечение TDD будет значительно лучшего качества.


6
Так зачем мне TDD? «Прошедшее время такое же»

21
@Peter Long: качество кода. Год "тестирования" - это то, как мы сталкиваемся с программным обеспечением, которое в основном работает
S.Lott

1
@ Питер, ты, должно быть, шутишь. Качество решения TDD будет намного выше.
Марк Томас

7
Зачем мне TDD? Кент Бек считает душевное спокойствие большим, и это очень убедительно для меня. Я живу в постоянном страхе сломаться, когда работаю над кодом без юнит-тестов.

7
@Peter Long: «Истекшее время одинаково» ... и в любой момент в течение этого времени вы можете доставить рабочий код .
Фрэнк Шиарар

20

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

Это заставляет меня задуматься о том, насколько сильно вы следуете этапу «Рефакторинг» TDD.

Когда все ваши тесты пройдены, настало время для рефакторинга кода и устранения дублирования. Хотя люди обычно помнят об этом, иногда они забывают, что пришло время и рефакторинг их тестов , чтобы удалить дублирование и упростить вещи.

Если у вас есть две сущности, которые объединяются в одну для повторного использования кода, рассмотрите возможность объединения их тестов. Вам действительно нужно только проверить инкрементные различия в вашем коде. Если вы не проводите регулярное обслуживание своих тестов, они могут быстро стать громоздкими.

Пара философских моментов о TDD, которые могут быть полезны:

  • Если вы не можете понять, как написать тест, несмотря на большой опыт написания тестов, тогда это определенно запах кода . В вашем коде почему-то отсутствует модульность, что затрудняет написание небольших простых тестов.
  • Выделение небольшого количества кода вполне приемлемо при использовании TDD. Напишите код, который вы хотите, чтобы понять, как он выглядит, затем удалите код и начните с тестов.
  • Я вижу практику чрезвычайно строгого TDD как форму упражнения. Когда вы только начинаете, обязательно каждый раз сначала пишите тест и пишите самый простой код, чтобы пройти тест, прежде чем двигаться дальше. Однако в этом нет необходимости, когда вы освоитесь с практикой. У меня нет модульного теста для каждого возможного пути кода, который я пишу, но по своему опыту я могу выбрать, что нужно тестировать с помощью модульного теста, а что вместо этого можно охватить функциональным или интеграционным тестированием. Если вы уже год строго практикуете TDD, я думаю, вы также близки к этому.

РЕДАКТИРОВАТЬ: На тему философии модульного тестирования, я думаю, что это может быть интересно для вас прочитать: Путь Testivus

И более практичный, если не обязательно очень полезный, пункт:

  • Вы упоминаете C ++ в качестве языка разработки. Я широко практиковал TDD на Java, используя отличные библиотеки, такие как JUnit и Mockito. Однако я обнаружил, что TDD в C ++ очень расстраивает из-за отсутствия доступных библиотек (в частности, фреймворков). Хотя этот момент не очень помогает вам в вашей текущей ситуации, я надеюсь, что вы примете это во внимание, прежде чем полностью отказаться от TDD.

4
Рефакторинг тестов опасен. Никто, кажется, не говорит об этом, но это так. У меня, конечно, нет юнит-тестов для тестирования моих юнит-тестов. Когда вы реорганизуете, чтобы уменьшить дублирование, вы обычно увеличиваете сложность (потому что ваш код становится более общим). Это означает, что в ваших тестах больше шансов быть ошибкой .
Скотт Уитлок

2
Я не согласен с тем, что рефакторинг-тесты опасен. Вы выполняете рефакторинг только тогда, когда все проходит, поэтому, если вы делаете рефакторинг, и все по-прежнему зеленый, то все в порядке. Если вы думаете, что вам нужно писать тесты для ваших тестов, я чувствую, что это показатель того, что вам нужно писать более простые тесты.
Джастин

1
C ++ сложен для модульного тестирования (язык не легко делает вещи, которые облегчают издевательства). Я заметил, что функции, которые являются «функциями» (работают только с аргументами, результаты выдаются как возвращаемые значения / параметры), гораздо проще тестировать, чем «процедуры» (возврат void, без аргументов). Я обнаружил, что на самом деле может быть проще выполнить модульное тестирование хорошо разработанного модульного кода C, чем кода C ++. Вам не нужно писать на C, но вы можете следовать примеру модульного C. Это звучит совершенно безумно, но я поставил юнит-тесты на «плохой C», где все было глобально, и это было супер просто - все состояние всегда доступно!
Anon

2
Я думаю, что это очень верно. Я делаю много RedGreenRedGreenRedGreen (или, чаще, RedRedRedGreenGreenGreen), но я редко выполняю рефакторинг. Мои тесты, конечно же, никогда не подвергались рефакторингу, так как я всегда чувствовал, что это потратит еще больше времени, не кодируя. Но я вижу, как это может быть причиной проблем, с которыми я сейчас сталкиваюсь. Пришло время серьезно заняться рефакторингом и консолидацией.
Nairou

1
Фреймворк для Google C ++ (интегрированный с gw C ++ test fw) - очень, очень мощная библиотека для макетов - гибкая, многофункциональная - вполне сопоставимая с любой другой фреймворковой системой.
раткок

9

Очень интересный вопрос

Важно отметить, что C ++ не очень легко тестируется, и игры, в общем, также являются очень плохим кандидатом на TDD. Вы не можете проверить, если OpenGL / DirectX рисует красный треугольник с драйвером X и желтый с драйвером Y легко. Если нормальный вектор карты рельефа не перевернут после преобразования шейдера. Вы также не можете тестировать проблемы отсечения версий драйверов с различной точностью и так далее. Неопределенное поведение при рисовании из-за неправильных вызовов также может быть проверено только с точным просмотром кода и SDK под рукой. Звук тоже плохой кандидат. Многопоточность, которая, опять же, очень важна для игр, практически бесполезна для юнит-тестирования. Так что это сложно.

В основном в играх много GUI, звука и потоков. GUI, даже со стандартными компонентами, на которые вы можете отправить WM_, сложен для модульного тестирования.

Итак, что вы можете протестировать, так это классы загрузки моделей, классы загрузки текстур, библиотеки матриц и тому подобное, что не так много кода и, как правило, не очень многократно используется, если это только ваш первый проект. Кроме того, они упакованы в собственные форматы, поэтому маловероятно, что сторонние материалы могут сильно отличаться, если только вы не выпустите инструменты для моддинга и т. Д.

Опять же, я не гуру или евангелист TDD, так что возьмите все это с крошкой соли.

Возможно, я бы написал несколько тестов для основных компонентов ядра (например, матричная библиотека, библиотека изображений). Добавьте кучу abort()неожиданных входов в каждую функцию. И самое главное, сконцентрируйтесь на стойком / отказоустойчивом коде, который нелегко ломается.

Что касается одних ошибок, умное использование C ++, RAII и хороший дизайн имеют большое значение для их предотвращения.

По сути, вам нужно многое сделать, чтобы охватить основы, если вы хотите выпустить игру. Я не уверен, поможет ли TDD.


3
+1 Мне действительно нравится понятие TDD, и я использую его везде, где могу, но вы затронули очень важный момент, о котором сторонники TDD с любопытством умалчивают. Как вы указали, существует множество типов программирования, для которых написание значимых модульных тестов чрезвычайно сложно, если не невозможно. Используйте TDD там, где это имеет смысл, но некоторые типы кода лучше разработаны и протестированы другими способами.
Марк Хит

@Mark: да, похоже, никто сейчас не заботится об интеграционных тестах, думая, что, поскольку у них есть автоматизированный набор тестов, все будет волшебным образом работать, когда они собраны вместе и опробованы на реальных данных.
gbjbaanb

Согласитесь с этим. Спасибо за прагматичный ответ, который не догматически предписывает TDD как ответ на все вопросы, а не на то, чем он является, что является еще одним инструментом в наборе инструментов для разработчиков.
ДБ

6

Я согласен с другими ответами, но я также хочу добавить очень важный момент: рефакторинг затрат!

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


4

Как другие люди переживают TDD, не убивая всю производительность и мотивацию?

Это полностью отличается от моего опыта. Вы либо поразительно умны и пишете код без ошибок (например, от одной ошибки), либо не понимаете, что в вашем коде есть ошибки, которые мешают работе вашей программы, и поэтому на самом деле они не завершены.

TDD - это смирение, чтобы знать, что вы (и я!) Совершаете ошибки.

Для меня время написания юнит-тестов более чем сэкономлено за счет сокращения времени отладки для проектов, которые выполняются с использованием TDD с самого начала.

Если вы не делаете ошибок, возможно, TDD не так важен для вас, как для меня!


Ну, у вас также есть ошибки в вашем коде TDD;)
Кодер

Это правда! но они имеют тенденцию быть другим типом ошибки, если TDD сделан правильно. Я думаю, что говорить, что код должен быть на 100% свободен от ошибок, чтобы быть законченным, не правильно. Хотя, если кто-то определяет ошибку как отклонение от определенного в модульном тесте поведения, то, я думаю, она не содержит ошибок :)
Том

3

У меня есть только несколько замечаний:

  1. Кажется, вы пытаетесь все проверить . Вы, вероятно, не должны, только случаи высокого риска и крайние случаи конкретного куска кода / метода. Я почти уверен, что здесь применимо правило 80/20: вы тратите 80% на написание тестов для последних 20% своего кода или случаев, которые не охвачены.

  2. Расставлять приоритеты. Начните гибкую разработку программного обеспечения и составьте список того, что вам действительно нужно сделать, чтобы выпустить его за один месяц. Тогда отпустите, просто так. Это заставит вас задуматься о приоритете функций. Да, было бы здорово, если бы ваш персонаж мог сделать сальто назад, но имеет ли это деловую ценность ?

TDD хорош, но только если вы не стремитесь к 100-процентному тестовому покрытию, и это не так, если он не позволяет вам создавать реальную ценность для бизнеса (т.е. функции, вещи, которые добавляют что-то в вашу игру).


1

Да, написание тестов и кода может занять больше времени, чем просто написание кода, но написание кода и связанных с ним модульных тестов (с использованием TDD) гораздо более предсказуемо, чем написание кода и его отладка.

Отладка практически исключается при использовании TDD, что делает весь процесс разработки гораздо более предсказуемым и, в конце концов, возможно более быстрым.

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


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.