CMake: structure de projet avec tests unitaires

139

J'essaye de structurer mon projet pour inclure les sources de production (dans le srcsous-dossier) et les tests (dans le testsous-dossier). J'utilise CMake pour construire ceci. À titre d'exemple minimal, j'ai les fichiers suivants:

CMakeLists.txt:

cmake_minimum_required (VERSION 2.8) 
project (TEST) 

add_subdirectory (src) 
add_subdirectory (test) 

src / CMakeLists.txt:

add_executable (demo main.cpp sqr.cpp) 

src / sqr.h

#ifndef SQR_H
#define SQR_H
double sqr(double);    
#endif // SQR_H

src / sqr.cpp

#include "sqr.h"
double sqr(double x) { return x*x; }

src / main.cpp - utilise sqr, cela n'a pas vraiment d'importance

test / CMakeLists.txt:

find_package(Boost COMPONENTS system filesystem unit_test_framework REQUIRED)

include_directories (${TEST_SOURCE_DIR}/src) 

ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK) 

add_executable (test test.cpp ${TEST_SOURCE_DIR}/src/sqr.cpp) 

target_link_libraries(test
                      ${Boost_FILESYSTEM_LIBRARY}
                      ${Boost_SYSTEM_LIBRARY}
                      ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
                      )

enable_testing()
add_test(MyTest test)

test / test.cpp:

#define BOOST_TEST_MODULE SqrTests
#include <boost/test/unit_test.hpp>

#include "sqr.h"

BOOST_AUTO_TEST_CASE(FailTest)
{
    BOOST_CHECK_EQUAL(5, sqr(2));
}

BOOST_AUTO_TEST_CASE(PassTest)
{
    BOOST_CHECK_EQUAL(4, sqr(2));
}

