前面簡單介紹了一下 Boost Test 的寫法,接下來來寫一下要跑測試的時候的東西。
首先,基本上如果是獨立撰寫這種測試程式的話,那寫出來的程式就是一般的執行檔,可以直接執行。
但是實際上,由於測試程式通常會越寫越多、所以基本上最後應該是得要有自動化測試的機制才會比較方便。像是 Visual Studio 就有提供測試功能的整合,可以在「測試總管」中直接看到測試的結果;而 GitLab 也可以透過 CI/CD 來產生測試報告、顯示在網頁上。
這邊就拿 Hersey 放在 GitLab 上的 BoostTest(網址)這個專案來當例子吧~
在這個方案裡面,有「Lib」和「Test」兩個資料夾。
其中「Lib」裡面是一個簡單的函式庫,包含一個不完整的二維向量實作;「Test」則就是使用 Boost Test 開發、針對「Lib」所寫的測試。
主要的測試程式檔案則是 Test/Test.cpp
(連結),裡面有兩個 test case。
而這邊除了有 Visual Studio 2022 的專案檔案之外,也有基本的 Makefile,使用 g++ 來在 Linux 上進行建置。
直接執行時的輸出訊息
而預設執行的時候,基本上他只會顯示錯誤、還有最後彙整的結果,不會有中間的資訊,算是相當簡略。像是這邊的測試程式如果直接執行的話,就只會顯示下面的資訊:
>Test.exe Running 2 test cases... *** No errors detected
而如果要顯示警告、或是其他訊息的話,則需要在執行時加上額外的參數;其中最暴力的方法,基本上就是加上「--log_level=all
」、讓它顯示所有資訊了~
下面就是這樣執行的結果:
>Test.exe --log_level=all Running 2 test cases... Entering test module "Vec2Test" S:\BoostTest\Test\Test.cpp(15): Entering test suite "Vec2_Test" S:\BoostTest\Test\Test.cpp(17): Entering test case "constructor" S:/BoostTest/Test/Test.cpp(20): info: check a.x() == 0.0f has passed S:/BoostTest/Test/Test.cpp(21): info: check a.y() == 0.0f has passed S:/BoostTest/Test/Test.cpp(24): info: check b.x() == 1.0f has passed S:/BoostTest/Test/Test.cpp(25): info: check b.y() == 2.0f has passed S:/BoostTest/Test/Test.cpp(28): info: check c == b has passed S:\BoostTest\Test\Test.cpp(17): Leaving test case "constructor"; testing time: 1939us S:\BoostTest\Test\Test.cpp(31): Entering test case "compute" S:/BoostTest/Test/Test.cpp(34): info: check a + b == c has passed S:\BoostTest\Test\Test.cpp(31): Leaving test case "compute"; testing time: 394us S:\BoostTest\Test\Test.cpp(15): Leaving test suite "Vec2_Test"; testing time: 4158us Leaving test module "Vec2Test"; testing time: 5087us *** No errors detected
而這邊的層級也有很多,詳細列表可以參考官方文件,有需要的話可以根據需要來設定。
如果還希望輸出一些額外的資訊,幫助釐清的話,也可以透過 BOOST_TEST_MESSAGE
這個巨集來輸出文字和變數,例如:
int i = 0; BOOST_TEST_MESSAGE("Test start with i == " << i);
透過 BOOST_TEST_MESSAGE
輸出的訊息層級會是 message,正常執行不會輸出,必須要在執行時加上參數才行。
執行特定測試
如果手邊有一個測試程式,但是跑裡面特定的測試的話,其實也可以透過參數來做控制的。
首先,透過 --list_content
這個參數,可以列出有哪些測試:
>.\Test --list_content
Vec2_Test*
constructor*
compute*
而透過 --run_test
則可以指定要執行的測試;例如:
>.\Test --run_test=Vec2_Test/compute --log_level=all Running 1 test case... Entering test module "Vec2Test" S:\BoostTest\Test\Test.cpp(15): Entering test suite "Vec2_Test" S:\BoostTest\Test\Test.cpp(17): Test case "Vec2_Test/constructor" is skipped because disabled S:\BoostTest\Test\Test.cpp(31): Entering test case "compute" S:/BoostTest/Test/Test.cpp(34): info: check a + b == c has passed S:\BoostTest\Test\Test.cpp(31): Leaving test case "compute"; testing time: 374us S:\BoostTest\Test\Test.cpp(15): Leaving test suite "Vec2_Test"; testing time: 1736us Leaving test module "Vec2Test"; testing time: 2759us *** No errors detected
這邊可以看到,本來兩個 test case 這邊是跳過一個、只執行第二個。
而使用時要注意的,是如果有 test suite 的話,就必須要在 test suite 與 test case 之間加上「/
」、以類似路徑的形式來指定;而如果是要執行 test suite 下的所有測試的話,那就只需要指定 test suite 的名字就好了~
如果是要多個特定的測試的話,則也可以透過「,
」來連接;而如果是不想執行特定測試的話,也可以透過在測試名稱前加上「!
」來排除。
這部分完整的說明,也請自己參考官方文件。
Visual Studio 的整合
在 Visual Studio 裡面,本身就有提供 C++ 單元測試的圖形介面整合功能(官網);針對 Boost Test 和 Google Test 的部分,也有提供「測試配接器」(test adapter)來做串接。
不過由於他是獨立的一個可選取的元件,可以讓使用者選擇是否要安裝;所以要使用的話可能要先執行「Visual Studio Installer」來確認自己有沒有安裝這個元件。
而如果都沒問題的話,那在完成專案的建置後,Visual Studio 就會去檢查建置出來的檔案中有沒有可以使用的測試,如果有找到的話,就會顯示在「測試總管」(test explorer)的視窗中。
不過呢…雖然 Boost Test 的元件也是微軟提供的,但是在欄位的對應上,並沒有做得很好。
Visual Studio 裡面的測試架構中間的分層是「命名空間」和「類別」,和 Boost Test 是用 test suite 不相同;而微軟給的 test adapter 也沒有做轉換,所以變成 Boost Test 的 test case 在測試總管裡面,會完全看不出 test suite 的資訊、全部被放到「<空白命名空間> / <空類別>」下,這點算是比較討厭的。
而在這邊,用滑鼠左鍵雙擊就可以跳到對應的程式碼、確認測試的內容;而透過上方的工具列或是右鍵選單、則可以執行個別的測試、或是所有的測試了~最後也會顯示個別測試的結果,以及彙整的結果。
像是下圖就是另外加了一個 test suite 以及錯誤的 test case 的結果:
上面可以看到,有兩個名為「compute」的測試,實際上是屬於不同的 test suite 的,但是由於 Visual Studio 不會去識別 test suite,所以會放在一起、變得有點難以識別。
而如果點選錯誤的測試的話,詳細資料的窗格也會顯示他的錯誤訊息:
不過,這邊都是理想的狀況。實際上 Heresy 在真正使用中的大型專案中,常常會出現 Visual Studio 沒有找到部分測試的狀況;而由於測試總館中的測試基本上都是靠它在背景自動探索冒出來的,所以如果遇到沒有找到想要執行的測試的時候,其實還滿討厭、也不知道該怎麼解決?
另外,理論上從 Visual Studio 2017 開始,就會在程式碼中每個 test case 的位置加上一個圖示、來代表測試的狀態(參考《Announcing CodeLens for C++ Unit Testing》)、同時也可以快速地執行測試;但是在 Heresy 這邊實際使用呢…恩,不能說都沒有出來過,但是看到那個圖示的機會真的少得可憐…這就不知道是怎麼回事了。
整合到 GitLab CI/CD
而如果自己的程式有用 GitLab 來管理的話,其實也可以透過 GitLab CI/CD 的機制、來自動執行測試的~
在這個例子中,.gitlab-ci.yml
的內容可以寫成下面的樣子(網址):
stages: - build - test build-job: stage: build image: gcc:13 before_script: - apt update; apt install libboost-test-dev -y script: - make artifacts: paths: - TestApp test-job: stage: test image: gcc:13 script: - ./TestApp --logger=HRF,message,stdout:JUNIT,all,TestApp.xml artifacts: reports: junit: TestApp.xml
這邊的 pipeline 分成兩個工作,一個是建置用的「build-job」、一個則是測試用的「test-job」;build-job 建置出來的程式「TestApp
」會透過 artifact 的機制傳遞給 test-job。
這邊由於是直接拿 GitLab 官方提供的 shared runner 來用,所以都是透過 Linux 的 Docker Executor 來跑、沒辦法跑 Windows 版的;如果需要建置、測試 Windows 版本的話,就會需要自己建立 Windows 的 runner 了(參考一、參考二)。
build-job
在 build-job 中,這邊是使用 gcc:13
的 Docker 映像檔,由於有使用 Boost Test,所以在建置前要先透過 apt
安裝 libboost-test-dev
這個套件;之後透過 make
這個指令、就可以透過寫好的 Makefile
來進行建置了~
最後建置出來的測試程式,就是 TestApp
這個檔案;這邊也把他設定成 artifact,除了可以傳遞給 test-job 外,也讓他可以透過 GitLab 的網站系統可以下載。
test-job
測試階段的時候,GitLab 會讓他自動去下載前一個階段的 artifact、也就是 TestApp
這個檔案,所以這邊只要執行程式就好、不需要建置。
不過雖然這邊沒有要編譯程式,但是由於執行 TestApp
的時候會需要 g++13 的 so 檔,所以這邊一樣是使用 gcc:13
這個 Docker 映像檔;相對地,就沒有需要另外安裝 Boost Test 的套件了。
而在執行測試的時候,這邊加上了一個很長的參數「--logger=HRF,message,stdout:JUNIT,all,TestApp.xml
」。
「--logger
」這個參數(官方文件)是用來設定要怎麼輸出 Boost Test 的記錄的,這邊實際上是透過「:
」分成兩個部分。
第一段的「HRF,message,stdout
」的三個項目,分別代表:
HRF
:以人類可閱讀的格式(human readable log format)輸出message
:輸出層級到「message
」為止,效果相當於「--log_level=message
」stdout
:把內容輸出到標準輸出、也就是 console
這部分的目的主要是讓 GitLab 網站上、CI/CD 的工作中可以看到對應的輸出用的(範例)。
第二部分的「JUNIT,all,TestApp.xml
」,則是以 JUNIT 的格式、把所有層級的資訊輸出到 TestApp.xml
這個檔案。要這樣設定的原因,是因為 GitLab 支援的測試報告、就是 JUNIT 格式的 XML 檔案(官方文件)。
而最後,則是指定為 JUNIT report 的格式,把產生出來的 XML 檔案透過 artifact 的形式丟給 GitLab Server。
這樣設定好後,如果一切都正確的話,那在 push 到 GitLab 之後,就會透過 GitLab CI/CD 的機制自動建立出一個 pipeline、來進行建置與測試。
而他的 Pipeline 應該會顯示像下面這樣(網頁):
這邊可以看到,Pipeline 右側的「Tests」標示了「2」、代表有兩個測試;而點進去之後,則是會顯示彙整的測試結果。
這邊會是以 Test Job 作為做最上層,然後列出裡面有的測試狀況。
點選「test-job」後,則是會列出裡面的測試項目:
可以看到,這邊有正確地處理 Boost Test 裡面的 test suite 名稱;但是可惜的是,沒有使用樹狀結構的形式來做管理。
而透過這樣的設定,就可以讓每次 push 到 GitLab Server 的時候,自動進行測試了~這樣一來,如果程式碼改爛了導致測試執行失敗的時候,就會讓整個 pipeline 被標記成失敗、並通知相關的人了。
如果有要針對多平台、或者多種環境測試的話,也可以自行擴展 .gitlab-ci.yml
的內容來達成;像是 Heresy 這邊基本上就是會自動去建置 Windows 版和 Linux 版、並各自進行測試了~
這篇大概就這樣了?和 Visual Studio 與 GitLab 搭配應用的部分,之後應該會繼續講 code coverage(程式碼覆蓋率)的部分。