cmake ctest

¿Cómo adaptar mis pruebas unitarias a cmake y ctest?



(1)

Usaría el modo de secuencia de comandos independiente de CMake para ejecutar las pruebas y comparar las salidas. Normalmente, para un programa de prueba unitaria, escribiría add_test(testname testexecutable) , pero puede ejecutar cualquier comando como prueba.

Si escribe un script "runtest.cmake" y ejecuta su programa de prueba de unidad a través de esto, entonces el script runtest.cmake puede hacer lo que quiera, incluido el uso de la utilidad cmake -E compare_files . Desea algo como lo siguiente en su archivo CMakeLists.txt:

enable_testing() add_executable(testprog main.c) add_test(NAME runtestprog COMMAND ${CMAKE_COMMAND} -DTEST_PROG=$<TARGET_FILE:testprog> -DSOURCEDIR=${CMAKE_CURRENT_SOURCE_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/runtest.cmake)

Esto ejecuta un script (cmake -P runtest.cmake) y define 2 variables: TEST_PROG, establecido en la ruta del ejecutable de prueba, y SOURCEDIR, establecido en el directorio de origen actual. Necesita el primero para saber qué programa ejecutar, el segundo para saber dónde encontrar los archivos de resultados de prueba esperados. El contenido de runtest.cmake sería:

execute_process(COMMAND ${TEST_PROG} RESULT_VARIABLE HAD_ERROR) if(HAD_ERROR) message(FATAL_ERROR "Test failed") endif() execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files output.txt ${SOURCEDIR}/expected.txt RESULT_VARIABLE DIFFERENT) if(DIFFERENT) message(FATAL_ERROR "Test failed - files differ") endif()

El primer execute_process ejecuta el programa de prueba, que escribirá "output.txt". Si eso funciona, entonces el siguiente execute_process ejecuta efectivamente cmake -E compare_files output.txt expected.txt . El archivo "expected.txt" es el buen resultado conocido en su árbol fuente. Si hay diferencias, se produce un error para que pueda ver la prueba fallida.

Lo que esto no hace es imprimir las diferencias; CMake no tiene una implementación "dif" completa oculta dentro de él. En el momento en que usa Subversion para ver qué líneas han cambiado, entonces una solución obvia es cambiar la última parte para:

if(DIFFERENT) configure_file(output.txt ${SOURCEDIR}/expected.txt COPYONLY) execute_process(COMMAND svn diff ${SOURCEDIR}/expected.txt) message(FATAL_ERROR "Test failed - files differ") endif()

Esto sobrescribe el árbol de origen con el resultado de compilación en caso de error y luego ejecuta svn diff en él. El problema es que no deberías ir cambiando el árbol fuente de esta manera. Cuando ejecutas la prueba por segunda vez, pasa! Una mejor manera es instalar alguna herramienta de visualización visual y ejecutarla en la salida y el archivo esperado.

Hasta ahora, he usado un procedimiento de prueba de unidad improvisado, básicamente una carga completa de programas de prueba de unidad ejecutados automáticamente por un archivo por lotes. Aunque muchos de estos comprueban explícitamente sus resultados, muchos más trucos: descargan los resultados en archivos de texto que están versionados. Cualquier cambio en los resultados de la prueba se marca con subversión y puedo identificar fácilmente cuál fue el cambio. Muchas de las pruebas generan archivos de puntos o alguna otra forma que me permite obtener una representación visual de la salida.

El problema es que estoy cambiando a usar cmake. Ir con el flujo de cmake significa usar compilaciones fuera de la fuente, lo que significa que la conveniencia de descartar los resultados en una carpeta de fuente / compilación compartida y su versión junto con la fuente realmente no funciona.

Como reemplazo, lo que me gustaría hacer es decirle a la herramienta de prueba de la unidad dónde encontrar los archivos de los resultados esperados (en el árbol de origen) y hacer que haga la comparación. En caso de fallo, debe proporcionar los resultados reales y las listas de diferencias.

¿Es esto posible, o debo tomar un enfoque completamente diferente?

Obviamente, podría ignorar ctest y simplemente adaptar lo que siempre he hecho a las compilaciones fuera de la fuente. Podría versionar mi carpeta donde todas las compilaciones en vivo, por ejemplo (con el uso liberal de ''ignorar'' por supuesto). ¿Eso es cuerdo? Probablemente no, ya que cada compilación terminaría con una copia separada de los resultados esperados.

Además, cualquier consejo sobre la forma recomendada de hacer pruebas de unidad con cmake / ctest agradecido recibido. Perdí un poco de tiempo con cmake, no porque sea malo, sino porque no entendía cómo trabajar mejor con él.

EDITAR

Al final, decidí que la prueba del lado de cmake / ctest de la unidad fuera lo más simple posible. Para probar los resultados reales y los resultados esperados, encontré un hogar para la siguiente función en mi biblioteca ...

bool Check_Results (std::ostream &p_Stream , const char *p_Title , const char **p_Expected, const std::ostringstream &p_Actual ) { std::ostringstream l_Expected_Stream; while (*p_Expected != 0) { l_Expected_Stream << (*p_Expected) << std::endl; p_Expected++; } std::string l_Expected (l_Expected_Stream.str ()); std::string l_Actual (p_Actual.str ()); bool l_Pass = (l_Actual == l_Expected); p_Stream << "Test: " << p_Title << " : "; if (l_Pass) { p_Stream << "Pass" << std::endl; } else { p_Stream << "*** FAIL ***" << std::endl; p_Stream << "===============================================================================" << std::endl; p_Stream << "Expected Results For: " << p_Title << std::endl; p_Stream << "-------------------------------------------------------------------------------" << std::endl; p_Stream << l_Expected; p_Stream << "===============================================================================" << std::endl; p_Stream << "Actual Results For: " << p_Title << std::endl; p_Stream << "-------------------------------------------------------------------------------" << std::endl; p_Stream << l_Actual; p_Stream << "===============================================================================" << std::endl; } return l_Pass; }

Una prueba unitaria típica ahora parece algo así como ...

bool Test0001 () { std::ostringstream l_Actual; const char* l_Expected [] = { "Some", "Expected", "Results", 0 }; l_Actual << "Some" << std::endl << "Actual" << std::endl << "Results" << std::endl; return Check_Results (std::cout, "0001 - not a sane test", l_Expected, l_Actual); }

Donde necesito una función de volcado de datos reutilizable, toma un parámetro de tipo std::ostream& , para que pueda volcarse en una secuencia de resultados reales.