試(十)——嵌入式CI/CD流水線集成單元測(cè)試:Jenkins + Ceedling實(shí)例)
摘要本文以 Jenkins 和 Ceedling 為例介紹如何在嵌入式 C 項(xiàng)目中搭建集成單元測(cè)試的 CI/CD 流水線。文章先分析傳統(tǒng)開(kāi)發(fā)模式中測(cè)試滯后、回歸成本高、環(huán)境不一致等痛點(diǎn)以及 CI/CD 帶來(lái)的快速反饋、質(zhì)量門(mén)禁和可追溯性收益隨后講解 Ceedling 與 Jenkins 的技術(shù)選型與環(huán)境準(zhǔn)備并逐步演示 Ceedling 測(cè)試工程的創(chuàng)建、單元測(cè)試的編寫(xiě)與本地運(yùn)行以及 Jenkins 流水線的配置、JUnit 報(bào)告輸出和結(jié)果分析。最后介紹 Docker Agent 環(huán)境隔離、代碼覆蓋率集成和多平臺(tái)交叉編譯等進(jìn)階實(shí)踐并給出常見(jiàn)問(wèn)題的排查方案幫助團(tuán)隊(duì)將單元測(cè)試從“事后補(bǔ)救”轉(zhuǎn)變?yōu)椤俺掷m(xù)保障”。1. 引言在嵌入式軟件開(kāi)發(fā)中單元測(cè)試往往被視為“成本高、收益低”的環(huán)節(jié)而被推遲甚至省略。然而隨著產(chǎn)品復(fù)雜度上升、交付節(jié)奏加快缺乏自動(dòng)化測(cè)試的嵌入式項(xiàng)目往往在集成階段暴露出大量缺陷修復(fù)成本成倍增長(zhǎng)。將單元測(cè)試嵌入 CI/CD 流水線是解決這一問(wèn)題的關(guān)鍵手段。本文以 Jenkins 和 Ceedling 為例介紹如何在嵌入式項(xiàng)目中搭建一條集成單元測(cè)試的 CI/CD 流水線。Ceedling 是嵌入式 C 語(yǔ)言項(xiàng)目常用的測(cè)試構(gòu)建框架基于 Unity 和 CMock 提供測(cè)試運(yùn)行與樁代碼生成能力Jenkins 則負(fù)責(zé)流水線編排、定時(shí)觸發(fā)與結(jié)果展示。兩者結(jié)合可以讓單元測(cè)試在每次代碼提交后自動(dòng)運(yùn)行并把測(cè)試報(bào)告反饋給開(kāi)發(fā)團(tuán)隊(duì)。2. 為什么嵌入式項(xiàng)目需要 CI/CD 集成單元測(cè)試嵌入式項(xiàng)目與純軟件項(xiàng)目相比存在交叉編譯、目標(biāo)板依賴(lài)、硬件資源受限等特殊性導(dǎo)致自動(dòng)化測(cè)試落地難度更大。但正因?yàn)槿绱吮M早發(fā)現(xiàn)問(wèn)題的價(jià)值也更高。2.1 傳統(tǒng)開(kāi)發(fā)模式的痛點(diǎn)測(cè)試滯后單元測(cè)試往往在功能開(kāi)發(fā)完成后才補(bǔ)寫(xiě)甚至直接跳過(guò)缺陷在集成階段集中爆發(fā)。回歸成本高修改底層驅(qū)動(dòng)或算法后缺乏快速回歸手段人工驗(yàn)證耗時(shí)且容易遺漏。環(huán)境不一致開(kāi)發(fā)者本地環(huán)境與 CI 環(huán)境差異大導(dǎo)致“本地能過(guò)、CI 失敗”的尷尬局面。2.2 CI/CD 帶來(lái)的收益快速反饋每次提交自動(dòng)觸發(fā)測(cè)試幾分鐘內(nèi)即可獲知是否引入回歸。質(zhì)量門(mén)禁測(cè)試失敗時(shí)阻斷合并防止缺陷流入主干。可追溯性測(cè)試報(bào)告與構(gòu)建記錄關(guān)聯(lián)便于定位問(wèn)題引入的提交。3. 技術(shù)選型Jenkins 與 Ceedling 簡(jiǎn)介3.1 CeedlingCeedling 是面向嵌入式 C 項(xiàng)目的測(cè)試構(gòu)建系統(tǒng)底層依賴(lài) Unity斷言框架、CMock樁代碼生成和 Ruby 生態(tài)。它通過(guò)一個(gè)project.yml配置文件管理源碼路徑、測(cè)試路徑、編譯選項(xiàng)和模擬器設(shè)置能夠自動(dòng)發(fā)現(xiàn)測(cè)試文件并生成可執(zhí)行測(cè)試程序。Ceedling 的核心優(yōu)勢(shì)在于自動(dòng)發(fā)現(xiàn)并編譯測(cè)試文件無(wú)需手寫(xiě) Makefile。內(nèi)置 CMock自動(dòng)為外部依賴(lài)生成樁函數(shù)。支持 GCC 本地模擬運(yùn)行也可對(duì)接模擬器或目標(biāo)板。輸出 JUnit 格式測(cè)試報(bào)告便于 CI 工具解析。3.2 JenkinsJenkins 是使用最廣泛的開(kāi)源 CI/CD 服務(wù)器支持 Pipeline 即代碼、分布式構(gòu)建、豐富的插件生態(tài)。對(duì)于嵌入式項(xiàng)目Jenkins 可以運(yùn)行在 Linux 主機(jī)上通過(guò) Docker 或 Agent 節(jié)點(diǎn)提供交叉編譯工具鏈和測(cè)試環(huán)境。4. 環(huán)境準(zhǔn)備4.1 安裝 CeedlingCeedling 依賴(lài) Ruby 環(huán)境安裝命令如下# 安裝 Ruby以 Ubuntu 為例 sudo apt-get install ruby-full build-essential 安裝 Ceedling gem install ceedling4.2 安裝 JenkinsJenkins 的安裝方式較多這里以 Docker 方式為例便于快速搭建和遷移docker run -d \ --name jenkins \ -p 8080:8080 \ -p 50000:50000 \ -v jenkins_home:/var/jenkins_home \ -v /opt/embedded-toolchain:/opt/embedded-toolchain \ jenkins/jenkins:lts注意如果項(xiàng)目需要交叉編譯工具鏈需要將工具鏈目錄掛載到 Jenkins 容器中或在 Jenkins Agent 節(jié)點(diǎn)上預(yù)先安裝。5. 創(chuàng)建 Ceedling 測(cè)試工程5.1 初始化工程結(jié)構(gòu)在項(xiàng)目根目錄執(zhí)行以下命令生成 Ceedling 標(biāo)準(zhǔn)目錄結(jié)構(gòu)ceedling new embedded_ci_demo cd embedded_ci_demo生成的目錄結(jié)構(gòu)如下embedded_ci_demo/ ├── project.yml ├── src/ ├── test/ ├── lib/ └── build/5.2 配置 project.yml編輯project.yml配置源碼路徑、測(cè)試路徑和編譯選項(xiàng)5.3 編寫(xiě)被測(cè)模塊在src/目錄下創(chuàng)建一個(gè)簡(jiǎn)單的計(jì)算模塊calculator.c和頭文件calculator.h5.4 編寫(xiě)單元測(cè)試在test/目錄下創(chuàng)建測(cè)試文件test_calculator.c5.5 本地運(yùn)行測(cè)試在項(xiàng)目根目錄執(zhí)行ceedling test:all如果一切正常會(huì)看到類(lèi)似輸出Test test_calculator.c ----------------------- - OVERALL TEST SUMMARY ----------------------- TESTED: 2 PASSED: 2 FAILED: 0 IGNORED: 0 ----------------------- BUILD SUCCESS6. 配置 Jenkins 流水線6.1 創(chuàng)建 Jenkinsfile在項(xiàng)目根目錄創(chuàng)建Jenkinsfile定義流水線階段。這里采用聲明式流水線語(yǔ)法6.2 配置 Ceedling 輸出 JUnit 報(bào)告為了讓 Jenkins 能解析測(cè)試結(jié)果需要在project.yml中啟用 JUnit 報(bào)告插件:plugins: :load_paths: - #{Ceedling.load_path} :enabled: - report_tests_junit :report_tests_junit: :artifact_filename: report.xml :artifact_path: build/artifacts/test重新運(yùn)行ceedling test:all會(huì)在build/artifacts/test/目錄下生成 JUnit 格式的report.xml文件。6.3 在 Jenkins 中創(chuàng)建流水線任務(wù)打開(kāi) Jenkins 管理界面點(diǎn)擊「新建任務(wù)」。輸入任務(wù)名稱(chēng)選擇「流水線」類(lèi)型。在「流水線」配置區(qū)域選擇「Pipeline script from SCM」。填寫(xiě) Git 倉(cāng)庫(kù)地址和分支腳本路徑填寫(xiě)Jenkinsfile。保存并點(diǎn)擊「立即構(gòu)建」觸發(fā)首次構(gòu)建。7. 流水線運(yùn)行與結(jié)果分析7.1 觸發(fā)構(gòu)建構(gòu)建觸發(fā)方式有三種手動(dòng)觸發(fā)在 Jenkins 任務(wù)頁(yè)面點(diǎn)擊「立即構(gòu)建」。提交觸發(fā)配置 Webhook代碼推送到 Git 倉(cāng)庫(kù)時(shí)自動(dòng)觸發(fā)。定時(shí)觸發(fā)在 Jenkinsfile 中配置triggers字段例如每晚定時(shí)執(zhí)行。7.2 查看測(cè)試報(bào)告構(gòu)建完成后在任務(wù)頁(yè)面點(diǎn)擊「測(cè)試結(jié)果」即可查看 JUnit 報(bào)告。Jenkins 會(huì)展示測(cè)試用例總數(shù)、通過(guò)數(shù)、失敗數(shù)和跳過(guò)數(shù)并支持按模塊或歷史構(gòu)建對(duì)比趨勢(shì)。7.3 失敗定位當(dāng)測(cè)試失敗時(shí)Jenkins 控制臺(tái)輸出會(huì)顯示具體失敗的斷言和堆棧信息。結(jié)合 Ceedling 的詳細(xì)輸出可以快速定位到具體測(cè)試用例和被測(cè)函數(shù)。8. 進(jìn)階實(shí)踐8.1 使用 Docker Agent 隔離環(huán)境為了保持構(gòu)建環(huán)境一致性可以使用 Docker Agent 運(yùn)行流水線。在 Jenkinsfile 中指定 Agent 鏡像pipeline { agent { docker { image embedded-ci:latest args -v /opt/toolchain:/opt/toolchain } } // ... 其余階段 }8.2 集成代碼覆蓋率Ceedling 支持通過(guò) Gcov 生成代碼覆蓋率報(bào)告。在project.yml中啟用 gcov 插件:plugins: :enabled: - report_tests_junit - report_build_gcov :gcov: :utilities: - gcovr然后在 Jenkinsfile 中增加覆蓋率報(bào)告歸檔步驟stage(Publish Coverage Report) { steps { publishCoverage adapters: [gcovParser(build/artifacts/gcov/**/*.xml)] } }8.3 多平臺(tái)交叉編譯測(cè)試對(duì)于需要交叉編譯的項(xiàng)目可以在 Jenkins 中配置多個(gè) Agent 節(jié)點(diǎn)分別對(duì)應(yīng)不同目標(biāo)平臺(tái)。在 Jenkinsfile 中使用矩陣Matrix語(yǔ)法并行執(zhí)行9. 常見(jiàn)問(wèn)題與排查問(wèn)題現(xiàn)象可能原因解決方案Jenkins 中找不到 ceedling 命令Ruby 環(huán)境未安裝或 PATH 未配置在 Jenkinsfile 中顯式指定 gem 安裝路徑或使用 Docker Agent 預(yù)裝環(huán)境測(cè)試報(bào)告為空J(rèn)Unit 插件未啟用或報(bào)告路徑配置錯(cuò)誤檢查 project.yml 中 report_tests_junit 配置確認(rèn)報(bào)告生成路徑與 Jenkinsfile 中 junit 路徑一致交叉編譯工具鏈無(wú)法執(zhí)行工具鏈未掛載到 Jenkins 容器或權(quán)限不足掛載工具鏈目錄并設(shè)置可執(zhí)行權(quán)限或在 Agent 節(jié)點(diǎn)上安裝工具鏈CMock 生成樁代碼失敗頭文件依賴(lài)缺失或函數(shù)原型不完整檢查被測(cè)模塊頭文件是否包含完整函數(shù)聲明必要時(shí)在 project.yml 中配置 :cmock 的 :treat_externs 選項(xiàng)10. 總結(jié)本文以 Jenkins 和 Ceedling 為例完整演示了嵌入式 C 項(xiàng)目集成單元測(cè)試的 CI/CD 流水線搭建過(guò)程。核心要點(diǎn)如下Ceedling 負(fù)責(zé)測(cè)試發(fā)現(xiàn)、編譯、運(yùn)行和報(bào)告生成是嵌入式單元測(cè)試的得力工具。Jenkins 負(fù)責(zé)流水線編排、結(jié)果展示和失敗通知將測(cè)試納入自動(dòng)化交付流程。通過(guò) JUnit 報(bào)告和覆蓋率數(shù)據(jù)團(tuán)隊(duì)可以持續(xù)跟蹤代碼質(zhì)量變化。使用 Docker Agent 和矩陣構(gòu)建可以進(jìn)一步實(shí)現(xiàn)環(huán)境隔離和多平臺(tái)并行測(cè)試。將單元測(cè)試嵌入 CI/CD 流水線并不意味著測(cè)試工作量的減少而是把測(cè)試從“事后補(bǔ)救”轉(zhuǎn)變?yōu)椤俺掷m(xù)保障”。對(duì)于嵌入式團(tuán)隊(duì)而言這是一項(xiàng)值得投入的基礎(chǔ)設(shè)施建設(shè)。