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

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

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

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

Ответы [ 6 ]

9 голосов
/ 15 сентября 2008

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

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

Проблема : В C ++, если classA должен знать о ClassB, объявление Class B прямо или текстуально включено в исходный файл ClassA. И так как мы, программисты, предпочитаем использовать это неверное значение, файл может рекурсивно включать в себя миллиард других файлов. Сборки занимают годы .. но, по крайней мере, он строит .. мы можем подождать.

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

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

В результате появится дракон включений с целым рядом ошибок сборки.
Удар # 1 Терпение-н-настойчивость : Берите каждое из них по одному и решайте, действительно ли нам нужна эта зависимость. Давайте предположим, что SchedulerDisplay является одним из них, чей метод displayEntry вызывается в ctor планировщика.
Удар # 2 Фальшивка, пока не сделаешь (Спасибо, RonJ):

#include "TestHarness.h"
#include "Scheduler.h"
void SchedulerDisplay::displayEntry(const string& entryDescription) {}
TEST(create, Scheduler)
{
  Scheduler scheduler("fred");
}

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

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

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

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

Подробнее ... пожалуйста, прочитайте книгу. Бесценный. Бой на брата!

1 голос
/ 15 сентября 2008

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

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

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

1 голос
/ 15 сентября 2008

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

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

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

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

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

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

1 голос
/ 15 сентября 2008

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

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

0 голосов
/ 15 сентября 2008

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

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

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

0 голосов
/ 15 сентября 2008

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

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

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

...