RTOS任務進行單元測試的4種策略

来源:https://www.cnblogs.com/testing-/archive/2023/06/21/17460629.html
-Advertisement-
Play Games

產品設計出來之後啊,大家使用的時候覺得反過來使用更加便捷。但是屏幕顯示是反的。那怎麼辦那????? 修改硬體費時費工,那能否軟體實現那????? 如果純軟體使用那就太費系統資源了。於是就想到了使用全志R528 自帶的G2D功能(硬體加速功能)。 使用它進行旋轉,後又發現uboot階段系統沒有G2D導 ...


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

超過50%的嵌入式軟體項目使用實時操作系統(RTOS)。不幸的是,使用RTOS會給使用現代開發技術(如測試驅動開發(TDD)、DevOps或自動測試)的開發者帶來一些問題。例如,當開發者試圖為他們的任務編寫測試時,他們遇到的第一個問題是任務函數包含一個無限迴圈!任何直接調用任務函數的測試都會被認為是一個無限迴圈!因此,任何直接調用任務函數的測試將永遠不會完成。這篇文章將探討對RTOS任務進行單元測試的幾種策略,其中包括:

  • 迴圈的重新定義
  • 完成信號
  • 任務排除
  • 通過OSAL使用主機線程(強烈建議)。

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

任務的剖析

在RTOS任務中,有幾個部分用於管理任務行為。首先,初始化部分聲明變數,實例化對象,初始化驅動程式,並負責將傳遞給任務的任何數據轉換成正確的類型。接下來,有一個無限迴圈,執行所有任務的行為。例如,如果我們寫了管理感測器的任務,我們希望任務的無限迴圈能定期檢索和處理感測器的數據。最後,任務完成部分規定了任務完成後的情況。

典型的任務使用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
    }

我發現,編寫周期性任務的開發人員希望他們的任務能夠無限期地運行,而沒有考慮如果任務運行到完成會發生什麼。

看看這個任務,你可以看到,如果一個開發者想在測試中對Task_Sensors進行調用,他們會遇到幾個問題。因此,讓我們來看看這些問題,並按照我經常看到的開發人員在達成最直接和最好的解決方案之前所嘗試的各種策略。

參考資料

迴圈的重新定義

經常看到工程師部署的第一個戰術是迴圈重定義。迴圈的重新定義是指根據代碼是生產代碼還是測試代碼,對任務中的無限迴圈進行操作。例如,Task_Sensor的代碼將被改寫為如下內容:


    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
    }

這個想法是,開發人員可以創建有條件編譯的代碼,以控制迴圈是永遠運行還是一次。這段代碼通常看起來像下麵這樣:

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

除了編譯後的代碼外,構建時還必須定義或不定義PRODUCTION巨集。

一般來說,這不是使RTOS任務可測試的好方法,有幾個原因。首先,我們正在測試的代碼正在改變。我們的任務在測試過程中的行為會與運行時的行為不同。第二,我們正在添加有條件編譯的代碼,這通常不是很乾凈。第三,在定義巨集的過程中,有可能出現人為錯誤。最後,雖然迴圈的重新定義對於Task_Sensor來說似乎很有吸引力,但測試任務的相互作用會變得過於複雜。事實上,如果我們需要迴圈運行三到四次會發生什麼?我們現在需要為LOOP_STATE定義獨特的定義。

完成信號

完成信號是對迴圈重定義思想的修改,允許任務無限期地運行,直到收到任務應該停止執行的信號。在這種情況下,任務代碼被修改為刪除巨集定義,轉而使用迴圈變數,如下圖所示:

    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
    }

正如你所看到的,在任務結束時,我們檢查該任務是否仍在運行。這就解決了運行多個迴圈的問題,並通過刪除巨集來清理代碼。然而,我們現在已經為任務的運行引入了複雜性,併為記憶體損壞或單一事件干擾(SEU)打開了機會,以改變isRunning的狀態並完成我們的任務。

任務在生產中運行到完成似乎不是什麼大問題,但不是所有的實時操作系統都能優雅地處理這個問題。例如,如果你允許FreeRTOS的任務運行到完成,內核就會窒息並停止執行所有的代碼!

任務排除法

當我們不測試我們的任務時,任務排除就發生了!我們不需要測試任務!而不是試圖從測試線束中調用任務,我們創建測試來運行任務本身的代碼。 任務排除要求我們重寫我們的函數,使其看起來像下麵這樣:

    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);
    }

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

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

任務排除的問題是,在我們在目標上運行應用程式之前,我們永遠不會測試任務代碼。不幸的是,我們也欺騙自己,認為我們的測試覆蓋了所有的代碼。

