C++項目組織(使用gtest,cmake和doxygen)




googletest (3)

我是一般的編程新手,所以我決定先從C ++中創建一個簡單的向量類開始。 不過,我想從一開始就接觸良好的習慣,而不是稍後嘗試修改我的工作流程。

我目前只有兩個文件vector3.hppvector3.cpp 。 隨著我對所有東西都變得更加熟悉,這個項目會慢慢地開始增長(使它更像是一個普通的線性代數庫),所以我想採用“標準”的項目佈局來讓生活更輕鬆。 所以在環顧四周之後,我發現了兩種組織hpp和cpp文件的方法,第一種方法是:

project
└── src
    ├── vector3.hpp
    └── vector3.cpp

第二個是:

project
├── inc
│   └── project
│       └── vector3.hpp
└── src
    └── vector3.cpp

你會推薦哪個,為什麼?

其次,我想用谷歌C ++測試框架對我的代碼進行單元測試,因為它似乎很容易使用。 你是否建議將其與我的代碼捆綁在一起,例如在inc/gtestcontrib/gtest文件夾中? 如果捆綁,您是否建議使用fuse_gtest_files.py腳本來減少數量或文件,或者保持原樣? 如果沒有捆綁,這個依賴關係如何處理?

當談到編寫測試時,這些通常是如何組織的? 我想每個類都有一個cpp文件(例如test_vector3.cpp ),但都編譯為一個二進製文件,以便它們可以很容易地一起運行?

因為gtest庫通常是使用cmake和make構建的,所以我想我的項目也可以像這樣構建? 如果我決定使用以下項目佈局:

├── CMakeLists.txt
├── contrib
│   └── gtest
│       ├── gtest-all.cc
│       └── gtest.h
├── docs
│   └── Doxyfile
├── inc
│   └── project
│       └── vector3.cpp
├── src
│   └── vector3.cpp
└── test
    └── test_vector3.cpp

CMakeLists.txt如何查看,以便它可以只構建庫或庫以及測試? 我也見過不少有buildbin目錄的項目。 構建是否發生在構建目錄中,然後將二進製文件移出到bin目錄中? 考試的二進製文件和圖書館會住在同一個地方嗎? 或者更有意義的構造如下:

test
├── bin
├── build
└── src
    └── test_vector3.cpp

我也想用doxygen來記錄我的代碼。 是否有可能得到這個自動運行與cmake和使?

對不起,有很多問題,但我還沒有找到一本關於C ++的書,它能令人滿意地回答這類問題。


構建項目

我通常會贊成以下幾點:

├── CMakeLists.txt
|
├── docs/
│   └── Doxyfile
|
├── include/
│   └── project/
│       └── vector3.hpp
|
├── src/
    └── project/
        └── vector3.cpp
        └── test/
            └── test_vector3.cpp

這意味著你的庫有一套非常清晰的API文件,這個結構意味著你的庫的客戶端可以這樣做

#include "project/vector3.hpp"

而不是那麼不明確

#include "vector3.hpp"


我喜歡/ src樹的結構來匹配/ include樹的結構,但這確實是個人喜好。 但是,如果您的項目擴展為包含/ include / project中的子目錄,通常可以幫助匹配/ src樹中的那些子目錄。

對於測試,我傾向於保持它們“接近”它們測試的文件,如果你最終得到/ src中的子目錄,那麼如果他們想要找到給定文件的測試代碼,那麼其他人可以遵循這個範例。

測試

其次,我想用谷歌C ++測試框架對我的代碼進行單元測試,因為它似乎很容易使用。

Gtest的確使用簡單,功能相當全面。 它可以很容易地與gmock一起使用來擴展它的功能,但我自己的經驗與gmock不太有利。 我很願意接受這可能歸結於我自己的缺點,但gmock測試往往更難創建,並且更難以維護。 gmock棺材中的一個大釘子就是它對智能指針的使用確實不好。

