使用Ceedling進行單元測試(嵌入式)C應用程序
https://dmitryfrank.com/articles/unit_testing_embedded_c_applications
就像許多其他嵌入式軟件工程師一樣,我過去常常將嵌入式應用程序交付生產,而沒有對其進行適當的測試。僅進行了一些手動測試。我給人的印象是沒有真正的方法可以對其進行測試:您知道,嵌入式應用程序在自定義硬件中運行並與該特定硬件進行大量交互,這使得它們不容易自動測試。
但是我在這一領域工作的時間越長,我就越認爲應該有某種方法可以使我的應用程序更可靠。當然,我努力開發正確的應用程序設計並編寫良好的實現,但是會發生錯誤(有時非常愚蠢的錯誤)。有一天,我遇到了一本很棒的書:James W. Grenning 撰寫的《嵌入式C的測試驅動開發》。這是一本非常好的書,它對主題進行了詳盡的解釋。強烈推薦。
儘管本文主要基於我從本書中學到的內容,但它不僅僅是其中的簡短版本:在嘗試將我的知識付諸實踐時,我發現了一些新工具,這些工具甚至可以進一步簡化流程。希望本文能幫助您快速入門。
我們將測試什麼
嵌入式開發與其他軟件工程領域的不同之處在於,嵌入式應用程序必須與硬件交互,並且每個應用程序之間的硬件可能有很大的不同。
不幸的是,我已經看到了大量代碼,這些代碼在直接與硬件交互時執行一些應用程序邏輯。有時以效率爲名進行操作(在非常低端的芯片上,甚至函數調用也可能被認爲是昂貴的),但最終它可能會導致一個人的習慣,當然應該避免這種習慣。
主要思想(它本身非常好,不僅對於單元測試而言是很好的)是儘可能地分離硬件交互和應用程序邏輯。然後,我們得到一堆單獨的模塊,可以在硬件外部進行測試。
當我們在編寫應用程序時考慮到可測試性時,我們實際上必須將各部分分開。因此,編寫可測試代碼的“副作用”是模塊化,這是一件好事。可測試的代碼都很好!
因此,在本文中,我們將不會測試硬件層。它必須儘可能薄,我仍然手動對其進行測試。我們將測試的其他所有內容:使用硬件層的模塊以及使用其他模塊的模塊。
儘管存在在目標硬件上運行測試的技術(James W. Grenning的書尤其涉及到這些技術),但本文重點是在主機上進行測試。
也許值得一提的是,單元測試並不是將項目變成完全沒有錯誤的神奇的靈丹妙藥。即使對於桌面編程也不是這樣,在桌面編程中,我們使用相同的編譯器進行測試和生產。在嵌入式世界中,情況甚至更糟,因爲:
-
我們無法測試編譯器的錯誤(不幸的是,嵌入式編譯器往往……不是很好)。
-
軟硬件交互中可能存在細微問題。
-
等等
不過,根據我的個人經驗,經過仔細測試的項目比未經測試的項目要有更好的最終結果。至少,編寫得當的測試可以使您擺脫自己的愚蠢錯誤。而且,如果您像我一樣,您一定會想要的。
我們將使用的工具
前面提到的書利用了幾個很棒的工具:
-
統一。
Unity是C語言中功能強大的C語言單元測試。它旨在支持大多數嵌入式編譯器,從8位微型處理器到64位龐然大物。Unity被設計得很小,但仍然爲您提供了豐富的表達斷言集。
CMock是一個實用程序,用於自動生成Unity測試的存根和模擬。它是Ruby中腳本的集合,這些腳本調查您的C標頭併爲您的測試創建可重用的僞造版本。
這些工具對我來說無疑是改變了生活,但是仍然缺少一些東西:測試構建系統。撰寫本書時可能還不存在,因此本書中完全沒有提及。這裏是:
Ceedling是用於C項目的構建系統,是對Ruby Rake(make-ish)構建系統的擴展。Ceedling主要針對C語言中的測試驅動開發,旨在將CMock,Unity和CException整合在一起-如果您要使用C語言創建出色的代碼,那麼您將無法缺少另外三個出色的開源項目。
有了這些出色的工具,讓我們繼續前進。
樣例項目
毫不奇怪,我們將通過測試示例項目來學習如何編寫單元測試。讓它成爲一個相當簡單的設備,我爲它實際開發了固件:用於電池充電器的指示器,該指示器在具有16 kB閃存和1 kB RAM的8位PIC16 MCU上運行。如您所見,資源非常有限。
可以在以下位置找到此示例項目的存儲庫:https : //github.com/dimonomid/test_ceedling_example。
這個項目看起來像什麼
爲了使項目進入初始狀態,請簽出至標籤v0.02
:
<span style="color:#333333"><span style="color:#333333"> $ git checkout v0.02</span></span>
該設備應僅測量一些電壓(通過ADC將其轉換爲整數),並正確顯示它們。它也應該可以與檢查器設備進行通信,從而對電路進行校準。
初始項目樹如下所示:
<span style="color:#333333"><span style="color:#333333">。
└──src
├──應用
│├──appl_adc_channels.h
│└──適用
├──bsp
│├──bsp_adc.c
│└──bsp_adc.h
└──效用
├──adc_handler.c
├──adc_handler.h
├──itoae.c
├──itoae.h
└──util_macros.h
</span></span>
有三個目錄:
-
appl
:主要應用代碼; -
bsp
:與主板和MCU有關的代碼; -
util
:各種實用程序。
由於資源非常有限,因此我們不使用RTOS:只是一個超級循環。我們也不能使用任何printf
類似函數,因爲它們太昂貴了。而且我們不能使用浮點數:我們僅使用整數。
因此,main()
看起來像這樣:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">int</span> main <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>// TODO:特定於MCU的init</em></span>
<span style="color:#808080"><em>//-初始化MCU專用ADC的東西</em></span>
bsp_adc__init <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//- </em></span>
<span style="color:#b1b100">爲</span> <span style="color:#66cc66">(</span><span style="color:#66cc66">;; </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
bsp_adc__proceed <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">} </span>
<span style="color:#66cc66">}</span></span></span>
對於非RTOS解決方案,這是一個非常常見的方案:在超級循環中,我們只調用每個模塊的proceed
函數,這可能會做一些工作。目前,我們只有一個模塊:bsp_adc
。
該bsp_adc
模塊是依賴於MCU的例程,用於檢索原始ADC值。其API如下所示:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
*用於ADC原始計數的類型。
* / </em></span>
<span style="color:#993333">typedef </span> <span style="color:#993333">uint16_t</span> T_BspAdcCounts <span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*執行模塊初始化,包括硬件初始化
* / </em></span>
<span style="color:#993333">void</span> bsp_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*返回指定通道的原始ADC計數
* /</em></span>
T_BspAdcCounts bsp_adc__value__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*從應用程序的超級循環中反覆調用:存儲當前
*在準備就緒並在不同通道之間切換時進行測量
*適當
* / </em></span>
<span style="color:#993333">void</span> bsp_adc__proceed <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
如您所見,這非常容易。當我們調用時bsp_adc__value__get()
,它返回指定通道的原始ADC值,這並不是特別有用。我們想將其轉換爲更易讀的內容,對嗎?也就是說,以伏特爲單位。
有一個adc_handler
模塊。基本上,它只是對所返回的原始值應用一個求和數和一個乘數bsp_adc__value__get()
,我們得到一個以伏特爲單位的整數值,再乘以某個比例因子(在此示例中,該因子100
被使用;因此,該值1250
表示12.5伏特)。
對於ADC處理程序(struct S_ADCHandler
)的每個實例,我們調用其構造函數:
<span style="color:#333333"><span style="color:#333333">T_ADCHandler_Res adc_handler__ctor <span style="color:#66cc66">(</span>
T_ADCHandler <span style="color:#66cc66">*</span> me <span style="color:#66cc66">,</span> <span style="color:#993333">const</span> T_ADCHandler_CtorParams <span style="color:#66cc66">*</span> p_params
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
傳遞指向參數的指針:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
* ADC處理程序的構造函數參數
* / </em></span>
<span style="color:#993333">typedef </span> <span style="color:#993333">struct</span> S_ADCHandler_CtorParams <span style="color:#66cc66">{</span>
<span style="color:#808080"><em>/ **
* ADC可以返回的最大值。說,對於10位ADC,應該是
* 0x3ff。
* /</em></span>
T_ADCHandler_CountsValue max_counts <span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*可以測量的取決於板的最大電壓
*對應於max_counts。
*
*僅在計算名義乘數時需要。
* /</em></span>
T_ADCHandler_Voltage bsp_max_voltage <span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*校準數據:求和和乘數。
*
*僅將所有零設置爲使用標稱值。名義mul將通過以下方式計算
* max_counts和bsp_max_voltage。添加將從
* nominal_add(見下文)
* /</em></span>
T_ADCHandler_Clb clb <span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*標稱求和,以伏特爲單位
* /</em></span>
T_ADCHandler_Voltage標稱值_add_volts <span style="color:#66cc66">;</span>
<span style="color:#66cc66">}</span> T_ADCHandler_CtorParams <span style="color:#66cc66">;</span></span></span>
然後,當我們想將原始ADC值轉換my_raw_adc_value
爲伏特時,就很簡單:
<span style="color:#333333"><span style="color:#333333">T_ADCHandler_Voltage my_voltage <span style="color:#66cc66">=</span> adc_handler__voltage__get_by_counts_value <span style="color:#66cc66">(</span>
<span style="color:#66cc66">&</span> my_instance <span style="color:#66cc66">,</span> my_raw_adc_value
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
如您所見,ADC處理程序模塊是完全獨立的:它沒有任何依賴性。這樣的模塊是最容易測試的模塊,因此,讓我們從ADC處理程序開始測試過程。
安裝測試工具
在繼續進行之前,我們需要安裝上述Ceedling和所有隨附的工具。感謝ThrowTheSwitch.org的幫助,安裝過程非常簡單。爲此,您需要紅寶石。安裝完ruby後,通過在終端中輸入以下內容來安裝Ceedling:
<span style="color:#333333"><span style="color:#333333">$ gem安裝數量</span></span>
就是這樣!現在您已經準備好所有工具,除其他外,還有一個ceedling
二進制文件,我們將利用它。
向項目添加測試內容
該ceedling
二進制允許我們通過執行命令來創建新的項目樹ceedling new my_project
,甚至包括主源代碼目錄。由於我通常以其他方式創建項目,因此在創建新項目後,我需要移動其他內容。
讓我們繼續:cd
轉到項目的目錄(您在其中擁有src
目錄),然後執行以下操作:
<span style="color:#333333"><span style="color:#333333">$ ceedling新的test_ceedling</span></span>
這將創建test_ceedling
具有以下內容的新目錄:
-
test
:測試C文件的目錄; -
build
:我們的內置測試將位於的目錄; -
vendor
:ceedling提供的二進制文件和所有隨附的東西。它包括CMock,Unity和其他工具。由於我們只是Ceedling的用戶,因此除文檔外,我們對該目錄的實際內容幾乎沒有興趣: -
vendor/ceedling/docs
:吊頂和組件的文檔。很有用; -
src
:Ceedling假定具有源文件的目錄,但我們將不在此處存儲文件,而是在我們自己的src
目錄中存儲文件; -
rakefile.rb
:需要運行測試;您不需要了解它; -
project.yml
:實際的項目文件,我們需要根據需要進行調整。
測試構建系統的核心是項目文件:project.yml
。除其他外,它包含應用程序源文件的路徑。由於ceedling
默認情況下未包含我們的源文件,因此我們需要稍作更改:打開文件project.yml
並找到以下paths
部分:
<span style="color:#333333"><span style="color:#333333">:路徑:
:測試:
-+:測試/ **
--:測試/支持
:資源:
-src / **
:支持:
-測試/支持</span></span>
您可能已經猜到了,我們需要更改src/**
相對於項目文件位置的實際源文件的路徑。好的,這很容易:讓我們添加當前存在的所有源路徑。我們在那裏到達了三個路徑:
<span style="color:#333333"><span style="color:#333333"> :資源:
-../src/appl
-../src/bsp
-../src/util</span></span>
現在,爲了避免混淆,讓我們刪除自動創建的test_ceedling/src
目錄,因爲我們將不使用它。我們還想.gitkeep
在空目錄中添加一些文件,以便git將它們保存在存儲庫中。在存儲庫的根目錄中,輸入:
<span style="color:#333333"><span style="color:#333333">$ touch test_ceedling / build / .gitkeep
$ touch test_ceedling / test / support / .gitkeep</span></span>
將文件添加到存儲庫並提交:
<span style="color:#333333"><span style="color:#333333">$ git添加。
$ git commit</span></span>
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.03
。
實際上,我們的測試構建系統雖然是空的,但可以運行了!嘗試一下:確保您在test_ceedling
目錄中,然後在終端中輸入:
<span style="color:#333333"><span style="color:#333333">$抽水測試:全部</span></span>
您應該看到以下輸出:
<span style="color:#333333"><span style="color:#333333">--------------------
總體測試總結
--------------------
沒有執行測試。
</span></span>
它有效,並且可以預測地報告我們沒有測試。因此,讓我們在骨頭上添加一些肉!
測試獨立模塊
爲ADC處理程序編寫測試
如上所述,我們將從爲ADC處理程序編寫測試開始,因爲它是最容易測試的事情之一:它沒有特定於應用程序的依賴關係。ADC處理程序的工作是將原始ADC計數轉換爲電壓,反之亦然。我們將測試此功能。
首先,讓我們創建空白測試文件。我有一個模板:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
*包含的文件
****************************************************** ******************************** /</em></span>
<span style="color:#808080"><em>//-unity:單元測試框架</em></span>
<span style="color:#339933">#include“ unity.h”</span>
<span style="color:#808080"><em>//-被測試的模塊</em></span>
<span style="color:#808080"><em>// TODO</em></span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*定義
****************************************************** ******************************** /</em></span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*私人類型
****************************************************** ******************************** /</em></span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*私人數據
****************************************************** ******************************** /</em></span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*私人功能
****************************************************** ******************************** /</em></span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*設置,拆卸
****************************************************** ******************************** /</em></span>
<span style="color:#993333">無效</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">無效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#66cc66">}</span>
<span style="color:#993333">void</span> tearDown <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#66cc66">}</span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*測試
****************************************************** ******************************** /</em></span>
<span style="color:#993333">無效</span> test_first <span style="color:#66cc66">(</span><span style="color:#993333">無效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>// TODO </em></span>
<span style="color:#66cc66">}</span></span></span>
每當需要編寫新測試時,我們都可以使用此模板。我們的測試只是名稱以開頭的函數test_
。在上面的示例中,我們只有空test_first()
函數。
我們還有兩個特殊功能:setUp()
和tearDown()
。在setUp()
每次測試之前被調用,並且tearDown()
每次測試後調用。我們將盡快利用它們。現在,讓我們在此處添加ADC處理程序測試。
我們首先添加要測試的模塊的標題:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-正在測試的模塊:ADC處理程序</em></span>
<span style="color:#339933">#include“ adc_handler.h”</span></span></span>
然後,添加一個我們將對其運行測試的實例,以及構造函數返回的結果代碼:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
*私人數據
****************************************************** ******************************** /</em></span>
<span style="color:#993333">靜態</span> T_ADCHandler _adc_handler <span style="color:#66cc66">; </span>
<span style="color:#993333">靜態</span> T_ADCHandler_Res _ctor_result <span style="color:#66cc66">;</span></span></span>
然後,分別在setUp()
/中構造/銷燬它tearDown()
。
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span>
T_ADCHandler_CtorParams params <span style="color:#66cc66">= </span> <span style="color:#66cc66">{ </span><span style="color:#66cc66">} </span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-10位ADC</em></span>
參數。<span style="color:#006600">max_counts </span> <span style="color:#66cc66">= </span> 0x3ff <span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-取決於電路板的最大測得電壓:10 V</em></span>
參數。<span style="color:#006600">bsp_max_voltage </span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">10 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-偏移量爲0伏</em></span>
參數。<span style="color:#006600">標稱_添加_伏特</span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#808080"><em>/ * V * / </em></span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-構造ADC處理程序,將結果保存到_ctor_result</em></span>
_ctor_result <span style="color:#66cc66">=</span> adc_handler__ctor <span style="color:#66cc66">(</span><span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> <span style="color:#66cc66">&</span> params <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
<span style="color:#993333">void</span> tearDown <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span>
adc_handler__dtor <span style="color:#66cc66">(</span><span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
現在,我們能想到的最簡單的測試是檢查構造函數是否已返回成功狀態,即ADC_HANDLER_RES__OK
。因此,將我們的虛擬對象重命名test_first
爲test_ctor_ok
,並添加我們的第一個斷言:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_ctor_ok <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-檢查構造函數是否返回OK</em></span>
TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span> ADC_HANDLER_RES__OK <span style="color:#66cc66">,</span> _ctor_result <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
我們已經準備好進行第一個測試!
<span style="color:#333333"><span style="color:#333333">$抽水測試:全部
測試'test_adc_handler.c'
-------------------------
正在生成test_adc_handler.c的運行程序...
編譯test_adc_handler_runner.c ...
編譯test_adc_handler.c ...
編譯unity.c ...
編譯adc_handler.c ...
編譯cmock.c ...
連結test_adc_handler.out ...
正在運行test_adc_handler.out ...
-----------
測試輸出
-----------
[test_adc_handler.c]
-“
--------------------
總體測試總結
--------------------
測試:1
通過:1
失敗:0
忽略:0</span></span>
它有效,並且我們的測試已通過。好!
除其他事項外,您還可以看到Ceedling已經確定需要構建adc_handler.c
。它是如何工作的?Ceedling檢查我們包含在測試文件中的標頭,並查找適當的源文件。由於adc_handler.h
包含了Ceedling,因此Ceedling會adc_handler.c
在我們在中指定的所有源路徑中查找文件project.yml
並進行編譯。相當容易。
如果您以前使用過TDD做法,那麼您會知道,如果代碼行爲錯誤,最好確保我們的測試失敗。我們在這裏不做TDD,因爲在編寫測試之前我們已經有一些代碼,但是我們仍然可以確保測試能夠失敗。如果我們進行更改adc_handler__ctor()
以使其返回(例如)ADC_HANDLER_RES__CLB_ERR__WRONG_PARAMS
,則測試將失敗,如下所示:
<span style="color:#333333"><span style="color:#333333">-----------
測試輸出
-----------
[test_adc_handler.c]
-“
-------------------
失敗的測試摘要
-------------------
[test_adc_handler.c]
測試:test_ctor_ok
在第(72)行:“預期1是6”
--------------------
總體測試總結
--------------------
測試:1
通過:0
失敗:1
忽略:0
---------------------
建立失敗摘要
---------------------
單元測試失敗。</span></span>
而已。讓我們adc_handler
回到正確的狀態,然後提交。
我們絕對不想在存儲庫中包含構建輸出,因此,添加test_ceedling/build
目錄以忽略列表。在倉庫的根目錄中,創建.gitignore
具有以下內容的文件:
<span style="color:#333333"><span style="color:#333333">test_ceedling /構建</span></span>
然後,提交更改:
<span style="color:#333333"><span style="color:#333333">$ git添加。
$ git commit -m“添加了test_adc_handler”</span></span>
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.04
。
現在,讓我們檢查一下ADC處理程序實際上是否能夠將ADC計數轉換爲電壓。我們將測試該功能adc_handler__voltage__get_by_counts_value()
。使用我們在setUp()
函數中提供的參數,我們需要確保將0
計數轉換爲0.0伏,將0x3ff
計數轉換爲10.0伏,例如,將0x3ff / 3
計數轉換爲3.33伏。
開始了:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">無效</span> test_counts_to_voltage <span style="color:#66cc66">(</span><span style="color:#993333">無效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span>
T_ADCHandler_Voltage電壓<span style="color:#66cc66">;</span>
<span style="color:#808080"><em>// ------------------------------------------------ ------------------</em></span>
電壓<span style="color:#66cc66">=</span> adc_handler__ 電壓__get_by_counts_value <span style="color:#66cc66">(</span>
<span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#cc66cc">0 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">,</span>電壓<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>// ------------------------------------------------ ------------------</em></span>
電壓<span style="color:#66cc66">=</span> adc_handler__ 電壓__get_by_counts_value <span style="color:#66cc66">(</span>
<span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> 0x3ff
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#cc66cc">10 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">,</span>電壓<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>// ------------------------------------------------ ------------------</em></span>
電壓<span style="color:#66cc66">=</span> adc_handler__ 電壓__get_by_counts_value <span style="color:#66cc66">(</span>
<span style="color:#66cc66">&</span> _adc_handler <span style="color:#66cc66">,</span> 0x3ff <span style="color:#66cc66">/ </span> <span style="color:#cc66cc">3 </span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#66cc66">(</span>3.33 <span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">)</span><span style="color:#66cc66">,</span>電壓<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
如果我們運行此測試,它將通過:
<span style="color:#333333"><span style="color:#333333">--------------------
總體測試總結
--------------------
測試:2
通過:2
失敗:0
忽略:0</span></span>
因此,我們現在可以確定ADC處理程序執行其基本工作。
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.05
。
用於整數到字符串轉換的編寫測試
如前所述,我們不能使用浮點數,因爲它們對於這種廉價的MCU來說太昂貴了,因此,我們將電壓存儲爲整數(單位爲伏),乘以係數100
。當然,我們需要將電壓整數值like轉換爲like 1250
的字符串“12.5”
。這就是這個名字叫名字的模塊itoae
的用途。名稱“ itoae”代表“整數到數組擴展”。
其API如下所示:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
* Itoa擴展了一點:允許設置字符串的最小長度
*(有效地使我們可以通過右對齊文本),
*並允許將小數點放在某個固定位置。
*
* @參數p_buf
*在哪裏保存字符串數據
* @參數值
*值轉換爲字符串
* @參數dpp
*小數點位置。如果爲0,則不放置小數點。
*如果爲1,則從右邊開始放一位,依此類推。
* @參數min_len
*字符串的最小長度。如果實際字符串短於
*指定長度,然後將最左邊的字符填充爲
* fill_char。
* @參數fill_char
*字符以填充“額外”空間
* / </em></span>
<span style="color:#993333">void</span> itoae <span style="color:#66cc66">(</span><span style="color:#993333">uint8_t </span> <span style="color:#66cc66">*</span> p_buf <span style="color:#66cc66">,</span> <span style="color:#993333">int</span>值<span style="color:#66cc66">,</span> <span style="color:#993333">int</span> dpp <span style="color:#66cc66">,</span> <span style="color:#993333">int</span> min_len <span style="color:#66cc66">,</span> <span style="color:#993333">uint8_t</span> fill_char <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
您可以在文件中找到源src/util/itoae.c
。該實現相當愚蠢:首先,我們稱“常規” itoa
,它對小數點一無所知,它只是將整數轉換爲字符串。然後,如果需要小數點,則向右移動一些字符,然後插入.
。
如您所見,此功能也非常易於測試。讓我們test_ceedling/test/test_itoae.c
從上面的模板創建新文件,幷包含要測試的模塊的標題:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-正在測試的模塊</em></span>
<span style="color:#339933">#include“ itoae.h”</span></span></span>
我們將爲生成的字符串提供一個緩衝區:
<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#定義_BUF_LEN 20</span>
<span style="color:#808080"><em>/ **
*緩衝區以存儲生成的字符串數據
* / </em></span>
<span style="color:#993333">靜態</span> <span style="color:#993333">uint8_t</span> _buf <span style="color:#66cc66">[</span> _BUF_LEN <span style="color:#66cc66">] </span><span style="color:#66cc66">;</span></span></span>
以及將緩衝區與0xff
:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> _fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#993333">int</span> i <span style="color:#66cc66">; </span>
<span style="color:#b1b100">對於</span> <span style="color:#66cc66">(</span> i <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">< </span> <span style="color:#993333">sizeof </span><span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{_</span>
buf <span style="color:#66cc66">[</span> i <span style="color:#66cc66">] </span> <span style="color:#66cc66">= </span> 0xff <span style="color:#66cc66">; </span>
<span style="color:#66cc66">} </span>
<span style="color:#66cc66">}</span></span></span>
我們將在每個斷言之前調用此函數,以便每次都重新初始化緩衝區。和一些簡單的測試:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">無效</span> test_basic <span style="color:#66cc66">(</span> <span style="color:#993333">無效</span> <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{_</span>
fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 123” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
_fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#66cc66">- </span><span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ -123” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">無效</span> test_dpp <span style="color:#66cc66">(</span> <span style="color:#993333">無效</span> <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{_</span>
fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">1 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 12.3” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
_fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">2 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 1.23” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
_fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">3 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“ 0.123” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
_fill_with_0xff <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
itoae <span style="color:#66cc66">(</span> _buf <span style="color:#66cc66">,</span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">4 </span><span style="color:#66cc66">,</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">,</span> <span style="color:#ff0000">'0' </span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
TEST_ASSERT_EQUAL_STRING <span style="color:#66cc66">(</span><span style="color:#ff0000">“</span> 0.0123 <span style="color:#ff0000">” </span><span style="color:#66cc66">,</span> _buf <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
運行測試:
<span style="color:#333333"><span style="color:#333333">-------------------
失敗的測試摘要
-------------------
[test_itoae.c]
測試:test_dpp
在第(127)行:“預期的'12 .3'是'12 .3 \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF \ 0xFF'
--------------------
總體測試總結
--------------------
測試:4
通過:3
失敗:1
忽略:0</span></span>
糟糕!有些不對勁。似乎儘管點已正確插入到字符串中,但終止符0x00
並沒有向右移動1個字符。
在檢查了源代碼之後,我看到這是令人討厭的代碼段:
<span style="color:#333333"><span style="color:#333333"> <span style="color:#993333">詮釋</span>我<span style="color:#66cc66">; </span>
<span style="color:#b1b100">對於</span> <span style="color:#66cc66">(</span> i <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">< </span> <span style="color:#66cc66">(</span> dpp <span style="color:#66cc66">+ </span> <span style="color:#cc66cc">1 </span><span style="color:#808080"><em>/ * null-terminate * / </em></span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">] </span> <span style="color:#66cc66">=</span> p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">- </span> <span style="color:#cc66cc">1 </span><span style="color:#66cc66">] </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> dpp <span style="color:#66cc66">] </span> <span style="color:#66cc66">= </span> <span style="color:#ff0000">'。</span><span style="color:#66cc66">;</span></span></span>
儘管我提供了終止空字符的處理,但是這裏還是有一個傳統的“一對一”錯誤。正確的代碼如下所示:
<span style="color:#333333"><span style="color:#333333"> <span style="color:#993333">詮釋</span>我<span style="color:#66cc66">; </span>
<span style="color:#b1b100">對於</span> <span style="color:#66cc66">(</span> i <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">< </span> <span style="color:#66cc66">(</span> dpp <span style="color:#66cc66">+ </span> <span style="color:#cc66cc">1 </span><span style="color:#808080"><em>/ * null-terminate * / </em></span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span> i <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">+ </span> <span style="color:#cc66cc">1 </span><span style="color:#66cc66">] </span> <span style="color:#66cc66">=</span> p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> i <span style="color:#66cc66">] </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
p_buf <span style="color:#66cc66">[</span> len <span style="color:#66cc66">-</span> dpp <span style="color:#66cc66">] </span> <span style="color:#66cc66">= </span> <span style="color:#ff0000">'。</span><span style="color:#66cc66">;</span></span></span>
保存,切換到終端,再次運行測試:
<span style="color:#333333"><span style="color:#333333">--------------------
總體測試總結
--------------------
測試:4
通過:4
失敗:0
忽略:0</span></span>
涼!這些非常簡單的測試已經使我擺脫了愚蠢的錯誤。信不信由你,這個錯誤itoae
實際上已經發生在我身上,測試有助於揭示它。這一集很快鼓勵我將更多的時間投入到測試中。
注意:本文並不包含所有測試代碼,因爲它是非常重複和直接的。您可以使用準備好的存儲庫來完成所有工作。在此處輸入:git checkout v0.06
。
測試具有依賴性的模塊
應用ADC模塊
我們的應用程序需要一些資源豐富的方法來獲取特定通道上的當前電壓(以伏特爲單位)。在整個應用程序中使用我們的bsp_adc
+ adc_handler
模塊是不明智的。
現在,讓我們添加模塊appl_adc
,該模塊至少具有獲取某些通道上當前電壓(以伏特爲單位)的功能。
標頭src/appl/appl_adc.h
應至少包含以下內容:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-對於T_ADCHandler_Voltage </em></span>
<span style="color:#339933">#include“ adc_handler.h”</span>
<span style="color:#808080"><em>//-對於枚舉E_ApplAdcChannel </em></span>
<span style="color:#339933">#include“ appl_adc_channels.h”</span>
<span style="color:#808080"><em>/ **
*初始化模塊
* / </em></span>
<span style="color:#993333">void</span> appl_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ **
*獲取給定通道的當前電壓。
* /</em></span>
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
此函數應調用bsp_adc__value__get()
給定channel_num
,然後將返回值饋送到相應的adc_handler
,並以伏特爲單位返回結果值。
現在,讓我們僞造實現(src/appl/appl_adc.c
):
<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#include“ appl_adc.h”</span>
<span style="color:#993333">void</span> appl_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//待辦事項</em></span>
<span style="color:#66cc66">}</span>
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>// TODO </em></span>
<span style="color:#b1b100">返回</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
然後進行測試:創建新文件test_appl_adc.c
,然後將測試模板放入其中。與往常一樣,包括要測試的模塊的標題:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-正在測試的模塊</em></span>
<span style="color:#339933">#include“ appl_adc.h”</span></span></span>
我們的setUp()
和tearDown()
非常簡單:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-在每次測試之前,重新初始化appl_adc模塊</em></span>
appl_adc__init <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
<span style="color:#993333">void</span> tearDown <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-此處無操作</em></span>
<span style="color:#66cc66">}</span></span></span>
並進行以下測試appl_adc__voltage__get()
:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">無效</span> test_voltage_get <span style="color:#66cc66">(</span><span style="color:#993333">無效</span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>// ..... </em></span>
<span style="color:#66cc66">}</span></span></span>
如前所述,appl_adc__voltage__get()
首先應該調用bsp_adc__value__get()
適當的通道。我們如何測試呢?
答案是-使用CMock。Ceedling也會在這裏爲我們提供幫助:如果我們需要“模擬”某個模塊,我們要做的就是添加要模擬的模塊的標頭,並加上mock_
前綴(實際上,該前綴可在中自定義project.yml
,並且默認情況下爲mock_
)。
然後繼續:添加以下include指令:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//- </em></span>
<span style="color:#339933">模擬的</span><span style="color:#808080"><em>模塊</em></span><span style="color:#339933">#include“ mock_bsp_adc.h”</span></span></span>
現在,正在測試的模塊將使用中所有功能的模擬版本bsp_adc.h
,並且爲我們提供了“期望”功能。讓我們看看它們的作用:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_voltage_get <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-我們期望bsp_adc__value__get()被調用:</em></span>
bsp_adc__value__get_ExpectAndReturn <span style="color:#66cc66">(</span>
<span style="color:#808080"><em>//-期望提供給</em></span>
<span style="color:#808080"><em>// bsp_adc__value__get()</em></span>
APPL_ADC_CH__I_SETT的參數<span style="color:#66cc66">,</span>
<span style="color:#808080"><em>//-以及bsp_adc__value__get()應該返回的值</em></span>
<span style="color:#66cc66">(</span>0x3ff <span style="color:#66cc66">/ </span> <span style="color:#cc66cc">2 </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-實際調用要測試的函數,該函數應該執行</em></span>
<span style="color:#808080"><em>//所有未決的預期調用</em></span>
T_ADCHandler_Voltage voltage <span style="color:#66cc66">=</span> appl_adc__voltage__get <span style="color:#66cc66">(</span>
APPL_ADC_CH__I_SETT
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-檢查返回的電壓(我們假設</em></span>
<span style="color:#808080"><em>使用相同的參數// </em></span>
<span style="color:#808080"><em>初始化了adc_handler </em></span><span style="color:#808080"><em>,其中0x3ff是ADC的最大值,並且</em></span><span style="color:#808080"><em>//它對應於值(10 * ADC_HANDLER__SCALE_FACTOR__U))</em></span>
TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#66cc66">(</span><span style="color:#cc66cc">5 </span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">)</span><span style="color:#66cc66">,</span>電壓<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
如果運行測試,則會得到以下結果:
<span style="color:#333333"><span style="color:#333333">-------------------
失敗的測試摘要
-------------------
[test_appl_adc.c]
測試:test_voltage_get
在第(77)行:“預期500是0”
</span></span>
因此,它抱怨返回的值是錯誤的。好的,讓我們appl_adc__voltage__get()
進一步僞造我們的假人:使其返回,500
而不是0
:
<span style="color:#333333"><span style="color:#333333">T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>// TODO </em></span>
<span style="color:#b1b100">返回</span> <span style="color:#cc66cc">500 </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
並再次運行測試:
<span style="color:#333333"><span style="color:#333333"><span style="color:#66cc66">-------------------</span>
失敗的測試摘要
<span style="color:#66cc66">------------------- </span>
<span style="color:#66cc66">[</span> test_appl_adc。<span style="color:#006600">c </span><span style="color:#66cc66">]</span>
測試<span style="color:#66cc66">:</span> test_voltage_get
在行<span style="color:#66cc66">(</span><span style="color:#cc66cc">56 </span><span style="color:#66cc66">)</span><span style="color:#66cc66">:</span> <span style="color:#ff0000">“功能‘bsp_adc__value__get’叫倍低於預期。”</span></span></span>
哦,太酷了!它報告該函數的bsp_adc__value__get()
調用次數少於預期的次數(實際上,根本沒有調用該函數)。CMock在行動!
那麼,它的時間來執行appl_adc__voltage__get()
或多或少完全:我們要同時使用bsp_adc
,並adc_handler
在那裏。
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
*包含的文件
****************************************************** ******************************** /</em></span>
<span style="color:#339933">#include“ appl_adc.h” </span>
<span style="color:#339933">#include“ appl_adc_channels.h” </span>
<span style="color:#339933">#include“ bsp_adc.h”</span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*私人數據
****************************************************** ******************************** /</em></span>
<span style="color:#993333">靜態</span> T_ADCHandler _adc_handlers <span style="color:#66cc66">[</span> APPL_ADC_CH_CNT <span style="color:#66cc66">] </span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>/ ***************************************************** ******************************
*公開功能
****************************************************** ******************************** /</em></span>
<span style="color:#993333">void</span> appl_adc__init <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-將所有ADC通道初始化</em></span>
<span style="color:#b1b100">爲</span> <span style="color:#66cc66">(</span> channel <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span> channel <span style="color:#66cc66"><</span> APPL_ADC_CH_CNT <span style="color:#66cc66">;</span> channel <span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
T_ADCHandler_CtorParams params <span style="color:#66cc66">= </span> <span style="color:#66cc66">{ </span><span style="color:#66cc66">} </span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-在這裏,我們使用相同的參數初始化所有通道,</em></span>
<span style="color:#808080"><em>//但實際上,不同的ADC通道當然可能</em></span>
<span style="color:#808080"><em>具有不同的參數。</em></span>
參數。<span style="color:#006600">max_counts </span> <span style="color:#66cc66">= </span> 0x3ff <span style="color:#66cc66">; </span>
參數。<span style="color:#006600">bsp_max_voltage </span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">10 </span><span style="color:#808080"><em>/ * V * / </em></span> <span style="color:#66cc66">*</span> ADC_HANDLER__SCALE_FACTOR__U <span style="color:#66cc66">; </span>
參數。<span style="color:#006600">標稱_添加_伏特</span> <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#808080"><em>/ * V * / </em></span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-構造ADC處理程序,將結果保存到_ctor_result</em></span>
adc_handler__ctor <span style="color:#66cc66">(</span><span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel <span style="color:#66cc66">] </span><span style="color:#66cc66">,</span> <span style="color:#66cc66">&</span> params <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">} </span>
<span style="color:#66cc66">}</span>
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#b1b100">返回</span> adc_handler__voltage__get_by_counts_value <span style="color:#66cc66">(</span>
<span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel_num <span style="color:#66cc66">] </span><span style="color:#66cc66">,</span>
bsp_adc__value__get <span style="color:#66cc66">(</span> channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
好的,看來,一切都正確。嘗試運行測試:
<span style="color:#333333"><span style="color:#333333">連結test_appl_adc.out ...
build / test / out / appl_adc.o:在函數'0097?ppl_adc__init'中:
/home/dimon/projects/indicator_git/test_ceedling/../src/appl/appl_adc.c:70:未定義對'0097?dc_handler__ctor'的引用
build / test / out / appl_adc.o:在函數'0097?ppl_adc__voltage__get'中:
/home/dimon/projects/indicator_git/test_ceedling/../src/appl/appl_adc.c:76:未定義對'0097?dc_handler__voltage__get_by_counts_value'的引用
collect2:錯誤:ld返回1退出狀態
....
注意:如果鏈接器報告缺少符號,則可能是以下原因:
1.測試缺少與所需源文件相對應的#include語句。
2.項目搜索路徑不包含與測試中的#include語句相對應的源文件。
3.測試未#include所需的模擬。</span></span>
噢親愛的。鏈接器抱怨未定義對ADC處理函數的引用。Ceedling通過向我們提供有用的通知而非常友好:正如它所暗示的那樣,可能的原因之一是測試缺少#include
與所需源文件相對應的語句。您還記得Ceedling檢查我們包含在測試文件中的標頭,並尋找適當的源文件嗎?因此,要使其正常工作,我們應該將標頭包含在adc_handler.h
我們的中test_appl_adc.c
:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//-其他需要編譯的模塊</em></span>
<span style="color:#339933">#include“ adc_handler.h”</span></span></span>
現在,測試應該終於可以工作了:
<span style="color:#333333"><span style="color:#333333">--------------------
總體測試總結
--------------------
測試:7
通過:7
失敗:0
忽略:0</span></span>
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.07
。
應用ADC模塊的更隔離測試
您可能已經注意到,在上一節中,我們實際上最終一次測試了兩個模塊:appl_adc.c
和adc_handler.c
。似乎不對:至少,我們已經針對進行了專用測試adc_handler.c
,對吧?畢竟,即使是“單元測試”一詞也表明我們應該一次測試一個單元。儘管有時我會讓自己打破這個規則,但讓我們嘗試儘可能地隔離測試appl_adc.c
。
您可能已經猜到我們也要模擬ADC處理程序,而不是使用實際代碼。因此,首先,讓我們刪除#include "adc_handler.h"
,幷包括模擬版本:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>//- </em></span>
<span style="color:#339933">模擬的</span>
<span style="color:#808080"><em>模塊</em></span><span style="color:#339933">#include“ mock_bsp_adc.h” </span><span style="color:#339933">#include“ mock_adc_handler.h”</span></span></span>
現在,我們實際上有幾種選擇。
忽略提供給ADC處理程序的參數
最簡單的選擇是忽略提供給adc_handler__voltage__get_by_counts_value()
任何參數。這就是…_IgnoreAndReturn()
功能的用途(由CMock生成):
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_voltage_get <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-我們期望bsp_adc__value__get()被調用:</em></span>
bsp_adc__value__get_ExpectAndReturn <span style="color:#66cc66">(</span>
<span style="color:#808080"><em>//-期望提供給</em></span>
<span style="color:#808080"><em>// bsp_adc__value__get()</em></span>
APPL_ADC_CH__I_SETT的參數<span style="color:#66cc66">,</span>
<span style="color:#808080"><em>//-和bsp_adc__value__get()應該返回的值</em></span>
<span style="color:#cc66cc">123 </span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-期望調用adc_handler__voltage__get_by_counts_value(),</em></span>
<span style="color:#808080"><em>//忽略參數。模擬版本僅返回</em></span>
456。adc_handler__voltage__get_by_counts_value_IgnoreAndReturn <span style="color:#66cc66">(</span><span style="color:#cc66cc">456 </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-實際調用要測試的函數,該函數應該執行</em></span>
<span style="color:#808080"><em>//所有未決的預期調用</em></span>
T_ADCHandler_Voltage voltage <span style="color:#66cc66">=</span> appl_adc__voltage__get <span style="color:#66cc66">(</span>
APPL_ADC_CH__I_SETT
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-檢查返回的電壓(上面的模擬應該爲456)</em></span>
TEST_ASSERT_EQUAL_INT <span style="color:#66cc66">(</span><span style="color:#cc66cc">456 </span><span style="color:#66cc66">,</span> voltage <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
運行測試:
<span style="color:#333333"><span style="color:#333333">-------------------
失敗的測試摘要
-------------------
[test_appl_adc.c]
測試:test_voltage_get
在第(57)行:“函數'adc_handler__ctor'的調用次數超出預期。”</span></span>
哦,是的,我們忘記了現在我們不僅必須模擬adc_handler__voltage__get_by_counts_value()
,而且還必須模擬從調用的構造函數setUp()
。我們也將忽略其參數,因此,修改後的setUp()
外觀如下:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> setUp <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-將爲每個通道調用ADC處理程序構造函數。</em></span>
<span style="color:#808080"><em>//模擬的構造函數都返回ADC_HANDLER_RES__OK。</em></span>
<span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">; </span>
<span style="color:#b1b100">爲</span> <span style="color:#66cc66">(</span>通道<span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span>通道<span style="color:#66cc66"><</span> APPL_ADC_CH_CNT <span style="color:#66cc66">;</span>通道<span style="color:#66cc66">++ </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
adc_handler__ctor_IgnoreAndReturn <span style="color:#66cc66">(</span> ADC_HANDLER_RES__OK <span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
<span style="color:#808080"><em>//-在每次測試之前,請重新初始化appl_adc模塊</em></span>
appl_adc__init <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#66cc66">}</span></span></span>
現在測試通過了。
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.08
。
檢查提供給ADC處理程序的參數
給出的參數adc_handler__voltage__get_by_counts_value()
是:
-
指向實例的指針
T_ADCHandler
; -
計算值以轉換爲伏特。
因此,如果我們要檢查參數,則需要訪問T_ADCHandler
每個專用通道,這是私有的appl_adc.c
。爲此,我們必須創建伴隨測試的“保護”函數,appl_adc
該函數將返回適當的指針:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ **
*僅用於測試!
* /</em></span>
T_ADCHandler <span style="color:#66cc66">*</span> _appl_adc__adc_handler__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#b1b100">return </span> <span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel <span style="color:#66cc66">] </span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
我們不會在頭文件中包含此函數原型appl_adc.h
;相反,我們會將其作爲外部函數原型包含到test_appl_adc.c
:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
*外部功能原型
****************************************************** ******************************** /</em></span>
<span style="color:#000000"><strong>extern</strong></span> T_ADCHandler <span style="color:#66cc66">*</span> _appl_adc__adc_handler__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
現在,adc_handler__voltage__get_by_counts_value()
我們可以使用…_ExpectAndReturn()
模擬功能來代替忽略給定的參數:
<span style="color:#333333"><span style="color:#333333"> <span style="color:#808080"><em>//-期望調用adc_handler__voltage__get_by_counts_value(),</em></span>
<span style="color:#808080"><em>//忽略參數。嘲笑版本只是返回456</em></span>
adc_handler__voltage__get_by_counts_value_ExpectAndReturn <span style="color:#66cc66">(</span>
<span style="color:#808080"><em>// -指向適當的ADC處理程序實例</em></span>
_appl_adc__adc_handler__get <span style="color:#66cc66">(</span> APPL_ADC_CH__I_SETT <span style="color:#66cc66">)</span><span style="color:#66cc66">,</span>
<span style="color:#808080"><em>// -從bsp_adc__value__get返回的值()</em></span>
<span style="color:#cc66cc">123 </span><span style="color:#66cc66">,</span>
<span style="color:#808080"><em>// -以伏特返回值</em></span>
<span style="color:#cc66cc">456 </span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
請注意,從嘲笑bsp_adc__value__get()
(即123
)返回的值與賦予的值匹配adc_handler__voltage__get_by_counts_value()
。並且測試通過。如果值不匹配,則測試將失敗。
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.09
。
將模擬與回調一起使用
除了易於使用的幫助程序外,CMock還爲我們提供了非常靈活的回調幫助程序。回調應具有與模擬函數相同的簽名,但它需要一個附加參數:num_calls
。第一次調用該函數時,該函數num_calls
爲0,並且在以後的每次調用中都會增加1。
在回調中,我們可以檢查所需內容,如果出現問題,可以調用Unity宏TEST_FAIL_MESSAGE()
。
讓我們實現這樣的回調:
<span style="color:#333333"><span style="color:#333333"><span style="color:#808080"><em>/ ***************************************************** ******************************
*私人功能
****************************************************** ******************************** /</em></span>
<span style="color:#993333">靜態</span> T_ADCHandler_Voltage _get_by_counts_value_Callback <span style="color:#66cc66">(</span>
T_ADCHandler <span style="color:#66cc66">*</span> me <span style="color:#66cc66">,</span>
T_ADCHandler_CountsValue counts_value <span style="color:#66cc66">,</span>
<span style="color:#993333">int</span> num_calls
<span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span>
T_ADCHandler_Voltage ret <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span>
<span style="color:#b1b100">切換</span> <span style="color:#66cc66">(</span> num_calls <span style="color:#66cc66">)</span><span style="color:#66cc66">{ </span>
<span style="color:#b1b100">情況</span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">:</span>
<span style="color:#b1b100">if </span> <span style="color:#66cc66">(</span> counts_value <span style="color:#66cc66">!= </span> <span style="color:#cc66cc">123 </span><span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
<span style="color:#808080"><em>//-我們可以在這裏檢查任何內容。例如,我們可以</em></span>
<span style="color:#808080"><em>//檢查“ me”所指向的數據,但是請注意,當前</em></span>
<span style="color:#808080"><em>//只是零,因爲我們也模擬了adc_handler__ctor()</em></span>
<span style="color:#808080"><em>//,所以沒有調用原始構造函數,並且</em></span>
<span style="color:#808080"><em>//實例保持統一。</em></span>
TEST_FAIL_MESSAGE <span style="color:#66cc66">(</span>
<span style="color:#ff0000">“ adc_handler__voltage__get_by_counts_value()被稱爲” </span>
<span style="color:#ff0000">counts_value錯誤“ </span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
ret <span style="color:#66cc66">= </span> <span style="color:#cc66cc">456 </span><span style="color:#66cc66">; </span>
<span style="color:#000000"><strong>休息</strong></span><span style="color:#66cc66">;</span>
<span style="color:#b1b100">默認值</span><span style="color:#66cc66">:</span>
TEST_FAIL_MESSAGE <span style="color:#66cc66">(</span>
<span style="color:#ff0000">“ adc_handler__voltage__get_by_counts_value()被稱爲“ </span>
<span style="color:#ff0000">“太多次” </span>
<span style="color:#66cc66">))</span><span style="color:#66cc66">;</span>
<span style="color:#000000"><strong>休息</strong></span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
<span style="color:#b1b100">返回</span> ret <span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
在我們的中test_voltage_get()
,我們按以下方式使用它:
<span style="color:#333333"><span style="color:#333333"> <span style="color:#808080"><em>//-預期調用adc_handler__voltage__get_by_counts_value()</em></span>
adc_handler__voltage__get_by_counts_value_StubWithCallback <span style="color:#66cc66">(</span>
_get_by_counts_value_Callback
<span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
儘管這樣的回調看起來不太優雅,並且對於這個特定示例來說,這是不必要的過大殺傷力,但是它們卻非常靈活。因此,請將其保留在工具箱中,並在適當時使用。
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.10
。
處理特定於編譯器的東西
編譯器通常具有一些有用的非標準內置內容。例如,XC8 Microchip編譯器具有函數__builtin_software_breakpoint()
,顧名思義,該函數放置軟件斷點。如果MCU附帶調試器在其中運行,則調試器將停止執行。如果包含"xc.h"
標頭,則此功能可用。
我經常將其用於某些永遠不會發生的情況。例如,appl_adc__voltage__get()
永遠不要以錯誤的方式來調用我們channel_num
。讓我們爲此添加檢查:
<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#include“ xc.h”</span>
<span style="color:#808080"><em>// .....</em></span>
T_ADCHandler_Voltage appl_adc__voltage__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">{</span>
T_ADCHandler_Voltage ret <span style="color:#66cc66">= </span> <span style="color:#cc66cc">0 </span><span style="color:#66cc66">;</span>
<span style="color:#b1b100">如果</span> <span style="color:#66cc66">(</span> channel_num <span style="color:#66cc66"><</span> APPL_ADC_CH_CNT <span style="color:#66cc66">)</span><span style="color:#66cc66">{</span>
ret <span style="color:#66cc66">=</span> adc_handler__voltage__get_by_counts_value <span style="color:#66cc66">(</span>
<span style="color:#66cc66">&</span> _adc_handlers <span style="color:#66cc66">[</span> channel_num <span style="color:#66cc66">] </span><span style="color:#66cc66">,</span>
bsp_adc__value__get <span style="color:#66cc66">(</span> channel_num <span style="color:#66cc66">)</span>
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">} </span> <span style="color:#b1b100">else </span> <span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-給出了非法的channel_num:永遠不要在這裏</em></span>
__builtin_software_breakpoint <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span>
<span style="color:#b1b100">返回</span> ret <span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
在任何應用程序中都必須進行這樣的檢查,但是如果嘗試運行測試,最終將出現以下錯誤:
<span style="color:#333333"><span style="color:#333333">編譯appl_adc.c ...
../src/appl/appl_adc.c:15:16:致命錯誤:xc.h:沒有此類文件或目錄
#include“ xc.h”</span></span>
顯然,GCC(默認用於測試)既沒有這種內置函數,也沒有“xc.h”
頭文件。
我們可以通過使用Ceedling“支持”目錄來解決此問題,該目錄默認位於test/support
。讓我們在其中創建“xc.h”
文件,並在其中放置以下內容:
<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#ifndef _MY_XC_DUMMY#定義</span>
<span style="color:#339933">_MY_XC_DUMMY</span>
<span style="color:#993333">void</span> __builtin_software_breakpoint <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#339933">#endif // _MY_XC_DUMMY</span></span></span>
如果現在運行測試,將會有不同的錯誤:
<span style="color:#333333"><span style="color:#333333">連結test_appl_adc.out ...
build / test / out / appl_adc.o:在函數'0097?ppl_adc__voltage__get'中:
/home/dimon/projects/indicator_git/test_ceedling/../src/appl/appl_adc.c:101:未定義對“ 0095”的引用?_builtin_software_breakpoint'
collect2:錯誤:ld返回1退出狀態</span></span>
不錯:至少,“xc.h”
文件清楚地使用了appl_adc.c
我們的文件,現在我們需要提供的實際實現__builtin_software_breakpoint()
。最簡單的方法就是模擬它。因此,將以下行添加到我們的test_appl_adc.c
文件中:
<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#include“ mock_xc.h”</span></span></span>
現在,運行測試,它們就通過了!
<span style="color:#333333"><span style="color:#333333">--------------------
總體測試總結
--------------------
測試:7
通過:7
失敗:0
忽略:0</span></span>
我們可以再編寫一個測試:讓我們檢查__builtin_software_breakpoint()
是否通過了非法的通道號,即被調用:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">void</span> test_voltage_get_wrong_channel_number <span style="color:#66cc66">(</span><span style="color:#993333">void </span><span style="color:#66cc66">)</span>
<span style="color:#66cc66">{ </span>
<span style="color:#808080"><em>//-我們期望__builtin_software_breakpoint()被稱爲...</em></span>
__builtin_software_breakpoint_Expect <span style="color:#66cc66">(</span><span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#808080"><em>//-...當我們使用非法的</em></span>
<span style="color:#808080"><em>通道號</em></span><span style="color:#808080"><em>調用appl_adc__voltage__get()時</em></span><span style="color:#808080"><em>。</em></span>
appl_adc__voltage__get <span style="color:#66cc66">(</span>
APPL_ADC_CH_CNT
<span style="color:#66cc66">)</span><span style="color:#66cc66">; </span>
<span style="color:#66cc66">}</span></span></span>
測試再次通過:
<span style="color:#333333"><span style="color:#333333">--------------------
總體測試總結
--------------------
測試:8
通過:8
失敗:0
忽略:0</span></span>
我們鼓勵您確認如果__builtin_software_breakpoint()
在非法頻道號的情況下我們刪除呼叫,測試將失敗。
注意:您可以使用準備好的存儲庫完成所有操作。在此處輸入:git checkout v0.12
。
有關在主機上進行測試的更多注意事項
在主機上進行測試非常方便:只需按幾次鍵即可運行測試,測試運行很快,我們幾乎可以立即獲得結果。但是,由於架構不同,因此我們應格外小心。
如上所述,不同的編譯器具有一些內置函數。除此之外,內存對齊方式通常是不同的:在8位MCU上,對齊方式爲1字節,但是在您的主機上,通常爲4或8字節(取決於您的體系結構)。因此,我們必須將應用程序多目標化。
我經常發現自己創建了一個文件,例如my_crosscompiler.h
,根據所使用的編譯器在其中聲明瞭一些內容。例如,如果需要某種結構packed
,則必須使用特定於編譯器的屬性。所以它可能看起來像這樣:
<span style="color:#333333"><span style="color:#333333"><span style="color:#339933">#if defined(__ XC8__)</span>
<span style="color:#808080"><em>//-不需要8位MCU上的“打包”屬性</em></span>
<span style="color:#339933">#define __MY_CROSS_ATTR_PACKED </span>
<span style="color:#339933">#elif defined(__ GNUC__)</span>
<span style="color:#339933">#define __MY_CROSS_ATTR_PACKED __attribute __((packed))</span>
<span style="color:#339933">#endif</span></span></span>
在應用程序代碼中,我使用了macro __MY_CROSS_ATTR_PACKED
。
這樣,我們可以編寫在目標MCU和主機上都可以使用的代碼。當然,這會花費額外的精力和時間,但是一般來說測試也是如此。這些天,我花了大量時間編寫測試。它的回報很好。
其他測試思路
編寫測試代碼通常被認爲是一個乏味的過程,我不能完全不同意。但是,我總是鼓勵自己找到一些測試事物的新方法,而不是反覆地對此進行測試。
例如,考慮EEPROM模塊(電可擦可編程只讀存儲器)。我們很可能最終將使用特定於MCU或電路板的模塊bsp_eeprom
,該模塊僅能從指定地址讀取普通數據或從指定地址讀取普通數據。由於它很大程度上取決於硬件,因此我們無法在主機上對其進行測試。
除之外bsp_eeprom
,使用像這樣的與應用程序有關的模塊也很方便appl_eeprom
,該模塊應具有向EEPROM寫入或從EEPROM讀取某些應用程序實體的功能。當然,這些依賴於應用程序的bsp_eeprom__...
函數在下面調用函數。例如,我們可能具有以下功能來讀取/寫入每個特定ADC通道的乘法器:
<span style="color:#333333"><span style="color:#333333"><span style="color:#993333">int16_t</span> appl_eeprom__adc_clb_mul__get <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel channel <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span>
<span style="color:#993333">void</span> appl_eeprom__adc_clb_mul__set <span style="color:#66cc66">(</span><span style="color:#000000"><strong>枚舉</strong></span> E_ApplAdcChannel通道<span style="color:#66cc66">,</span> <span style="color:#993333">int16_t</span> mul <span style="color:#66cc66">)</span><span style="color:#66cc66">;</span></span></span>
如果應用程序很大,則可能會有很多類似的功能。這是非常乏味的所有單獨測試。
相反,我們可能會想到最容易犯的錯誤。對於appl_eeprom
模塊之類的東西,這是複製粘貼錯誤:當我們有很多相似的函數(實際上,它們都調用相同的bsp_eeprom
函數,但是針對不同的地址)時,很容易將一個函數複製並粘貼到另一個函數中,也很容易忘記正確調整粘貼的代碼。
因此,我經常使用這樣的技術:爲bsp_eeprom
函數定義存根回調,即僅檢查給定區域是否“乾淨”,如果是,則使用一些預定義的數據填充該區域(使其“髒”)。如果該區域已經“髒”,則會生成錯誤。
然後,執行“寫入”測試:我appl_eeprom
使用所有允許的參數調用模塊中的每個“寫入”函數,然後,EEPROM的整個工作區域應該是“髒的”,沒有孔。而且,正如我之前說的,每個回調還檢查將要寫入的區域是否乾淨。這樣,我們可以輕鬆消除這些“複製粘貼”問題:如果某個函數寫入錯誤的區域,我們最終將獲得覆蓋的數據和“空洞”,這將被我們的測試捕獲。
並且,當然,應該對“讀取”功能進行完全相同的測試。
這比分別測試每個功能要有趣(快速)得多,最後我們將獲得足夠可靠的測試。
結論
ThrowTheSwitch.org 的開發人員提供的工具使我們幾乎可以輕鬆地測試C代碼。感謝大夥們!
我希望本文能幫助您大致瞭解Ceedling及其配套產品,並鼓勵您仔細閱讀文檔。
將所有組件的文檔集中在一處的最簡單方法是通過執行以下操作創建新的立項項目:
<span style="color:#333333"><span style="color:#333333">$終止新的my_project</span></span>
並導航到my_project/vendor/ceedling/docs
包含幾個pdf文件的目錄。
再說一次,如果您真的想投入大量時間來測試嵌入式設計(這可能是個好主意),請考慮閱讀James W. Grenning 撰寫的《嵌入式C的測試驅動開發》一書,其中介紹了各種測試方法,方法和工具非常徹底。
讓我們編寫不爛的C代碼!