優點是我們可以根據需要從測試中調用任務中的函數。我們已經實現了這一點,並避免了需要處理無限的while迴圈的問題。代碼更加簡潔,我們也沒有創建一堆條件性編譯語句。

雖然這種技術可以使測試任務代碼更容易,但從技術上講,它不是在測試任務代碼。相反,它是一種變通方法。為了測試你的任務代碼,你應該嘗試在你的主機上創建一個線程或任務,併在那裡運行這些代碼。

通過OSAL使用主機線程(強烈建議)。

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

我們測試困境的根本原因是,開發人員沒有利用操作系統抽象層(OSAL)。相反,他們直接進入RTOS的API並使用這些API。雖然調用RTOS APIs很快,而且似乎是一個很好的方法,但它是將RTOS與應用程式代碼緊密地耦合在一起。這種耦合使得測試正確使用任務或線程的應用程式代碼變得非常困難。

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

    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是一個抽象,我們的應用程式代碼使用它來發送隊列中的數據。應用程式不應該關心我們是否在使用ThreadX、FreeRTOS、pthread或任何其他RTOS或任務調度器。根據我們想要編譯代碼的系統,會提供一個實現文件。這樣做有很多好處,比如說:

  • 應用程式不與實時操作系統相聯繫
  • 測試可以使用主機的線程框架
  • 應用程式是可移植和可重覆使用的
  • 避免了臨時性的和黑客式的測試方法進行測試。

對於許多對使用DevOps和自動測試線束感興趣的開發者來說,你可能至少有針對你所選擇的RTOS和pthread的實現,pthread是Linux的POSIX線程庫。不幸的是,我們如何使用pthread以及設計和構建OSAL已經超出了今天的範圍,但我們將在未來探討這些話題。

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

小結

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

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

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

釘釘或微信號: pythontesting 微信公眾號:pythontesting
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 1、安裝ES和Kibana ### kibana和ES的關係 ![image](https://img2023.cnblogs.com/blog/3216427/202306/3216427-20230620110520834-246432673.png) ### ES安裝 > 可以自己使用d ...
  • 二進位補碼(Binary Two's Complement)是一種表示有符號整數的方法,在電腦中廣泛使用。它是通過對正數取反加一得到負數的表示方式。 在二進位補碼表示中,一個固定位數的整數由固定數量的二進位位表示,其中最高位被用作符號位。對於N位的二進位補碼表示,最高位(最左側的位)為符號位,0表 ...
  • 哈嘍大家好,我是鹹魚 今天跟大家介紹一下 python 當中星號(`*`)的一些用法 首先大家最常見的就是在 python 中 `*` 是乘法運算符,實現乘法 ```python sum = 5 * 5 # 25 ``` 除此之外,還有一種常見的用法就是 `*` 號操作符在函數中的用法 - 單星號( ...
  • [TOC](【後端面經-Java】Java創建線程的方法簡介) ## 1. 線程的基本概念 ### 1.1 線程 學過操作系統的同學應該不陌生,線程是電腦中的最小調度單元,一個進程可以有多個線程,執行併發操作,提高任務的運行效率 ### 1.2 線程狀態和生命周期 1. 線程狀態包括: - **新 ...
  • # Python的多線程和多進程 ## 一、簡介 併發是今天電腦編程中的一項重要能力,尤其是在面對需要大量計算或I/O操作的任務時。Python 提供了多種併發的處理方式,本篇文章將深入探討其中的兩種:多線程與多進程,解析其使用場景、優點、缺點,並結合代碼例子深入解讀。 ## 二、多線程 Pyth ...
  • 類型的別名是C#12的一種比較“實用”的“新功能”。它可以讓你在開發過程中使用 using 別名指令創建任何類型的別名,也可以為元組類型、數組類型、指針類型或其他不安全類型創建語義別名,這樣可以通過類型知道當前參數的含義,降低錯誤率。之前的C#版本也支持類型別名,但是使用沒有這麼優雅。C#12的使用 ...
  • 函數 NCalc 本身已經實現的函數列表如下: 函數名描述用例用例結果 Abs 返回絕對值 Abs(-1) 1M Acos 返回餘弦值對應的角度 Acos(1) 0d Asin - - d Atan - - d Ceiling 向上取整 Ceiling(1.5) 2d Cos - - d Exp 相 ...
  • 在日常工作中,我們常常需要將SVG轉換為PDF格式。這是因為SVG格式的圖像在列印時可能會出現問題,例如失去解析度或無法正確適應紙張大小。與此相比,PDF格式則專門用於列印和共用文檔,可以確保高質量輸出,並且能夠自動適應不同的紙張大小。在本文中,我們將介紹如何使用編程方式將SVG文件轉換為PDF,並... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...