對於一個巨大的問題(這可能不屬於SO),這是一個非常微不足道的和主觀的答案,

你是否建議將其與我的代碼捆綁在一起,例如在“inc / gtest”或“contrib / gtest”文件夾中? 如果捆綁,您是否建議使用fuse_gtest_files.py腳本來減少數量或文件,或者保持原樣? 如果沒有捆綁,這個依賴關係如何處理?

我更喜歡使用CMake的ExternalProject_Add模塊。 這樣可以避免您必須將gtest源代碼保存在存儲庫中,或者將其安裝在任何地方。 它會自動下載並構建在構建樹中。

看到我在這里處理具體問題的答案 。

當談到編寫測試時,這些通常是如何組織的? 我想每個類都有一個cpp文件(例如test_vector3.cpp),但都編譯為一個二進製文件,以便它們可以很容易地一起運行?

好計劃。

建造

我是CMake的粉絲,但是和你的測試相關的問題一樣,SO可能不是就這樣的主觀問題徵求意見的最佳地點。

CMakeLists.txt如何查看,以便它可以只構建庫或庫以及測試?

add_library(ProjectLibrary <All library sources and headers>)
add_executable(ProjectTest <All test files>)
target_link_libraries(ProjectTest ProjectLibrary)

該庫將作為目標“ProjectLibrary”出現,並將測試套件作為目標“ProjectTest”出現。 通過將該庫指定為測試exe的依賴項,構建測試exe將自動導致庫在過期時被重建。

此外,我也看到了不少有一些build目錄的項目。 構建是否發生在構建目錄中,然後將二進製文件移出到bin目錄中? 考試的二進製文件和圖書館會住在同一個地方嗎?

CMake建議使用“源代碼外”構建,即在項目之外創建自己的構建目錄並從那裡運行CMake。 這樣可以避免使用構建文件“污染”源代碼樹,如果您使用的是vcs,則非常可取。

可以指定二進製文件在生成後移動或複製到其他目錄,或者默認情況下在另一個目錄中創建二進製文件,但通常不需要。 CMake提供全面的方法來安裝您的項目,或者使其他CMake項目能夠輕鬆地“查找”您項目的相關文件。

關於CMake自己對查找和執行gtest測試支持,如果你將gtest作為你的項目的一部分來構建,這在很大程度上是不合適的。 FindGtest模塊確實被設計用於在你的項目之外單獨構建gtest的情況。

CMake提供了自己的測試框架(CTest),理想情況下,每個gtest案例都將被添加為CTest案例。

但是, GTEST_ADD_TESTS提供的GTEST_ADD_TESTS宏允許輕鬆添加gtest個案作為單獨的ctest個案,這在某種程度上是缺乏的,因為除了TESTTEST_F之外,它不適用於gtest的宏。 完全不處理使用TEST_PTYPED_TEST_P等進行的TEST_PType-parameterised測試。

這個問題沒有我知道的簡單解決方案。 獲取gtest案例列表最可靠的方法是使用--gtest_list_tests標誌執行test exe。 但是,這只能在exe文件生成後才能完成,所以CMake不能使用它。 這給你兩個選擇; CMake必須嘗試解析C ++代碼以推導出測試的名稱(如果要考慮所有gtest宏,註釋掉的測試,禁用的測試,極端情況並不重要),或者手動添加測試用例CMakeLists.txt文件。

我也想用doxygen來記錄我的代碼。 是否有可能得到這個自動運行與cmake和使?

是的 - 雖然我沒有這方面的經驗。 CMake為此提供了FindDoxygen


C ++構建系統是一種黑色藝術,而這個項目越老,你就可以找到更奇怪的東西,所以出現很多問題並不奇怪。 我將嘗試逐一解答這些問題,並提及關於構建C ++庫的一些常規事項。

