天天看點

軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

作者:硬核老王
進行單元測試可以提高代碼品質,并且它不會打斷你的工作流。
軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

本文是 使用 CMake 和 VSCodium 設定一個建構系統的後續文章。

在上一篇文章中我介紹了基于 VSCodium和CMake配置建構系統。本文我将介紹如何通過GoogleTest和CTest将單元測試內建到這個建構系統中。

首先克隆 這個倉庫,用 VSCodium 打開,切換到

devops_2

标簽。你可以通過點選

main

分支符号(紅框處),然後選擇

devops_2

标簽(黃框處)來進行切換:

軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

或者你可以通過指令行來切換:

$ git checkout tags/devops_2
           

GoogleTest

GoogleTest 是一個平台無關的開源 C++ 測試架構。單元測試是用來驗證單個邏輯單元的行為的。盡管 GoogleTest 并不是專門用于單元測試的,我将用它對

Generator

庫進行單元測試。

在 GoogleTest 中,測試用例是通過斷言宏來定義的。斷言可能産生以下結果:

  • 成功: 測試通過。
  • 非緻命失敗: 測試失敗,但測試繼續。
  • 緻命失敗: 測試失敗,且測試終止。

緻命斷言和非緻命斷言通過不同的宏來區分:

  • ASSERT_*

    : 緻命斷言,失敗時終止。
  • EXPECT_*

    : 非緻命斷言,失敗時不終止。

谷歌推薦使用

EXPECT_*

宏,因為當測試中包含多個的斷言時,它允許繼續執行。斷言有兩個參數:第一個參數是測試分組的名稱,第二個參數是測試自己的名稱。

Generator

隻定義了

generate(...)

函數,是以本文中所有的測試都屬于同一個測試組:

GeneratorTest

針對

generate(...)

函數的測試可以從GeneratorTest.cpp中找到。

引用一緻性檢查

generate(...)函數有一個std::stringstream的引用作為輸入參數,并且它也将這個引用作為傳回值。第一個測試就是檢查輸入的引用和傳回的引用是否一緻。

TEST(GeneratorTest, ReferenceCheck){
    const int NumberOfElements = 10;
    std::stringstream buffer;
    EXPECT_EQ(
        std::addressof(buffer),
        std::addressof(Generator::generate(buffer, NumberOfElements))
    );
}
           

在這個測試中我使用 std::addressof來擷取對象的位址,并用

EXPECT_EQ

來比較輸入對象和傳回對象是否是同一個。

檢查元素個數

本測試檢查作為輸入的

std::stringstream

引用中的元素個數與輸入參數中指定的個數是否相同。

TEST(GeneratorTest, NumberOfElements){
    const int NumberOfElements = 50;
    int nCalcNoElements = 0;

    std::stringstream buffer;

    Generator::generate(buffer, NumberOfElements);
    std::string s_no;

    while(std::getline(buffer, s_no, ' ')) {
        nCalcNoElements++;
    }

    EXPECT_EQ(nCalcNoElements, NumberOfElements);
}
           

亂序重排

本測試檢查随機化引擎是否工作正常。如果連續調用兩次

generate

函數,應該得到的是兩個不同的結果。

TEST(GeneratorTest, Shuffle){

    const int NumberOfElements = 50;

    std::stringstream buffer_A;
    std::stringstream buffer_B;

    Generator::generate(buffer_A, NumberOfElements);
    Generator::generate(buffer_B, NumberOfElements);

    EXPECT_NE(buffer_A.str, buffer_B.str);
}
           

求和校驗

與前面的測試相比,這是一個大體量的測試。它檢查 1 到 n 的數值序列的和與亂序重排後的序列的和是否相等。

generate(...)

函數應該生成一個 1 到 n 的亂序的序列,這個序列的和應當是不變的。

