首頁 智能 > 正文

RTOS任務(wù)進(jìn)行單元測試的4種策略

https://www.beningo.com/4-tactics-to-unit-test-rtos-tasks/

超過50%的嵌入式軟件項目使用實時操作系統(tǒng)(RTOS)。不幸的是,使用RTOS會給使用現(xiàn)代開發(fā)技術(shù)(如測試驅(qū)動開發(fā)(TDD)、DevOps或自動測試)的開發(fā)者帶來一些問題。例如,當(dāng)開發(fā)者試圖為他們的任務(wù)編寫測試時,他們遇到的第一個問題是任務(wù)函數(shù)包含一個無限循環(huán)!任何直接調(diào)用任務(wù)函數(shù)的測試都會被認(rèn)為是一個無限循環(huán)!因此,任何直接調(diào)用任務(wù)函數(shù)的測試將永遠(yuǎn)不會完成。這篇文章將探討對RTOS任務(wù)進(jìn)行單元測試的幾種策略,其中包括:

  • 循環(huán)的重新定義
  • 完成信號
  • 任務(wù)排除
  • 通過OSAL使用主機(jī)線程(強(qiáng)烈建議)。

注意:在這篇文章中,我們將把任務(wù)和線程作為同義詞。我們還將使用ThreadX作為RTOS的例子。


(相關(guān)資料圖)

任務(wù)的剖析

在RTOS任務(wù)中,有幾個部分用于管理任務(wù)行為。首先,初始化部分聲明變量,實例化對象,初始化驅(qū)動程序,并負(fù)責(zé)將傳遞給任務(wù)的任何數(shù)據(jù)轉(zhuǎn)換成正確的類型。接下來,有一個無限循環(huán),執(zhí)行所有任務(wù)的行為。例如,如果我們寫了管理傳感器的任務(wù),我們希望任務(wù)的無限循環(huán)能定期檢索和處理傳感器的數(shù)據(jù)。最后,任務(wù)完成部分規(guī)定了任務(wù)完成后的情況。

典型的任務(wù)使用ThreadX可能看起來像下面的代碼片段:

