34. 乾貨系列從零用Rust編寫負載均衡及代理,非同步測試在Rust中的實現

2023-12-16 12:00:35

wmproxy

wmproxy已用Rust實現http/https代理, socks5代理, 反向代理, 靜態檔案伺服器,四層TCP/UDP轉發,七層負載均衡,內網穿透,後續將實現websocket代理等,會將實現過程分享出來,感興趣的可以一起造個輪子

專案地址

國內: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

自動化測試

在程式中,通常會寫一些自動化測試的功能,來保證我們的程式碼正確的執行符合預期的效果,隨著時間的變化,當程式碼變動的資料越來越多時,保證能實時的測試確保整個系統高概率的正常運轉,不會因為改一個Bug而產生另一個Bug

Rust中的測試

Rust中的測試分為兩個部分

  • 檔案測試

檔案測試通常在函數前面或者類前面,通過檔案測試來保證該函數的執行能符合我們的預期,通常/// ``` 包圍,以下為測試範例,如果僅僅以///開頭那麼測試

/// 測試vec的大小 
///
/// use std::vec::Vec;
/// 
/// let mut vec = Vec::new();
/// assert_eq!(vec.len(), 0);
/// vec.push(1);
/// assert_eq!(vec.len(), 1);
fn show_test() {

}

此時我們執行cargo test可以看的到如下輸出:

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

此時並不能識別我們的檔案測試用例,那麼我們在程式碼前後加上/// ```再來執行

/// 測試vec的大小 
///
/// ```
/// use std::vec::Vec;
/// 
/// let mut vec = Vec::new();
/// assert_eq!(vec.len(), 0);
/// vec.push(1);
/// assert_eq!(vec.len(), 1);
/// ```
fn show_test() {

}

將得到如下輸出

running 1 test
test src\lib.rs - show_test (line 3) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s  

此時測試通過自動化模組

  • 測試模組
    這是每個模組下可以通過自定義測試函數來對每個類進行自動化測試,讓我們來看下面一個例子:
fn str_len(s: &str) -> usize {
  s.len()
}

async fn str_len_async(s: &str) -> usize {
  // 做些非同步的事情
  s.len()
}

#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
  use super::*;

  #[test]
  fn test_str_len() {
    assert_eq!(str_len("x5ff"), 4);
  }

此時執行測試結果很完美的通過測試

running 1 test
test tests::test_str_len ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 

當我們將str_len->str_len_async時,那麼他提示我們

error[E0369]: binary operation `==` cannot be applied to type `impl Future<Output = usize>`   
  --> src\lib.rs:29:9
   |
29 |         assert_eq!(str_len_async("x5ff"), 4);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         impl Future<Output = usize>
   |         {integer}
   |
   = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `impl Future<Output = usize>` doesn't implement `Debug`
  --> src\lib.rs:29:9
   |
29 |         assert_eq!(str_len_async("x5ff"), 4);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `impl Future<Output = usize>` cannot be formatted using `{:?}` because it doesn't implement `Debug`
   |
   = help: the trait `Debug` is not implemented for `impl Future<Output = usize>`
   = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info)

Some errors have detailed explanations: E0277, E0369.
For more information about an error, try `rustc --explain E0277`.

因為非同步返回的結果是Future,所以我們無法通過編譯。

那麼非同步的函數如何通過自動化測試呢?

我們嘗試將測試函數改成非同步

mod tests {
    use super::*;

    #[test]
    async fn test_str_len() {
        assert_eq!(str_len_async("x5ff").await, 4);
    }
}

編譯器提示我們,因為編譯器暫時不能支援非同步的測試

error: async functions cannot be used for tests
  --> src\lib.rs:28:5
   |
28 |       async fn test_str_len() {
   |       ^----
   |       |
   |  _____`async` because of this
   | |
29 | |         assert_eq!(str_len_async("x5ff").await, 4);
30 | |     }
   | |_____^

非同步測試的兩種方法

此處講的依賴tokioruntime,其它的非同步為類似。

  • #[tokio::test]來完成非同步測試
    首先我們依賴:
[dependencies]
tokio = { version = "*", features = [
    "macros",
    "test-util",
] }
tokio-test = "0.4.3"

此刻我們將程式碼改寫成:

#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_str_len() {
        assert_eq!(str_len_async("x5ff").await, 4);
    }
}

此時執行cargo test,將正常的執行通過

  • 用宏定義來完成非同步測試

此方法運用的是通過同步函數中執行一個runtime來執行非同步函數

以下是我們的宏定義及呼叫

macro_rules! aw {
    ($e:expr) => {
        tokio_test::block_on($e)
    };
}
#[test]
fn test_str_len_async_2() {
    assert_eq!(aw!(str_len_async("x5ff")), 4);
}

此時執行cargo test,將正常的執行通過

running 2 tests
test tests::test_str_len_async_2 ... ok
test tests::test_str_len ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 

但是此時如果是區域性測試,該方法有另一個好處,編輯器的識別度較高,能更好的顯示支援

小結

測試是程式設計中不可缺少的夥伴,他可以讓我們更早的發現問題解決問題,編寫測試用例可能看起來會慢一些,但是對後期可能潛在的Bug的排查會節省大量的時間。

點選 [關注][在看][點贊] 是對作者最大的支援