TEST(GeneratorTest, CheckSum){

    const int NumberOfElements = 50;
    int nChecksum_in = 0;
    int nChecksum_out = 0;

    std::vector           

你可以像對一般 C++ 程式一樣調試這些測試。

CTest

除了嵌入到代碼中的測試之外,CTest提供了可執行程式的測試方式。簡而言之就是通過給可執行程式傳入特定的參數,然後用正規表達式對它的輸出進行比對檢查。通過這種方式可以很容易檢查程式對于不正确的指令行參數的反應。這些測試定義在頂層的CMakeLists.txt檔案中。下面我詳細介紹 3 個測試用例:

參數正常

如果輸入參數是一個正整數,程式應該輸出應該是一個數列:

add_test(NAME RegularUsage COMMAND Producer 10)
set_tests_properties(RegularUsage
    PROPERTIES PASS_REGULAR_EXPRESSION "^[0-9 ]+"
)
           

沒有提供參數

如果沒有傳入參數,程式應該立即退出并提示錯誤原因:

add_test(NAME NoArg COMMAND Producer)
set_tests_properties(NoArg
    PROPERTIES PASS_REGULAR_EXPRESSION "^Enter the number of elements as argument"
)
           

參數錯誤

當傳入的參數不是整數時,程式應該退出并報錯。比如給

Producer

傳入參數

ABC

add_test(NAME WrongArg COMMAND Producer ABC)
set_tests_properties(WrongArg
    PROPERTIES PASS_REGULAR_EXPRESSION "^Error: Cannot parse"
)
           

執行測試

可以使用

ctest -R Usage -VV

指令來執行測試。這裡給

ctest

的指令行參數:

  • -R

    : 執行單個測試
  • -VV

    :列印詳細輸出

測試執行結果如下:

$ ctest -R Usage -VV
UpdatecTest Configuration from :/home/stephan/Documents/cpp_testing sample/build/DartConfiguration.tcl
UpdateCTestConfiguration from :/home/stephan/Documents/cpp_testing sample/build/DartConfiguration.tcl
Test project /home/stephan/Documents/cpp_testing sample/build
Constructing a list of tests
Done constructing a list of tests
Updating test list for fixtures
Added 0 tests to meet fixture requirements
Checking test dependency graph...
Checking test dependency graph end
           

在這裡我執行了名為

Usage

的測試。

它以無參數的方式調用

Producer

test 3
    Start 3: Usage
3: Test command: /home/stephan/Documents/cpp testing sample/build/Producer
           

輸出不比對

[^[0-9]+]

的正則模式,測試未通過。

3: Enter the number of elements as argument
1/1 test #3. Usage ................

Failed Required regular expression not found.
Regex=[^[0-9]+]

0.00 sec round.

0% tests passed, 1 tests failed out of 1
Total Test time (real) =
0.00 sec
The following tests FAILED:
3 - Usage (Failed)
Errors while running CTest
$
           

如果想要執行所有測試(包括那些用 GoogleTest 生成的),切換到

build

目錄中,然後運作

ctest

即可:

軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

在 VSCodium 中可以通過點選資訊欄的黃框處來調用 CTest。如果所有測試都通過了,你會看到如下輸出:

軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

使用 Git 鈎子進行自動化測試

目前為止,運作測試是開發者需要額外執行的步驟,那些不能通過測試的代碼仍然可能被送出和推送到代碼倉庫中。利用 Git 鈎子可以自動執行測試,進而防止有瑕疵的代碼被送出。

切換到

.git/hooks

目錄,建立

pre-commit

檔案,複制粘貼下面的代碼:

#!/usr/bin/sh

(cd build; ctest --output-on-failure -j6)
           

然後,給檔案增加可執行權限:

$ chmod +x pre-commit
           

這個腳本會在送出之前調用 CTest 進行測試。如果有測試未通過,送出過程就會被終止:

軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

隻有所有測試都通過了,送出過程才會完成:

軟體開發 | 使用 GoogleTest 和 CTest 進行單元測試

這個機制也有一個漏洞:可以通過

git commit --no-verify

指令繞過測試。解決辦法是配置建構伺服器,這能保證隻有正常工作的代碼才能被送出,但這又是另一個話題了。

總結

本文提到的技術實施簡單,并且能夠幫你快速發現代碼中的問題。做單元測試可以提高代碼品質,同時也不會打斷你的工作流。GoogleTest 架構提供了豐富的特性以應對各種測試場景,文中我所提到的隻是一小部分而已。如果你想進一步了解 GoogleTest,我推薦你閱讀 GoogleTest Primer。

(題圖:MJ/f212ce43-b60b-4005-b70d-8384f2ba5860)

via: https://opensource.com/article/22/1/unit-testing-googletest-ctest

作者:Stephan Avenwedde選題:lujun9972譯者:toknow-gh校對:wxy

本文由 LCTT原創編譯,Linux中國榮譽推出

繼續閱讀