iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
AI & Data

Rust 加 MLOps,你說有沒有搞頭?系列 第 7

[Day 07] - Rust x 單元測試 x MLOps (上)

  • 分享至 

  • xImage
  •  

今日份 Ferris

在這部分最一開始提到很多我們在 Python 中熟悉的概念都能轉換到 Rust 中,前幾天建立的 GitHub Template 便是其中一個例子。
而其中雖然有使用到測試的部分,但都沒有詳細說明,今天我們就要來說說怎麼寫啦!
程式碼測試有許多好處,其中一個好處就是幫助我們發現程式碼中的錯誤、缺陷和漏洞,提高軟體的可靠性。
簡單來說,就是幫忙抓蟲,所以 Ferris 也來幫忙抓王蟲啦!!!
https://ithelp.ithome.com.tw/upload/images/20230922/20141304dwRkCfXVQQ.jpg

🚨 測試是很複雜的技能,我不認為有辦法在短時間就涵蓋如何寫出好測試的細節,但我們還是會討論 Rust 測試的功能機制,以及在 MLOps 情境中可以做的一些測試。

重構初始資料夾結構

在談如何撰寫測試之前,我們首先從重構 cargo new 生成的初始資料夾結構說起。
在專案一開始被生成的時後,主要程式碼都會放在 src/main.rs 中的 main 函式中。
但隨著程式增長,會發現 main 函式負責太多事情了,而一般來說只有在每個函式都只負責一件事時,函式才能簡潔且易於維護。
事實上,因為將多個任務的責任分配給 main 函式的組織問題在許多執行檔專案中都很常見。
所以 Rust 社群已經制定了在 main 函式開始變大時,如何拆分任務的指導原則:

  • 將程式分成 main.rslib.rs 並將程式邏輯放到 lib.rs
  • 當邏輯很小時,可以留在 main.rs
  • 一但邏輯變得複雜時,就將其從 main.rs 移至 lib.rs

這個模式可以將任務拆分為:

  • main.rs 處理程式的執行。
  • lib.rs 處理眼前的所有任務邏輯。

由於我們無法直接測試 main,這個架構讓我們可以測試所有移至 lib.rs 的程式函式邏輯。
而留在 main.rs 的程式碼會非常小,所以直接用看的就能驗證。
而我們還能進一步將測試的程式碼放在另一個資料夾中,範例資料夾結構如下:

.
├── Cargo.toml
├── src
│   ├── lib.rs
│   └── main.rs
└── tests
    └── test_lib.rs

事實上,在開發程式時盡早重構是很好的做法,因為重構少量的程式碼會比較簡單。
例如這裡只是建立幾個檔案而已,但我們已經為未來維護程式碼打下良好的基礎。

Rust 測試基本寫法

在 Rust 中,測試區塊的基礎模板如下:

#[cfg(debug)]
mod tests {
    #[test]
    fn test_1 {
        assert_eq!(...)
    }
}

其中借助了模組化 (mod) 並引入 #[cfg(debug)]#[test] 兩個巨集。
而測試的本身則借助了 assert_eq 巨集 (若程式中斷則判斷為測試失敗)。
除了 assert_eq 以外,還有 assertassert_ne 可以使用。
以下為實際範例 (為了方便說明,這裡先把測試和函式放在同一個檔案中):

fn all_caps(word: &str) -> String {
    word.to_uppercase()
}

fn main() {}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn check_all_caps() {
        let result = all_caps("hello");
        let expected = String::from("HELLO");

        assert_eq!(result, expected, "string should be all uppercase")
    }
}

而要執行測試則可以使用前幾天就看過的 cargo test 指令。
可以發現,整體流程跟在 Python 中使用 pytest 幾乎一模一樣,不需要重新學一個技能!

💡 一個 crate 是 Rust 編譯器同個時間內視為程式碼的最小單位。
當我們傳入單一源碼檔案至 cargo (或 rustc) 時,編譯器會將該檔案視為一個 crate。
因此 use crate::*; 中的 crate 指的是檔案內所有程式碼,而這麼寫代表在此模組內引入所有模組外的東西。

測試驅動開發

測測試驅動開發(Test-Driven Development,TDD)是一種軟體開發方法,它的核心理念是在撰寫實際的程式碼之前先撰寫測試用例。

TDD 的過程通常分為以下三個主要步驟:

  1. 撰寫測試 — 開發者首先思考要解決的問題,然後為這個問題撰寫一個測試用例。
    這個測試用例描述了預期的功能行為或特定情況下的期望結果。
    通常代表寫出一個會失敗的測試並執行它,以確保它失敗的原因如你所預期。
  2. 實作程式碼 — 接下來,開發者開始撰寫程式碼,以滿足剛剛撰寫的測試用例。
    這個過程通常只需要寫出或修改足夠的程式碼來讓新測試可以通過,不需要添加過多的功能。
  3. 執行測試 — 一旦程式碼撰寫完成,開發者運行所有的測試用例。
    如果所有的測試通過,那麼這表示程式碼在當前需求下是正確的。
    如果有測試未通過,開發者則需要返回到第 2 步,修改程式碼以解決問題,然後再次運行測試,直到所有測試都通過為止。

更詳盡的流程如下圖:
https://ithelp.ithome.com.tw/upload/images/20230922/20141304mUGFHyIxVz.jpg

TDD 的主要優點包括:

  • 減少錯誤 — TDD 可以幫助開發者在撰寫程式碼之前捕捉和修復錯誤,從而減少後續的錯誤和缺陷。
  • 提高程式碼品質 — 由於測試用例是根據功能需求編寫的,因此TDD有助於確保程式碼滿足了這些需求,提高了程式碼的品質。
  • 更好的設計 — TDD 鼓勵模組化、解耦和單一職責原則,這有助於生成更容易理解和維護的程式碼。
  • 快速反饋 — 由於測試是自動運行的,所以開發者可以迅速得到有關程式碼變更的反饋,這有助於快速修復問題。

然而,TDD 也需要更多的時間和努力,因為需要撰寫大量的測試用例。此外,TDD 不一定適用於所有類型的項目,並且在某些情況下,可能需要靈活適應開發流程。儘管如此,它仍然是一種有價值的開發方法,可以提高軟體品質並減少後期的問題。

除了常見的測試情境以外,其實在 MLOps 的許多環節也可以加上測試,明天我們就來看看有哪些可能性吧!
/images/emoticon/emoticon33.gif

參考資料


上一篇
[Day 06] - 無敵風火輪 🌪️ GitHub Action 讓 Rust CI 轉起來
下一篇
[Day 08] - Rust x 單元測試 x MLOps (下)
系列文
Rust 加 MLOps,你說有沒有搞頭?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言