分離目錄中的標題和cpp文件。 如果您正在構建一個應該用作庫而不是實際應用程序的組件,這一點非常重要。 您的標題是用戶與您提供的內容進行交互並且必須安裝的基礎。 這意味著它們必須位於子目錄中(沒有人希望大量頭文件在頂級/usr/include/ ),並且頭文件必須能夠包含自己的這種設置。

└── prj
 ├── include
 │   └── prj
 │       ├── header2.h
 │       └── header.h
 └── src
     └── x.cpp

效果很好,因為包含路徑可以解決問題,您可以使用簡單的匹配來安裝目標。

捆綁依賴關係:我認為這在很大程度上取決於構建系統定位和配置依賴關係的能力以及如何將代碼依賴於單個版本。 這也取決於用戶的能力如何,以及在他們的平台上安裝依賴的容易程度。 CMake為Google測試提供了一個find_package腳本。 這使事情變得更容易。 我只會在必要時才捆綁捆綁,否則就避免捆綁。

如何構建:避免內源構建。 CMake使源代碼構建變得容易,並且使生活變得更加容易。

我想你也想用CTest來為你的系統運行測試(它還附帶對GTest的內置支持)。 目錄佈局和測試組織的一個重要決定將是:你最終是否有子項目? 如果是這樣,在設置CMakeLists時需要更多的工作,並且應該將子項目拆分為子目錄,每個子目錄都有自己的includesrc文件。 也許甚至他們自己的doxygen運行和輸出(結合多個doxygen項目是可能的,但不容易或漂亮)。

你最終會得到這樣的結果:

└── prj
    ├── CMakeLists.txt <-- (1)
    ├── include
    │   └── prj
    │       ├── header2.hpp
    │       └── header.hpp
    ├── src
    │   ├── CMakeLists.txt <-- (2)
    │   └── x.cpp
    └── test
        ├── CMakeLists.txt <-- (3)
        ├── data
        │   └── testdata.yyy
        └── testcase.cpp

哪裡

  • (1)配置依賴關係,平台細節和輸出路徑
  • (2)配置您要構建的庫
  • (3)配置測試可執行文件和測試用例

如果你有子組件,我會建議添加另一個層次結構,並為每個子項目使用上面的樹。 然後事情變得棘手,因為您需要決定子組件是否搜索和配置它們的依賴關係,或者如果您在頂層執行這些操作。 這應該根據具體情況來決定。

Doxygen:在設法通過doxygen的配置舞蹈之後,使用CMake add_custom_command來添加doc目標是很簡單的。

這就是我的項目最終結果,我看到了一些非常類似的項目,但這當然不是萬能的。

附錄在某些情況下,您將希望生成一個config.hpp文件,其中包含版本定義,並可能為某個版本控制標識(Git散列或SVN修訂版編號)定義。 CMake具有自動查找信息和生成文件的模塊。 您可以使用CMake的configure_file將模板文件中的變量替換為CMakeLists.txt定義的變量。

如果您正在構建庫,您還需要一個導出定義來正確地獲得編譯器之間的差異,例如MSVC上的__declspec和GCC / clang上的visibility屬性。


除了其他(優秀)答案之外,我將描述一個我用於比較大型項目的結構。
我不打算討論關於Doxygen的子問題,因為我只是重複其他答案中的說法。

合理

為了模塊化和可維護性,該項目被組織為一組小單元。 為了清楚起見,我們將它們命名為UnitX,其中X = A,B,C ...(但它們可以有任何通用名稱)。 然後組織目錄結構以反映這一選擇,並在必要時對單元進行分組。

基本的目錄佈局如下(單位的內容稍後詳述):

project
├── CMakeLists.txt
├── UnitA
├── UnitB
├── GroupA
│   └── CMakeLists.txt
│   └── GroupB
│       └── CMakeLists.txt
│       └── UnitC
│       └── UnitD
│   └── UnitE

project/CMakeLists.txt可能包含以下內容:

cmake_minimum_required(VERSION 3.0.2)
project(project)
enable_testing() # This will be necessary for testing (details below)