{    void Task_Sensors(ULONG ThreadInput)    {        // SECTION 1: Initialization        (void) ThreadInput;             SensorData_t SensorRawData;        SensorData_t SensorData;        SensorData_t pSensorDataTx = &SensorData;             Sensor_Init();             // SECTION 2: Tasks main function / behavior / purpose        while(true)        {            SensorRawData = Sensor_Sample();            SensorData    = SensorProcess(SensorRawData);                 (void)tx_queue_send(SensorTxQ, (void *)&pSensorDataTx, TX_WAIT_FOREVER);                 tx_thread_sleep(TASK_SENSORS_PERIOD_MS);                }             // SECTION 3: TasK Completion Activities    }

我發(fā)現(xiàn),編寫周期性任務(wù)的開發(fā)人員希望他們的任務(wù)能夠無限期地運(yùn)行,而沒有考慮如果任務(wù)運(yùn)行到完成會發(fā)生什么。

看看這個任務(wù),你可以看到,如果一個開發(fā)者想在測試中對Task_Sensors進(jìn)行調(diào)用,他們會遇到幾個問題。因此,讓我們來看看這些問題,并按照我經(jīng)??吹降拈_發(fā)人員在達(dá)成最直接和最好的解決方案之前所嘗試的各種策略。

參考資料

  • 軟件測試精品書籍文檔下載持續(xù)更新 https://github.com/china-testing/python-testing-examples 請點贊,謝謝!
  • 本文涉及的python測試開發(fā)庫 謝謝點贊! https://github.com/china-testing/python_cn_resouce
  • python精品書籍下載 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md

循環(huán)的重新定義

經(jīng)??吹焦こ處煵渴鸬牡谝粋€戰(zhàn)術(shù)是循環(huán)重定義。循環(huán)的重新定義是指根據(jù)代碼是生產(chǎn)代碼還是測試代碼,對任務(wù)中的無限循環(huán)進(jìn)行操作。例如,Task_Sensor的代碼將被改寫為如下內(nèi)容:

void Task_Sensors(ULONG ThreadInput)    {        // SECTION 1: Initialization        (void) ThreadInput;             SensorData_t SensorRawData;        SensorData_t SensorData;        SensorData_t pSensorDataTx = &SensorData;             Sensor_Init();             // SECTION 2: Tasks main function / behavior / purpose        while(LOOP_STATE)        {            SensorRawData = Sensor_Sample();            SensorData    = SensorProcess(SensorRawData);                 (void)tx_queue_send(SensorTxQ, (void *)&pSensorDataTx, TX_WAIT_FOREVER);                 tx_thread_sleep(TASK_SENSORS_PERIOD_MS);                }             // SECTION 3: TasK Completion Activities    }

這個想法是,開發(fā)人員可以創(chuàng)建有條件編譯的代碼,以控制循環(huán)是永遠(yuǎn)運(yùn)行還是一次。這段代碼通??雌饋硐裣旅孢@樣:

#ifdef PRODUCTION        #define LOOP_STATE true    #else        #define LOOP_STATE false    #endif

除了編譯后的代碼外,構(gòu)建時還必須定義或不定義PRODUCTION宏。

一般來說,這不是使RTOS任務(wù)可測試的好方法,有幾個原因。首先,我們正在測試的代碼正在改變。我們的任務(wù)在測試過程中的行為會與運(yùn)行時的行為不同。第二,我們正在添加有條件編譯的代碼,這通常不是很干凈。第三,在定義宏的過程中,有可能出現(xiàn)人為錯誤。最后,雖然循環(huán)的重新定義對于Task_Sensor來說似乎很有吸引力,但測試任務(wù)的相互作用會變得過于復(fù)雜。事實上,如果我們需要循環(huán)運(yùn)行三到四次會發(fā)生什么?我們現(xiàn)在需要為LOOP_STATE定義獨特的定義。

完成信號

完成信號是對循環(huán)重定義思想的修改,允許任務(wù)無限期地運(yùn)行,直到收到任務(wù)應(yīng)該停止執(zhí)行的信號。在這種情況下,任務(wù)代碼被修改為刪除宏定義,轉(zhuǎn)而使用循環(huán)變量,如下圖所示:

void Task_Sensors(ULONG ThreadInput)    {        // SECTION 1: Initialization        (void) ThreadInput;        bool   isRunning = true;             SensorData_t SensorRawData;        SensorData_t SensorData;        SensorData_t pSensorDataTx = &SensorData;             Sensor_Init();             // SECTION 2: Tasks main function / behavior / purpose        while(isRunning)        {            SensorRawData = Sensor_Sample();            SensorData    = SensorProcess(SensorRawData);                 (void)tx_queue_send(SensorTxQ, (void *)&pSensorDataTx, TX_WAIT_FOREVER);                 tx_thread_sleep(TASK_SENSORS_PERIOD_MS);                         isRunning = Task_GetDesiredRunState(TASK_SENSOR);        }             // SECTION 3: TasK Completion Activities    }

正如你所看到的,在任務(wù)結(jié)束時,我們檢查該任務(wù)是否仍在運(yùn)行。這就解決了運(yùn)行多個循環(huán)的問題,并通過刪除宏來清理代碼。然而,我們現(xiàn)在已經(jīng)為任務(wù)的運(yùn)行引入了復(fù)雜性,并為內(nèi)存損壞或單一事件干擾(SEU)打開了機(jī)會,以改變isRunning的狀態(tài)并完成我們的任務(wù)。

任務(wù)在生產(chǎn)中運(yùn)行到完成似乎不是什么大問題,但不是所有的實時操作系統(tǒng)都能優(yōu)雅地處理這個問題。例如,如果你允許FreeRTOS的任務(wù)運(yùn)行到完成,內(nèi)核就會窒息并停止執(zhí)行所有的代碼!

任務(wù)排除法

當(dāng)我們不測試我們的任務(wù)時,任務(wù)排除就發(fā)生了!我們不需要測試任務(wù)!而不是試圖從測試線束中調(diào)用任務(wù),我們創(chuàng)建測試來運(yùn)行任務(wù)本身的代碼。 任務(wù)排除要求我們重寫我們的函數(shù),使其看起來像下面這樣:

void Task_Sensors(ULONG ThreadInput)    {        // SECTION 1: Initialization        (void) ThreadInput;             Task_SensorInit();             // SECTION 2: Tasks main function / behavior / purpose        while(true)        {            Task_SensorRun();                 tx_thread_sleep(TASK_SENSORS_PERIOD_MS);                }             // SECTION 3: TasK Completion Activities    }              /**********************************     * Placed in a different module     **********************************/         void Task_SensorInit(void)    {        SensorData_t SensorRawData;        SensorData_t SensorData;        SensorData_t pSensorDataTx = &SensorData;             Sensor_Init();    }         void Task_SensorRun(void)    {        SensorRawData = Sensor_Sample();        SensorData    = SensorProcess(SensorRawData);             (void)tx_queue_send(SensorTxQ, (void *)&pSensorDataTx, TX_WAIT_FOREVER);    }

老實說,上面的代碼就是任務(wù)應(yīng)該有的樣子。這段代碼非常干凈,也很容易理解。但問題是,我們是通過嘗試使用一種不太理想的技術(shù)來達(dá)到這個目的的。

我們現(xiàn)在在Task_Sensors中看到的任務(wù)代碼非常簡單,任務(wù)排除會說我們不需要測試它。因此,相反,我們將在我們的測試線束中測試Task_SensorInit和Task_SensorRun函數(shù)。畢竟,這些函數(shù)被保存在一個單獨的模塊中,所以我們可以將任務(wù)代碼從測試線束中排除,實現(xiàn)我們所期望的100%的代碼覆蓋,對嗎?

任務(wù)排除的問題是,在我們在目標(biāo)上運(yùn)行應(yīng)用程序之前,我們永遠(yuǎn)不會測試任務(wù)代碼。不幸的是,我們也欺騙自己,認(rèn)為我們的測試覆蓋了所有的代碼。

優(yōu)點是我們可以根據(jù)需要從測試中調(diào)用任務(wù)中的函數(shù)。我們已經(jīng)實現(xiàn)了這一點,并避免了需要處理無限的while循環(huán)的問題。代碼更加簡潔,我們也沒有創(chuàng)建一堆條件性編譯語句。

雖然這種技術(shù)可以使測試任務(wù)代碼更容易,但從技術(shù)上講,它不是在測試任務(wù)代碼。相反,它是一種變通方法。為了測試你的任務(wù)代碼,你應(yīng)該嘗試在你的主機(jī)上創(chuàng)建一個線程或任務(wù),并在那里運(yùn)行這些代碼。

通過OSAL使用主機(jī)線程(強(qiáng)烈建議)。

測試RTOS任務(wù)的真正問題與大多數(shù)任務(wù)有while語句的事實無關(guān)。相反,這個問題來自于開發(fā)者對測試的思考和方法。到目前為止,我們所研究的所有策略都假定我們想直接從我們的測試線束中調(diào)用Task_Sensors。這就是問題所在。在我們的測試線束中,我們想創(chuàng)建一個運(yùn)行的Task_Sensors任務(wù)或線程,而不是進(jìn)行函數(shù)調(diào)用!這就是問題所在!

我們測試?yán)Ь车母驹蚴牵_發(fā)人員沒有利用操作系統(tǒng)抽象層(OSAL)。相反,他們直接進(jìn)入RTOS的API并使用這些API。雖然調(diào)用RTOS APIs很快,而且似乎是一個很好的方法,但它是將RTOS與應(yīng)用程序代碼緊密地耦合在一起。這種耦合使得測試正確使用任務(wù)或線程的應(yīng)用程序代碼變得非常困難。

最佳的方法是將RTOS的細(xì)節(jié)隱藏在操作系統(tǒng)抽象層(OSAL)的后面。例如,如果你檢查我們到目前為止所看到的各種版本的Task_Sensors,你會發(fā)現(xiàn)我們正在使用ThreadX tx_queue_send API來發(fā)送消息到隊列。因此,我們應(yīng)該把這些細(xì)節(jié)放在OSAL后面,這樣我們的任務(wù)就會像下面這樣:

void Task_Sensors(ULONG ThreadInput)    {        // SECTION 1: Initialization        (void) ThreadInput;        bool   isRunning = true;             SensorData_t SensorRawData;        SensorData_t SensorData;        SensorData_t pSensorDataTx = &SensorData;             Sensor_Init();             // SECTION 2: Tasks main function / behavior / purpose        while(true)        {            SensorRawData = Sensor_Sample();            SensorData    = SensorProcess(SensorRawData);                 (void)OSAL_Q_Send(SensorTxQ, (void *)&pSensorDataTx, OS_WAIT_FOREVER);                 tx_thread_sleep(TASK_SENSORS_PERIOD_MS);                }             // SECTION 3: TasK Completion Activities    }

OSAL_Q_Send是一個抽象,我們的應(yīng)用程序代碼使用它來發(fā)送隊列中的數(shù)據(jù)。應(yīng)用程序不應(yīng)該關(guān)心我們是否在使用ThreadX、FreeRTOS、pthread或任何其他RTOS或任務(wù)調(diào)度器。根據(jù)我們想要編譯代碼的系統(tǒng),會提供一個實現(xiàn)文件。這樣做有很多好處,比如說:

  • 應(yīng)用程序不與實時操作系統(tǒng)相聯(lián)系
  • 測試可以使用主機(jī)的線程框架
  • 應(yīng)用程序是可移植和可重復(fù)使用的
  • 避免了臨時性的和黑客式的測試方法進(jìn)行測試。

對于許多對使用DevOps和自動測試線束感興趣的開發(fā)者來說,你可能至少有針對你所選擇的RTOS和pthread的實現(xiàn),pthread是Linux的POSIX線程庫。不幸的是,我們?nèi)绾问褂胮thread以及設(shè)計和構(gòu)建OSAL已經(jīng)超出了今天的范圍,但我們將在未來探討這些話題。

現(xiàn)在,如果你有興趣看一些OSAL的例子,我推薦你看一下CMSIS-RTOS-V2和NASA的Aeronautics GSC-18730-1。有可能,對于你的需求來說,這些都是過剩的,但它們是完全實現(xiàn)OSAL的好例子。我建議探索它們,并慢慢設(shè)計和建立你的OSAL,你可以在你所有的應(yīng)用程序中使用。

小結(jié)

有幾種策略可以讓開發(fā)者用來對任務(wù)或線程進(jìn)行單元測試。正如我們在今天的文章中所看到的,大多數(shù)開發(fā)人員可以部署的戰(zhàn)術(shù)都是針對未能將其任務(wù)架構(gòu)為使用OSAL的變通方法。一旦有了OSAL,任務(wù)代碼就可以通過提供抽象層背后的必要功能,使用任何RTOS或本地線程庫進(jìn)行測試。OSAL層有助于:

  • 簡化測試策略
  • 保持代碼的清潔
  • 提高靈活性、可移植性和重用性

如果你想測試你所有的代碼,那么通過OSAL來利用pthread是最好的方法。

關(guān)鍵詞:

最近更新

關(guān)于本站 管理團(tuán)隊 版權(quán)申明 網(wǎng)站地圖 聯(lián)系合作 招聘信息

Copyright © 2005-2023 創(chuàng)投網(wǎng) - mallikadua.com All rights reserved
聯(lián)系我們:39 60 29 14 2@qq.com
皖I(lǐng)CP備2022009963號-3