Как должен быть организован код модульного теста C ++ для максимальной эффективности модульного теста?

47
  • Этот вопрос не о модульном тестировании.
  • Этот вопрос не о написании модульных тестов.
  • Этот вопрос о том, куда поместить написанный код UT и как / когда / где его компилировать и запускать.

В работе эффективно с унаследованным кодом , Майкл Перья утверждает , что

хорошие юнит-тесты ... беги быстро

и это

Модульный тест, для запуска которого требуется 1/10 секунды, является медленным модульным тестом.

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

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

  1. Отредактируйте ваш код (производственный или модульный тест, в зависимости от того, в каком «цикле» вы находитесь)
  2. Compile
  3. Ссылка
  4. Start Unit Test Executable ( s )

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

Как можно эффективно организовать код модульного теста C ++, чтобы можно было эффективно редактировать (тестовый) код и запускать тестовый код?


Первая , то проблема, чтобы решить , где поставить код теста блока таким образом , чтобы:

  • «естественно» редактировать и просматривать его в сочетании с соответствующим рабочим кодом.
  • легко / быстро запустить цикл компиляции для модуля, который вы сейчас меняете

Вторая тогда, связанная, проблема в том , что для компиляции , так что обратная связь мгновенно.

Экстремальные варианты:

  • Каждый Unit-Test-Test-Unit находится в отдельном файле cpp, и этот файл cpp компилируется + связывается отдельно (вместе с файлом модуля исходного кода, который он тестирует) с одним исполняемым файлом, который затем выполняет этот один модульный тест.
    • (+) Это минимизирует время запуска (компиляция + ссылка!) Для одного тестового блока.
    • (+) Тест выполняется очень быстро, потому что он тестирует только один модуль.
    • (-) Выполнение всего пакета потребует запуска нескольких процессов. Может быть проблемой в управлении.
    • (-) Заголовок запуска процесса станет видимым
  • Другой стороной было бы иметь - по-прежнему - один файл cpp на тест, но все тестовые файлы cpp (вместе с кодом, который они тестируют!) Связаны в один исполняемый файл (для модуля / для проекта / выберите ваш выбор).
    • (+) Время компиляции все равно будет в порядке, так как компилируется только измененный код.
    • (+) Выполнить весь пакет легко, так как нужно запустить только один exe.
    • (-) Пакету потребуется несколько веков для ссылки, так как каждая перекомпиляция любого объекта будет вызывать повторную ссылку.
    • (-) (?) Костюм будет работать дольше, хотя, если все юнит-тесты быстрые, время должно быть в порядке.

Итак, как же работают модульные тесты C ++ в реальном мире ? Если я запускаю этот материал только еженедельно / ежечасно, вторая часть на самом деле не имеет значения, но первая часть, а именно, как «связать» код UT с рабочим кодом, так что разработчикам «естественно» сохранять оба Фокус всегда имеет значение, я думаю. (И если разработчики сосредоточены на коде UT, они захотят запустить его, что возвращает нас ко второй части.)

Реальные истории и опыт ценятся!

Примечания:

  • Этот вопрос намеренно оставляет неуказанную платформу и систему создания / проекта.
  • Вопросы с тегами UT & C ++ - отличное место для начала, но, к сожалению, слишком много вопросов, и особенно ответов, слишком сильно сосредоточены на деталях или на конкретных платформах.
  • Некоторое время назад я ответил на аналогичный вопрос о структуре для буст-юнит-тестов. Я считаю, что этой структуры не хватает для «настоящих», быстрых юнит-тестов. И я нахожу другой вопрос слишком узким, отсюда и этот новый вопрос.
Мартин Ба
источник
6
Еще одним осложнением является идиома C ++, заключающаяся в перемещении как можно большего количества ошибок для компиляции времени - хороший набор модульных тестов часто должен быть в состоянии проверить, что определенное использование не компилируется.
3
@Closers: Не могли бы вы привести аргументы, которые привели вас к закрытию этого вопроса?
Люк Турэй
Я не совсем понимаю, почему это должно быть закрыто. :-(Где вы предполагаете искать ответы на такие вопросы, если не на этом форуме?
2
@Joe: Зачем мне нужен модульный тест, чтобы определить, скомпилируется ли что-то, когда компилятор все равно скажет мне об этом?
Дэвид Торнли
2
@ Дэвид: потому что вы хотите убедиться, что он не компилируется . В качестве быстрого примера можно привести объект конвейера, который принимает входные данные и производит выходные данные, Pipeline<A,B>.connect(Pipeline<B,C>)должен компилироваться, тогда как Pipeline<A,B>.connect(Pipeline<C,D>)не должен компилироваться: тип вывода первого этапа несовместим с типом ввода второго этапа.
Себастьян

Ответы:

6

У нас есть все модульные тесты (для модуля) в одном исполняемом файле. Тесты разбиты на группы. Я могу выполнить один тест (или несколько тестов) или группу тестов, указав имя (test / group) в командной строке бегуна. Система сборки может запускать группу «Сборка», отдел тестирования может запускать «Все». Разработчик может поместить несколько тестов в группу типа «BUG1234», где 1234 - это номер трекера проблемы, над которым он работает.

Ур.
источник
6

Во-первых, я не согласен с «1) Отредактируйте ваш (производственный) код и ваш модульный тест». Вы должны изменять только по одному, в противном случае, если результат меняется, вы не будете знать, какой из них вызвал.

Мне нравится помещать модульные тесты в дерево каталогов, которое скрывает основное дерево. Если у меня есть /sources/componentA/alpha/foo.ccи /objects/componentA/beta/foo.o, то я хочу что-то вроде /UTest_sources/componentA/alpha/test_foo.ccи /UTest_objects/componentA/beta/test_foo.o. Я использую то же дерево теней для объектов-заглушек и любых других источников, которые нужны тестам. Будут некоторые крайние случаи, но эта схема сильно упрощает вещи. Хороший макрос редактора может без труда вытащить тестовый источник рядом с исходным источником. Хорошая система сборки (например, GNUMake) может скомпилировать оба и запустить тест с помощью одной команды (например make test_foo), и может управлять базиллионом таких процессов - только те, чьи источники изменились с момента их последнего тестирования - довольно легко (у меня есть никогда не было проблем с запуском этих процессов, это O (N)).

В той же среде вы можете иметь более масштабные тесты (больше не модульные тесты), которые связывают множество объектов вместе и запускают множество тестов. Хитрость заключается в том, чтобы отсортировать эти тесты по времени, которое они затрачивают на сборку / запуск, и соответственно включить их в свой ежедневный график. Запускайте тест на одну секунду или меньше, когда захотите; начать десятисекундный тест и растянуть; пятиминутный тест и сделать перерыв; полчаса теста и отправляйтесь на обед; шестичасовой тест и иди домой. Если вы обнаружите, что тратите много времени, например, перепроверяете огромный тест после изменения только одного небольшого файла, вы делаете это неправильно - даже если ссылки были мгновенными, вы все равно выполняете длинный тест, когда он не был призван.

Бета
источник
объявление (1) - да, это было написано небрежно
Мартин Ба