С++ Unit Testing Legacy Code: Как обращаться С#include?
Я только что начал писать модульные тесты для устаревшего модуля кода с большими физическими зависимостями, используя директиву #include. Я имел дело с ними несколькими способами, которые казались слишком утомительными (предоставляя пустые заголовки, чтобы сломать длинные списки зависимостей #include и использовать #define для предотвращения компиляции классов) и искали некоторые более эффективные стратегии для решения этих проблем.
Я часто сталкивался с проблемой дублирования почти каждого заголовочного файла с пустой версией, чтобы отделить весь класс, который я тестирую в нем, и затем написание существенного кода заглушки/макета/фальшивки для объектов, которые будут необходимо заменить, так как теперь они undefined.
Кто-нибудь знает некоторые лучшие практики?
Ответы
Ответ 1
Депрессия в ответах подавляющая... Но не бойтесь, у нас есть священная книга, чтобы изгнать демонов унаследованного кода на С++. Серьезно просто купите книгу, если вы находитесь в очереди больше недели на игру с устаревшим кодом на С++.
Переходите на страницу 127: Случай ужасных включений зависимостей. (Теперь я даже не в милях от Майкла Перо, но здесь как-короткий-как-я мог бы управлять ответом.. )
Проблема. На С++, если классA должен знать о ClassB, объявление класса B прямо или частично включено в исходный файл ClassA. И поскольку мы, программисты, любим воспринимать это не в последнюю очередь, файл может рекурсивно включать в себя миллион других транзитно. Строит взять годы.. но эй по крайней мере он строит.. мы можем подождать.
Теперь сказать, что "экземпляр ClassA под тестовым жгутом затруднен" - это преуменьшение. (Цитируя пример MF - Планировщик - наш ребенок с проблемами с плакатами с депрессиями в изобилии.)
#include "TestHarness.h"
#include "Scheduler.h"
TEST(create, Scheduler) // your fave C++ test framework macro
{
Scheduler scheduler("fred");
}
Это приведет к тому, что включает дракона с ошибкой ошибок сборки.
Удар № 1 Терпение-н-Персистирование. Возьмите каждый из них по одному за раз и решите, действительно ли нам нужна эта зависимость. Пусть предположим, что SchedulerDisplay является одним из них, метод displayEntry которого вызывается в Scheduler ctor.
Blow # 2 Fake-it-till-you-make-it (спасибо RonJ):
#include "TestHarness.h"
#include "Scheduler.h"
void SchedulerDisplay::displayEntry(const string& entryDescription) {}
TEST(create, Scheduler)
{
Scheduler scheduler("fred");
}
И появляется популярность, и все ее транзитивные включают.
Вы также можете повторно использовать методы Fake, инкапсулируя его в файл Fakes.h, который будет включен в ваши тестовые файлы.
Удар № 3 Практика. Возможно, это не всегда так просто.. но вы получаете эту идею. После первых нескольких дуэлей процесс разлома депиляции станет легким-n-механическим.
Предостережения (Я упоминал, что есть оговорки?:)
- Нам нужна отдельная сборка для тестовых примеров в этом файле; мы можем иметь только одно определение для метода SchedulerDisplay:: displayEntry в программе. Поэтому создайте отдельную программу для тестов планировщика.
- Мы не нарушаем никаких зависимостей в программе, поэтому мы не делаем код более чистым.
- Вам нужно поддерживать эти подделки до тех пор, пока нам нужны тесты.
- Ваше чувство эстетики может быть оскорблено какое-то время.. просто кусайте свою губу и "несите с нами на лучшее завтра".
Используйте этот метод для очень огромного класса с серьезными проблемами с зависимостями. Не используйте часто или легко. Используйте это как отправную точку для более глубоких рефакторингов. С течением времени эту программу тестирования можно взять за сарай, когда вы извлекаете больше классов (со своими собственными тестами).
Для более.. пожалуйста, прочитайте книгу. Бесценный. Борьба с братом!
Ответ 2
Поскольку вы тестируете устаревший код, я предполагаю, что вы не можете реорганизовать упомянутый код на меньшее количество зависимостей (например, используя pimpl idiom)
Это оставляет вам немного вариантов, я боюсь. Каждому заголовку, который был включен для типа или функции, будет нужен макет-объект для этого типа или функции для всего, что нужно скомпилировать, мало что можно сделать...
Ответ 3
Я не отвечаю на ваш вопрос напрямую, но я боюсь, что модульное тестирование просто не может быть проблемой, если вы работаете с большим количеством устаревшего кода.
После того, как я возглавил команду XP в проекте разработки зеленого поля, мне очень понравились мои модульные тесты. Все произошло, и через несколько лет я обнаружил, что работаю над большой базой кода, которая имеет множество проблем с качеством.
Я попытался найти способ добавить тесты устройств в приложение, но в итоге просто застрял в catch-22:
- Чтобы написать полноценные модульные тесты, код нужно будет реорганизовать.
- Без модульных тестов это будет слишком опасно для рефакторинга кода.
Если вы чувствуете себя героем и пьете прохладную помощь на модульном тестировании, вы все равно можете попробовать, но есть реальный риск, что в итоге вы получите еще больше тестового кода, мало того, что теперь также необходимо поддерживается.
Иногда лучше всего работать над кодом так, чтобы он был "разработан" для работы.
Ответ 4
Я не знаю, будет ли это работать для вашего проекта, но
вы можете попытаться атаковать проблему из фазы ссылки вашей сборки.
Это полностью устранит вашу проблему #include.
Все, что вам нужно будет сделать, - это повторно реализовать интерфейсы в включенных файлах, чтобы делать все, что захотите, а затем просто привязать к файлам макетных объектов, которые вы создали, для реализации интерфейсов во включенном файле.
Большим недостатком этого метода является более сложная система сборки.
Ответ 5
Если вы продолжаете писать коды-заглушки/макеты/поддельные коды, вы рискуете провести модульное тестирование в классе, который имеет другое поведение, а затем компилируется в основном проекте.
Но если они включены и не имеют добавленного поведения, тогда это ОК.
Я бы попробовал ничего не менять при включении при выполнении модульного тестирования, чтобы вы были уверены (насколько вы можете быть в устаревшем коде:)), что вы проверяете реальный код.
Ответ 6
Вы определенно находитесь между камнем и жестким местом с устаревшим кодом с большими зависимостями. У вас есть длинный трудный путь, чтобы разобраться во всем.
Из того, что вы говорите, кажется, вы пытаетесь сохранить исходный код неповрежденным для каждого модуля, в свою очередь, помещая его в тестовый жгут с внешними зависимостями. Мое предложение здесь состояло бы в том, чтобы предпринять еще более смелый шаг по попытке некоторого рефакторинга устранить (или инвертировать) зависимости, что, вероятно, является тем самым шагом, который вы пытаетесь избежать.
Я предлагаю это, потому что я предполагаю, что зависимости будут убивать вас, когда вы пишете тесты. Вы, конечно, будете лучше в долгосрочной перспективе, если сможете устранить зависимости.