"Нормальное" отношение кода-кода к тестированию утверждает проблемы, когда тест терпит неудачу?

В настоящее время я работаю над проектом с довольно значительными бизнес-правилами, где проблемное пространство "обнаруживается" при написании решения (довольно типичное хаотическое управление проектами). У нас есть достойный охват тестирования и они очень много полагаются на них, чтобы убедиться, что наши существенные изменения ничего не взорвали. Этот сценарий - это то, что выделяет unit test zealots как простой пример тестирования помогающего программного обеспечения сразу же легко модифицироваться с меньшими дефектами и более быстрым завершением, если вы не используете модульные тесты. Я содрогаюсь, чтобы подумать о том, как я смогу справиться без набора тестов.

Мой вопрос в том, что, хотя я, конечно, верю в ценность модульного тестирования (этот проект на самом деле является TDD, но на самом деле это не вопрос), мне интересно, как и другие, о классической проблеме unit test имея гораздо больше кода для поиска и поддержания (т.е. самих тестов). Еще раз. нет сомнений в том, что этот конкретный проект намного лучше с unit test cruft, без него, я также обеспокоен долгосрочной ремонтопригодностью тестов.

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

  • мы создаем тестовые списки, которые либо находятся в "зависимом", либо независимом ведре. независимые тесты не требуют ничего, что не было в контроле источника. Таким образом, любые вызовы на наш уровень доступа к данным либо издеваются, либо получают данные из XML файла вместо реального db, например. Зависимые тесты, поскольку название предполагает, зависит от чего-то вроде файла конфигурации или db или сетевого элемента, которые могут быть неправильно настроены/доступны при запуске теста. Разделение тестов на группы, подобные этой, было чрезвычайно важным, что позволило нам писать зависимые "отброшенные" тесты для ранней разработки и независимые критические тесты миссии, на которые можно положиться и повторять тест-гниль. Он также упрощает управление сервером CI, поскольку ему не нужно настраивать и поддерживать соединения w/db и т.п.
  • Мы нацелены на разные уровни кода. Например, у нас есть тесты, попадающие в "главное", и тесты, которые ударяют по всем методам, которые вызовут "main". Это дает нам возможность ориентировать детали системы и общие цели. "Основные" тесты трудно отлаживать, если они ломаются, но обычно это не единственное, что ломается (подробные тесты также ломаются). Легче следить за подробными тестами и отлаживать их, если они ломаются, но их недостаточно, чтобы знать, убивает ли рефактор систему (для чего нужны "основные" тесты).
  • "Основные" тесты были очень важны для того, чтобы чувствовать себя комфортно, что рефактор не запустил кодовую базу. поэтому "основной" тест будет похож на многие тесты на один метод, называемый с разными аргументами, которые отображают для использования. Это, в основном, точка входа в наш код на самом высоком уровне и, как таковые, возможно, не совсем "единичные" тесты. Тем не менее, я нахожу, что мне действительно нужны тесты более высокого уровня, чтобы чувствовать себя комфортно, что рефактор не взорвал кодовую базу. Тесты нижнего уровня (те, которые являются действительно "единицей" работы) недостаточны.

Все это, чтобы ответить на вопрос. Поскольку проект продвигается вперед, и я нахожу, что мне нужно реализовать изменения (иногда довольно значимые, иногда тривиальные) для кодовой базы, я обнаружил, что когда изменения приводят к неудачам тестов, существует соотношение между неудачей теста и реальной регрессивной бизнес-логикой отказ и unit test недействительность. Другими словами, иногда сбой теста происходит из-за ошибки регрессии в реальной кодовой базе, а иногда и потому, что утверждения unit test уже недействительны и это утверждения, которые необходимо изменить. Похоже, что когда тесты заканчиваются неудачно, это был примерно ровный (50%) для этого конкретного проекта.

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

РЕДАКТИРОВАТЬ Обратите внимание, что этот вопрос касается изучения того, что означает это отношение, и вашего опыта с этим соотношением. Когда это "вонючий"?

Ответ 1

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

Когда тест не выполняется, существует три варианта:

  • реализация нарушена и должна быть исправлена,
  • тест сломан и должен быть исправлен, или
  • тест больше не нужен (из-за измененных требований) и должен быть удален.

Важно правильно определить, какой из этих трех вариантов он имеет. То, как я пишу свои тесты, я документирую в имени теста поведение, которое тест указывает/тесты, так что, когда тест не удался, я легко узнаю, почему тест был изначально написан. Я написал об этом подробнее: http://blog.orfjackal.net/2010/02/three-styles-of-naming-tests.html

В вашем случае

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

(Интересная сторона заключается в том, что если вы обнаружите, что в основном переписываете классы, а не меняете их, когда меняются требования, то это может быть признаком того, что код следует хорошо SRP, OCP и другие принципы проектирования.)

Ответ 2

"приводят меня к корректировке тестов, а не к исправлению ошибок регрессии в реальной кодовой базе."

Правильно. Ваши требования изменились. Ваши тестовые утверждения должны измениться.

"это заставляет меня чувствовать, что я просто потратил впустую х часов моего дня"

Почему? Как еще вы собираетесь отслеживать изменения требований?

"Для устранения ошибок проверки-утверждения часто требуется больше времени, чем фактические ошибки регрессии"

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

", который является... контрапунктирующим". Зависит от вашей интуиции. Моя интуиция (после 18 месяцев TDD) заключается в том, что изменение требований приводит к изменениям дизайна, множеству сложных тестовых изменений, отражающих изменения дизайна.

Иногда очень мало (или нет) изменений кода.

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

Иди домой счастливым.

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

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

Если вы проводите 1 час написания тестов и 1 час получения кода, пройдите тесты, это хорошо.

Если вы проводите тесты на 2 часа после изменения требований и 1 час получения кода для прохождения пересмотренных тестов, это означает, что ваш код не очень устойчив к изменениям.

Если вы проводите тесты на 2 часа после изменения требований и 1/2-часового кода для передачи этих тестов, вы написали действительно хороший код.

Ответ 3

Я определенно второй @S.Lott ответ. Я просто хотел бы отметить, что то, что происходит, когда спецификация устанавливается на кучи мертвых деревьев, заключается в том, что когда требования меняются, мертвые деревья (или файлы текстовых процессоров) не кричат ​​на вас так, как это делают тесты, поэтому все проходит просто отлично, за исключением того, что у вас есть эта куча мертвых деревьев, на которые все смотрят, и говорит: "Документация - это вымысел".

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

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

Ответ 4

S. Лотт почти все сказал. Я думаю, что единственное, на что влияет соотношение утверждений теста (T) и исправлений регрессии (R), - это комбинация того, насколько волатильны ваши требования (что сделает T выше) по сравнению с тем, насколько успешным является код приложения при передаче (что повлияет на значение R). Эти два фактора могут варьироваться независимо в зависимости от качества ваших требований и процессов разработки.