Как покрыть унаследованный код C++, управляемый препроцессором #ifdefs, юнит-тестами?
Я унаследовал не слишком большой унаследованный код C++, который в настоящее время реинжиниринг. До сих пор я достаточно хорошо понимаю большую часть кода и могу использовать его, хотя обслуживание - ад. Я думаю, что основная трудность заключается в МАССИВНОМ использовании директив препроцессора для управления поведением программы.
Рассмотрим следующие примеры:
void function(){
... // lots of code
#ifdef PARAMETER == 1
do_one_thing();
#elif PARAMETER == 2
do_another_thing();
...//etc
#endif
...//lots of code
}
или же
void function(double arg1,
#ifdef SOME_PP_VAR1 == 5
double arg2,
#endif
)
и тому подобное
#ifdef SOME_PP_VAR2 == 2
typedef myVector std::vector<double>;
#elif SOME_PP_VAR2 == 7
typedef myVector std::vector<int>;
#endif
в глобальном масштабе. Или даже
#ifdef SOME_PP_VAR2 == 2
#include "some_header.hpp"
#elif SOME_PP_VAR2 == 7
#include "some_other_header.hpp"
#endif
Около 30 таких переменных препроцессора устанавливаются в файле конфигурации, который передается в систему сборки, а затем в компилятор. Он в основном контролирует все и присутствует практически в любом файле. Кстати, в некоторых местах #ifs даже вложены.
Поэтому довольно сложно писать юнит-тесты. Я должен был бы построить все возможные комбинации переменных препроцессора и проверить каждую.
Мои (плохие) идеи пока таковы:
- Перепишите код с нуля, возможно, на другом языке. (Высокий риск, слишком много времени).
- Замените переменные препроцессора константами. (Не всегда выполнимо).
Вы когда-нибудь сталкивались с такой ситуацией и как вы справились с этим?
С наилучшими пожеланиями и спасибо заранее.
1 ответ
Как покрыть устаревший код C++, управляемый препроцессором #ifdefs, с помощью модульных тестов?
Просто напишите модульные тесты для каждого необходимого тестового примера, скомпилируйте каждый тестовый пример с разными
#ifdefs
нужно было протестировать и запустить их.
Вы когда-нибудь сталкивались с такой ситуацией
Думаю, у меня был похожий случай. Я (пытался ...) писал тесты для программного обеспечения, которое использовало библиотеку GuruxDLMS.c (и здесь).
и как ты с этим справился?
Решение о том, как компилировать материал и запускать тесты, - это работа по адаптации системы сборки, интегрированной в проект. Предполагая, например, систему сборки CMake, с которой мне комфортно, я бы просто написал тесты для каждого случая и скомпилировал библиотеку для каждого случая:
function(add_cpp_legacy_code_library name)
add_library(${name} sources.....cpp)
target_compile_definitions(${name} PUBLIC ${ARGN})
endfunction()
function(add_cpp_legacy_code_test name sourcefile)
add_cpp_legacy_code_library(${name}_library ${ARGN})
add_executable(${name} ${sourcefile})
target_link_libraries(${name} PRIVATE ${name}_library)
add_test(NAME ${name} COMMAND ${name})
endfunction()
add_cpp_legacy_code_test(test1 some_test1.cpp
PARAMETER=1
SOME_PP_VAR1=4
SOME_PP_VAR2=4
)
add_cpp_legacy_code_test(test2 some_test2.cpp
PARAMETER=100
SOME_PP_VAR1=1234
SOME_PP_VAR2=7890
)
# etc...
И т. Д. Для каждой комбинации макросов, которую вы хотите протестировать, где
some_test1.cpp
some_test2.cpp
либо имел бы
#if
для каждой комбинации макросов или будут отдельными файлами.