Quelques questions:

  1. Cette structure a-t-elle un sens? Quelles sont les meilleures pratiques lors de la structuration de ce code? (Je viens de C # et java, et là c'est plus facile dans un sens)
  2. Je n'aime pas le fait que je doive lister tous les fichiers du srcdossier dans le test/CMakeLists.txtfichier. S'il s'agissait d'un projet de bibliothèque, je lierais simplement la bibliothèque. Existe-t-il un moyen d'éviter de lister tous les fichiers cpp de l'autre projet?
  3. Quelles sont les lignes enable_testing()et add_test(MyTest test)faire? Je n'ai vu aucun effet. Comment puis-je exécuter les tests depuis CMake (ou CTest)?
  4. Jusqu'à présent, je viens de courir cmake .dans le dossier racine, mais cela a créé un désordre avec des fichiers temporaires partout. Comment puis-je obtenir les résultats de la compilation dans une structure raisonnable?
Grzenio
la source
Je me considère comme un novice de CMake, donc je ne sais pas quelles sont les meilleures pratiques acceptées, mais FWIW je créerais une bibliothèque "sqr" * dont dépendaient à la fois le test principal et le test. (* ou son équivalent moral)
user786653

Réponses:

125

Pour les questions 1 et 2, je recommanderais de créer une bibliothèque à partir de vos fichiers non test à l'exclusion de main.cpp (dans ce cas, juste src / sqr.cpp et src / sqr.h), puis vous pouvez éviter de lister (et plus important encore) recompilation) toutes les sources deux fois.

Pour la question 3, ces commandes ajoutent un test appelé "MyTest" qui invoque votre exécutable "test" sans aucun argument. Cependant, puisque vous avez ajouté ces commandes à test / CMakeLists.txt et non à votre CMakeLists.txt de niveau supérieur, vous ne pouvez appeler le test qu'à partir du sous-répertoire "test" de votre arborescence de construction (essayez cd test && ctest -N). Si vous voulez que le test soit exécutable à partir de votre répertoire de construction de niveau supérieur, vous devez appeler à add_testpartir du CMakeLists.txt de niveau supérieur. Cela signifie également que vous devez utiliser la forme la plus détaillée de add_testpuisque votre exe de test n'est pas défini dans le même CMakeLists.txt

Dans votre cas, puisque vous exécutez cmake dans le dossier racine, votre arborescence de construction et votre arborescence source sont identiques. Ceci est connu sous le nom de build in-source et n'est pas idéal, ce qui conduit à la question 4.

La méthode préférée pour générer l'arborescence de construction est de faire une construction hors source, c'est-à-dire de créer un répertoire quelque part en dehors de votre arbre source et d'exécuter cmake à partir de là. Même la création d'un répertoire "build" à la racine de votre projet et son exécution cmake ..fourniraient une structure propre qui n'interférera pas avec votre arborescence source.

Un dernier point est d'éviter d'appeler les exécutables "test" (sensible à la casse). Pour connaître les raisons, lisez cette réponse .

Pour réaliser ces changements, je ferais ce qui suit:

CMakeLists.txt:

cmake_minimum_required (VERSION 2.8)
project (TEST)
add_subdirectory (src) 
add_subdirectory (test)
enable_testing ()
add_test (NAME MyTest COMMAND Test)


src / CMakeLists.txt:

add_library (Sqr sqr.cpp sqr.h)
add_executable (demo main.cpp)
target_link_libraries (demo Sqr)


test / CMakeLists.txt:

find_package (Boost COMPONENTS system filesystem unit_test_framework REQUIRED)
include_directories (${TEST_SOURCE_DIR}/src
                     ${Boost_INCLUDE_DIRS}
                     )
add_definitions (-DBOOST_TEST_DYN_LINK)
add_executable (Test test.cpp)
target_link_libraries (Test
                       Sqr
                       ${Boost_FILESYSTEM_LIBRARY}
                       ${Boost_SYSTEM_LIBRARY}
                       ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
                       )
Fraser
la source
2
Je viens de remarquer que vous avez également ajouté les fichiers .h aux fichiers CMakeLists.txt en conséquence. Est-ce nécessaire? Et que se passerait-il si je les omis?
Grzenio
3
@Grzenio C'est juste une fonctionnalité pratique - ils apparaissent dans les IDE comme MSVC comme partie de la cible, mais sinon cela n'a aucun effet.
Fraser
1
Où est défini TEST_SOURCE_DIR?
aggsol
6
Il est automatiquement défini par CMake lors de l'appel project (TEST)- voir cmake.org/cmake/help/v3.6/variable/PROJECT-NAME_SOURCE_DIR.html
Fraser
> ils apparaissent dans les IDE comme MSVC dans le cadre de la cible --- MSVC ne prend pas en charge le marquage du répertoire avec des en-têtes comme "inclure le répertoire"?
isnullxbh
46

J'aime l'exemple de @Fraser mais j'utiliserais la commande add_test dans le test / CMakeLists.txt et utiliserais enable_testing avant add_subdirectory (test).

De cette façon, vous pouvez exécuter vos tests à partir du répertoire de construction de niveau supérieur tout en spécifiant vos tests dans le fichier test / CMakeLists.txt.

Le résultat ressemblerait à ceci (j'ai réutilisé l'exemple de @Fraser):

CMakeLists.txt

cmake_minimum_required (VERSION 2.8)
project (TEST)
add_subdirectory (src)

enable_testing ()
add_subdirectory (test)

src / CMakeLists.txt

add_library (Sqr sqr.cpp sqr.h)
add_executable (demo main.cpp)
target_link_libraries (demo Sqr)

test / CMakeLists.txt

find_package (Boost COMPONENTS system filesystem unit_test_framework REQUIRED)
include_directories (${TEST_SOURCE_DIR}/src
                     ${Boost_INCLUDE_DIRS}
                     )
add_definitions (-DBOOST_TEST_DYN_LINK)
add_executable (Test test.cpp)
target_link_libraries (Test
                       Sqr
                       ${Boost_FILESYSTEM_LIBRARY}
                       ${Boost_SYSTEM_LIBRARY}
                       ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}
                       )
add_test (NAME MyTest COMMAND Test)
Mathias
la source
1
Merci, je n'ai montré aucun test via ctest -Njusqu'à ce que vous ayez reçu une astuce sur l'activation des tests avant d'ajouter le sous-répertoire.
alaferg
@alaferg: sinon, ils se retrouveraient dans le sous-répertoire de test à l'intérieur du répertoire de construction.
gauteh
4
J'aimerais que CMake ait quelque chose qui ressemble à une structure.
ruipacheco