Написание тестов для кода, цель которого я не понимаю

59

Я недавно завершил рефакторинг черного ящика. Я не могу проверить это, потому что не могу понять, как это проверить.

На высоком уровне у меня есть класс, инициализация которого включает в себя получение значений из некоторого класса B. Если класс B «пустой», он генерирует некоторые разумные значения по умолчанию. Я извлек эту часть в метод, который инициализирует класс B с теми же значениями по умолчанию.

Мне еще предстоит выяснить цель / контекст любого класса, или как они будут использоваться. Поэтому я не могу инициализировать объект из пустого класса B и проверить, что он имеет правильные значения / делает правильные вещи.

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

Есть ли лучшая атака здесь?

JETM
источник
28
Я чувствую, что вы начали не с того конца. Сначала вы должны понять код, затем протестировать его, а затем выполнить рефакторинг. Почему вы рефакторинг, не зная, для чего код?
Джейкоб Райле
11
@JacobRaihle Это довольно специализированная программа для людей со степенями в вещах, которых я никогда не трогал. Я подбираю контекст по ходу дела, но просто не практично ждать прочного понимания, прежде чем я начну.
JETM
4
То, что не практично, это переписывать вещи и, когда изменения в производстве, обнаруживать, почему вы не должны этого делать. Если вы сможете провести тщательное тестирование до этого, хорошо, это может быть хорошим способом познакомиться с базой кода. Если нет, обязательно, чтобы вы поняли, прежде чем изменить.
Джейкоб Райле
37
Существует особый вид тестирования, называемый тестированием характеристик, когда вы хотите проверить реальное поведение системы. Вы просто берете свою оригинальную систему, затем добавляете тесты, которые утверждают, что она на самом деле делает (и не обязательно, что должна была делать!). Они служат в качестве основы для вашей системы, которую вы можете безопасно изменять, поскольку вы можете гарантировать, что она сохранит свое поведение.
Винсент
3
Разве вы не можете спросить / получить его обзор кем - то , кто делает понять это?
pjc50

Ответы:

122

У тебя все хорошо!

Создание автоматических регрессионных тестов часто - лучшее, что вы можете сделать для рефакторизации компонента. Это может быть удивительно, но такие тесты часто могут быть написаны без полного понимания того, что делает компонент внутри, если вы понимаете «интерфейсы» ввода и вывода (в общем смысле этого слова). Мы делали это несколько раз в прошлом для полноценных унаследованных приложений, а не только для классов, и это часто помогало нам избегать взлома вещей, которые мы до конца не понимали.

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

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

  1. получить понимание кода "извне",
  2. написать регрессионные тесты,
  3. рефактор, который приводит к лучшему пониманию внутренних частей кода
Док Браун
источник
21
Прекрасный ответ, точно так же, как описано в книге «Работа с устаревшим кодом»
Altoyr
Я должен был сделать что-то подобное один раз. Соберите типичные выходные данные из приложения, прежде чем я изменил его, затем проверьте мою новую версию приложения, запустив через нее те же самые тестовые данные. 30 лет назад ... Фортран ... Это была какая-то вещь, связанная с обработкой / отображением изображений, так что я не мог точно знать, каким должен быть результат, глядя на него или написание тестовых случаев. И я сделал это на векторном (постоянном) дисплее Tektronix. Правительственная работа ... 2 телетайпа стучат позади меня.
4
Можно добавить, что вы можете написать свои тесты для старого кода после факта. Затем вы можете попробовать их в своей рефакторированной версии, и, если она сломается, выполнить двукратный поиск по истории фиксации, чтобы найти точку, где она начинает ломаться.
CodeMonkey
2
Я бы предложил сделать еще одну вещь. При сборе тестовых данных соберите статистику покрытия кода, если это возможно. Вы будете знать, насколько хорошо ваши тестовые данные описывают соответствующий код.
Liori
2
@nocomprende, Забавно, что на прошлой неделе я сделал именно эту штуку с устаревшим научным кодом fortran 77. Добавьте печать данных ascii в файл, настройте тестовые каталоги с входными данными и ожидаемым выходным сигналом, и мой тестовый случай был просто различием двух наборов выходных данных. Если они не соответствуют характеру за персонажем, я что-то сломал. Когда код состоит в основном из двух подпрограмм, каждая из которых имеет размер 2-3 КБ, вы должны начать где-нибудь.
Годрик Провидец
1

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

Однако, если возможно сбросить состояние полностью (или создать новый объект тестирования каждый раз), можно написать тесты типа «мусор в мусор», которые просто подают в систему в основном случайный ввод и наблюдают за выводом.

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

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