Устаревший код модульного тестирования C ++: как работать с #include?

Я только начал писать модульные тесты для устаревшего модуля кода с большими физическими зависимостями, используя директиву #include. Я имел дело с ними несколькими способами, которые казались слишком утомительными (предоставление пустых заголовков для разрыва длинных списков зависимостей #include и использование #define для предотвращения компиляции классов) и искал более эффективные стратегии для решения этих проблем.

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

Кто-нибудь знает лучшие практики?


person MasD    schedule 15.09.2008    source источник
comment
Я хотел бы, чтобы я мог проголосовать за этот один раз в 10 раз, это, безусловно, лучшие вопросы по SO, которые я читал до сих пор. Я потерял счет людям, которые просто сдаются: «Мы не можем протестировать ... у нас есть код на C ++ ... Boohoo!»   -  person Gishu    schedule 15.09.2008
comment
Gishu, не могу не согласиться. У меня такие же дискуссии и здесь, где я работаю.   -  person Lou    schedule 10.10.2008


Ответы (6)


Депрессия в ответах огромна ... Но не бойтесь, у нас есть священная книга для изгнания демонов устаревшего кода C ++. Серьезно, просто купите книгу, если вы уже больше недели боретесь с унаследованным кодом C ++.

Перейдите на страницу 127: Случай с ужасными зависимостями include. (Теперь я не нахожусь даже в нескольких километрах от Майкла Фезерса, но здесь ответ настолько краткий, насколько я мог бы справиться ...)

Проблема: в C ++, если классу A нужно знать о классе B, объявление класса B напрямую поднимается / текстуально включается в исходный файл ClassA. А поскольку мы, программисты, любим доводить дело до крайности, файл может рекурсивно включать миллионы других транзитивно. На постройку уходят годы ... но, по крайней мере, она строится ... мы можем подождать.

Сказать, что «создать экземпляр ClassA с помощью тестовой оснастки сложно» - значит ничего не сказать. (Цитируя пример MF: Scheduler - это наш постер с изобилием проблемных детей.)

#include "TestHarness.h"
#include "Scheduler.h"
TEST(create, Scheduler)     // your fave C++ test framework macro
{
  Scheduler scheduler("fred");
}

Это приведет к появлению дракона включений с шквалом ошибок сборки.
Удар №1: терпение и настойчивость: принимайте каждое включение по одному и решайте, действительно ли нам нужна эта зависимость. Предположим, что SchedulerDisplay - один из них, чей метод displayEntry вызывается в 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");
}

И pop переходит в зависимость и включает все ее переходные элементы. Вы также можете повторно использовать методы Fake, инкапсулируя их в файл Fakes.h, который будет включен в ваши тестовые файлы.
Практика Blow # 3: это может быть не всегда так просто ... но вы получить идею. После нескольких первых поединков процесс взлома дипов станет легким и механическим.

Предостережения (я уже упоминал, что есть предостережения? :)

  • Нам нужна отдельная сборка для тестовых случаев в этом файле; у нас может быть только одно определение для метода SchedulerDisplay :: displayEntry в программе. Так что создайте отдельную программу для тестов планировщика.
  • Мы не нарушаем никаких зависимостей в программе, поэтому мы не делаем код более чистым.
  • Эти подделки нужно поддерживать до тех пор, пока нам нужны тесты.
  • Ваше чувство эстетики может быть оскорблено на время ... просто закусите губу и «потерпите нас к лучшему завтра»

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

Для получения дополнительной информации ... прочтите, пожалуйста, книгу. Бесценно. Сражайся, братан!

person Gishu    schedule 15.09.2008
comment
Хотя я считаю это приемлемым ответом, я чувствую, что он действительно приукрашивает процесс между предоставлением фальшивых заглушек в альтернативной реализации функции и магией, которая должна выполняться в процессе сборки. - person MasD; 23.09.2008
comment
Ссылка на книгу не работает. Я думаю, это указывает на «Эффективная работа с устаревшим кодом» Майкла Фезерса. - person Jonathan Johansen; 04.02.2020

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

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

person Pieter    schedule 15.09.2008

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

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

Я попытался найти способ добавить в приложение модульные тесты, но в конце концов застрял в уловке-22:

  1. Для написания значимых полных модульных тестов потребуется рефакторинг кода.
  2. Без модульных тестов рефакторинг кода будет слишком опасным.

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

Иногда лучше работать с кодом таким образом, чтобы он был «разработан» для работы.

person Jeroen Dirks    schedule 15.09.2008

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

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

Большой недостаток этого метода - более совершенная система сборки.

person Ted    schedule 15.09.2008

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

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

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

person Community    schedule 15.09.2008

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

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

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

person quamrana    schedule 15.09.2008