add_subdirectory(UnitA)
add_subdirectory(UnitB)
add_subdirectory(GroupA)

project/GroupA/CMakeLists.txt

add_subdirectory(GroupB)
add_subdirectory(UnitE)

project/GroupB/CMakeLists.txt

add_subdirectory(UnitC)
add_subdirectory(UnitD)

現在來看不同單位的結構(讓我們以UnitD為例)

project/GroupA/GroupB/UnitD
├── README.md
├── CMakeLists.txt
├── lib
│   └── CMakeLists.txt
│   └── UnitD
│       └── ClassA.h
│       └── ClassA.cpp
│       └── ClassB.h
│       └── ClassB.cpp
├── test
│   └── CMakeLists.txt
│   └── ClassATest.cpp
│   └── ClassBTest.cpp
│   └── [main.cpp]

對於不同的組件:

  • 我喜歡在同一個文件夾中有源代碼( .cpp )和頭文件( .h )。 這避免了重複的目錄層次結構,使維護更容易。 對於安裝,這是沒有問題的(特別是與CMake)只是過濾頭文件。
  • 目錄UnitD的作用是稍後允許包含帶#include <UnitD/ClassA.h> 。 另外,安裝本機時,您可以直接複製目錄結構。 請注意,您也可以在子目錄中組織源文件。
  • 我喜歡使用README文件來總結單元的內容,並指定有用的信息。
  • CMakeLists.txt可以簡單地包含:

    add_subdirectory(lib)
    add_subdirectory(test)
    
  • lib/CMakeLists.txt

    project(UnitD)
    
    set(headers
        UnitD/ClassA.h
        UnitD/ClassB.h
        )
    
    set(sources
        UnitD/ClassA.cpp
        UnitD/ClassB.cpp    
        )
    
    add_library(${TARGET_NAME} STATIC ${headers} ${sources})
    
    # INSTALL_INTERFACE: folder to which you will install a directory UnitD containing the headers
    target_include_directories(UnitD
                               PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
                               PUBLIC $<INSTALL_INTERFACE:include/SomeDir>
                               )
    
    target_link_libraries(UnitD
                          PUBLIC UnitA
                          PRIVATE UnitC
                          )
    

    在這裡,請注意,沒有必要告訴CMake我們需要UnitAUnitC的include目錄,因為在配置這些單元時已經指定了它。 此外, PUBLIC將告訴所有依賴於UnitD目標,它們應該自動包含UnitA依賴項,而UnitC則不會被要求( PRIVATE )。

  • test/CMakeLists.txt (如果您想使用GTest,請參閱下面的內容):

    project(UnitDTests)
    
    add_executable(UnitDTests
                   ClassATest.cpp
                   ClassBTest.cpp
                   [main.cpp]
                   )
    
    target_link_libraries(UnitDTests
                          PUBLIC UnitD
    )
    
    add_test(
            NAME UnitDTests
            COMMAND UnitDTests
    )
    

使用GoogleTest

對於Google測試,最簡單的方法是,如果源代碼位於源代碼目錄的某個位置,但您不必親自添加它。 我一直在使用這個項目自動下載它,並且我將它的用法封裝在一個函數中,以確保它只下載一次,儘管我們有幾個測試目標。

這個CMake函數如下:

function(import_gtest)
  include (DownloadProject)
  if (NOT TARGET gmock_main)
    include(DownloadProject)
    download_project(PROJ                googletest
                     GIT_REPOSITORY      https://github.com/google/googletest.git
                     GIT_TAG             release-1.8.0
                     UPDATE_DISCONNECTED 1
                     )
    set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent GoogleTest from overriding our compiler/linker options when building with Visual Studio
    add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
  endif()
endfunction()

然後,當我想在我的一個測試目標中使用它時,我會將以下行添加到CMakeLists.txt (這是上面的示例test/CMakeLists.txt ):

import_gtest()
target_link_libraries(UnitDTests gtest_main gmock_main)






googletest