Написание тестов для существующего кода

68

Предположим, у кого-то была относительно большая программа (скажем, 900k SLOC на C #), все тщательно прокомментированы / задокументированы, хорошо организованы и работают хорошо. Вся база кода была написана одним старшим разработчиком, который больше не работает в компании. Весь код тестируется как есть, и IoC используется повсеместно - за исключением какой-то странной причины, они не написали никаких модульных тестов. Теперь ваша компания хочет разветвить код и хочет добавить модульные тесты, чтобы определить, когда изменения нарушают основные функции.

  • Является ли добавление тестов хорошей идеей?
  • Если так, то как можно начать что-то подобное?

РЕДАКТИРОВАТЬ

Хорошо, поэтому я не ожидал ответов, дающих хорошие аргументы для противоположных выводов. В любом случае, проблема может быть не в моих руках. Я также прочитал «дублирующие вопросы», и общее мнение таково, что «написание тестов - это хорошо» ... да, но не слишком полезно в данном конкретном случае.

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

ЗАКЛЮЧЕНИЕ

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

Павел
источник
10
Добавление тестов не нарушит существующий код.
Дан Пичельман
30
@DanPichelman Вы никогда не сталкивались с schroedinbug - «Ошибка проектирования или реализации в программе, которая не проявляется, пока кто-то не читает исходный код или не использует программу необычным образом, замечая, что она никогда не должна была работать, и в этот момент программа немедленно перестает работать для всех, пока не исправлено ".
8
@MichaelT Теперь, когда вы упомянули об этом, я думаю, что видел один или два из них. Мой комментарий должен был гласить: «Добавление тестов обычно не нарушает существующий код». Спасибо
Дэн Пичельман
3
Пишите тесты только вокруг того, что вы собираетесь изменить или изменить.
Стивен Эверс
3
Проверьте книгу «Эффективная работа с устаревшим кодом»: amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/… Майкл Фезерс предлагает написать тесты перед тем, как модифицировать любой устаревший код.
Скарабей

Ответы:

68

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

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

Проблема заключается в том, что большую часть ценности принесут эти «ошибки» и менее очевидные ситуации. Без этих тестов набор тестов теряет практически всю свою эффективность. Кроме того, у компании будет ложное чувство безопасности в отношении их приложения, поскольку это не будет значительно более регрессивным доказательством.

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

Также см .

FMJaguar
источник
11
Но даже без TDD юнит-тесты полезны при рефакторинге.
PDR
1
Если код продолжает работать хорошо, это не проблема, но лучше всего было бы проверять интерфейс к унаследованному коду всякий раз, когда вы пишете что-либо, зависящее от его поведения.
deworde
1
Вот почему вы измеряете тестовое покрытие . Если тест для определенного раздела не охватывает все if и elses и все крайние случаи, то вы не можете безопасно реорганизовать этот раздел. Покрытие сообщит вам, все ли линии пройдены, поэтому ваша цель - максимально увеличить охват до рефакторинга.
Рудольф Олах
3
Основным недостатком TDD является то, что, хотя пакет может работать, для разработчика, незнакомого с базой кода, это ложное чувство безопасности. BDD намного лучше в этом отношении, поскольку вывод - это намерение кода на простом английском языке.
Робби Ди
3
Так же, как упомянуть, что 100% покрытие кода не означает, что ваш код работает правильно 100% времени. Можно протестировать каждую строку кода для метода, но только то, что он работает со значением1, не означает, что он гарантированно будет работать со значением2.
ryanzec
35

Да, добавление тестов, безусловно, хорошая идея.

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

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

Я бы порекомендовал книгу Майкла Фезерса « Эффективная работа с унаследованным кодом» за несколько полезных подробных советов.

Дэнни Вудс
источник
10
+1. Если вы пишете тесты на основе документации, вы обнаружите любые расхождения между рабочим кодом и документацией, и это бесценно.
Карл Манастер
1
Еще одна причина добавления тестов: когда обнаружена ошибка, вы можете легко добавить тестовый пример для будущего регрессионного тестирования.
Эта стратегия, в основном, изложена в (бесплатном) онлайн-курсе edX CS169.2x в главе о устаревшем коде. Как говорят учителя: «Установление основополагающей истины с помощью тестов характеристик» в главе 9 книги: beta.saasbook.info/table-of-contents
FGM
21

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

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

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

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

Карл Билефельдт
источник
3
Если код редко дает сбой, можно потратить много времени и усилий на поиск загадочных проблем, которые никогда не могут возникнуть в реальной жизни. С другой стороны, если код содержит ошибки и подвержен ошибкам, вы можете начать тестирование где угодно и сразу же найти проблемы.
Робби Ди
8

Является ли добавление тестов хорошей идеей?

Безусловно, хотя мне трудно поверить, что код чистый и работает хорошо, использует современные методы и просто не имеет модульных тестов. Вы уверены, что они не сидят в отдельном решении?

В любом случае, если вы собираетесь расширять / поддерживать код, то истинные модульные тесты неоценимы для этого процесса.

Если так, то как можно начать что-то подобное?

Один шаг за раз. Если вы не знакомы с модульным тестированием, то учитесь немного. Как только вы освоитесь с концепциями, выберите один небольшой раздел кода и напишите для него тесты. Затем следующий и следующий. Покрытие кода может помочь вам найти места, которые вы пропустили.

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

Telastyn
источник
7
"Вы уверены, что они не сидят в отдельном решении?" это отличный вопрос. Я надеюсь, что ОП не упускает это из виду.
Дэн Пичельман,
К сожалению, нет никаких шансов, что приложение было запущено много лет назад, когда TDD набирал обороты, поэтому в какой-то момент намеревались провести тесты, но по какой-то причине после запуска проекта они так и не добрались до него.
Пол
2
Они, вероятно, никогда не добрались до этого, потому что это заняло бы у них дополнительное время, которое того не стоило Хороший разработчик, работающий в одиночку, безусловно, может создать чистое, понятное, хорошо организованное и работающее приложение относительно большого размера без каких-либо автоматических тестов, и, как правило, они могут делать это быстрее и без ошибок, как с тестами. Поскольку все это в их собственной голове, вероятность появления ошибок или организационных проблем значительно ниже по сравнению с тем, как его создают несколько разработчиков.
Бен Ли
3

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

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

Бернард
источник
Вы действительно добавили бы неудачные тесты в программу, которая работает хорошо (ОП говорит)?
MarkJ
Да, потому что это показывает, что что-то работает не так, как задумано, и требует дальнейшего рассмотрения. Это вызовет обсуждение и, мы надеемся, исправит любые недоразумения или ранее неизвестные дефекты.
Бернард
@Bernard - или тесты могут выявить ваше недопонимание того, что должен делать код. Тесты, написанные после факта, рискуют не правильно инкапсулировать первоначальные намерения.
Дан Пичельман
@DanPichelman: Согласен, но это не должно мешать писать тесты.
Бернард
Если бы ничего другого, это указало бы, что код не был написан для защиты.
Робби Ди
3

Хорошо, я собираюсь дать противоположное мнение ....

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

Во всяком случае, я бы не стал добавлять какие-либо юнит-тесты вообще.

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

gbjbaanb
источник
2

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

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

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

Casey
источник