確定性測試和隨機性測試

  • 2019 年 12 月 12 日
  • 筆記

顧翔老師開發的bugreport2script開源了,希望大家多提建議。文件在https://github.com/xianggu625/bug2testscript,

主文件是:zentao.py 。bugreport是禪道,script是python3+selenium 3,按照規則在禪道上書寫的bugreport可由zentao.py程式生成py測試腳本。

來源:http://www.51testing.com

開篇

  在開始本篇文章之前,我們首先來認清兩個概念:

  確定性測試:在給定一條輸入,一定有對應的一條輸出結果的前提下。那麼如果我們的輸入數量固定,那麼輸出的數據也一定是固定的。

  隨機性測試: 如果想辦法讓輸入無限擴增,則在擁有無數條輸入情況下,我們就擁有了無數條的輸出。那麼,我們拿無限的輸入中的任意條來測試,則就擁有了隨機性測試。

確定性測試

  事實上,自打有 「驗證」 這個概念起,確定性測試就一直伴隨著人類本身,並且隨著人類的進步和發展逐步推進。直到電腦科學領域,我們擁有了形式化和場景化的測試形式,我們稱之為一條一條的 test case。也有瘋狂的人提出了 TDD 編碼,即,測試驅動開發。我們總假設我們的世界是有序的,至少在我們構築的規則里是有序的,我們這種秩序進行測量,並輔以各種說明。

  「嘿,你看!我的程式碼在這種情況下是OK的。」

  「你知道么,我的這段程式碼在這種情況下一定會出問題。」

  然而,這種確定性測試真的能幫我們證明:我的程式沒問題么?

  答案是不能的。我們必須要滿足所有的case,現在你寫出來的,沒寫出來的,現在不可能出現的,現在已經出現的。這樣,我們在所有的場景下都通過了我們的程式碼,這段程式碼才可以被認為是沒問題的。然而這可能么?在大多數情況下,不可能。當然了,你如果真寫出諸如 

fn assert_false(input: bool) {  if input {  panic!("");  }  }

這樣的程式碼,自然他的input是有限的,那麼他的case也就只有 true 或 false 兩種情況。然而事實上,我們面臨的處境往往遠遠比這種場景複雜的多。比如我們解析一段128位元組二進位數據,僅僅可能性就高達: 256^128 種,這裡面我們想要枚舉,那是基本上不可能的。莊子云:「吾生而有涯,而知也無涯。以有涯隨無涯,殆已!」

  我們在無法確定 full cover 測試用例的情況下,自然也就無法確保自身程式的正確性。即,絕對的符合行為是不存在的。那麼,有沒有辦法逼近這種絕對的正確性呢?當然有,也就是我們今天要講的 fuzz 測試。

隨機性測試

  我們將採取一定的演算法,從一定的基礎語料里生成一系列的基準 case,同時每個 case 由一定的隨機規則生成更多的測試case,並且由我們的測試用例判斷:當前測試語料有價值或者沒有價值。如果沒有價值則丟棄,有價值則重新加入進語料庫中或者提升權重。整個過程可以稱為遺傳選擇,也就是遺傳演算法的應用。最終,在足夠長的時間遺傳和選擇之後,我們得會得到一個最終收斂的語料庫,或者一個無限擴增的語料庫,或者一個。。。PANIC!

  這個 panic ,其實就是經過我們的語料積累之後隨機測試出來的BUG!

  當然,這個 panic 最終也是會被收錄入語料庫中,並且會給予高權重。

  有趣的是,我們雖然能確定哪些case是有價值的,但是,從最終的語料庫結果來說,並不是語料庫的最終積累都會像你預期的那樣。還是那句話,你能想到的case,語料庫都會幫你找到,你想不到的,fuzz會幫你找到。

  下面我們來進行一下實踐:

     cargo install cargo-fuzz      git clone https://github.com/servo/rust-url.git      cd rust-url      git checkout bfa167b4e0253642b6766a7aa74a99df60a94048      cargo fuzz init      cargo fuzz list      tee fuzz/fuzz_targets/fuzz_target_1.rs <<EOF      <<#![no_main]      <<#[macro_use] extern crate libfuzzer_sys;      <<extern crate url;      <<      <<fuzz_target!(|data: &[u8]| {      <<    if let Ok(s) = std::str::from_utf8(data) {      <<        let _ = url::Url::parse(s);      <<    }      <<});      <<EOF      cargo fuzz run fuzz_target_1

  然後就是漫長的等待,經過了足夠的隨機測試之後,我們得到了如下的結果:

   [0] ==14475== ERROR: libFuzzer: deadly signal      [0]     #0 0x7f4907f099f7  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0xe29f7)      [0]     #1 0x7f49080fd697  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2d6697)      [0]     #2 0x7f49080d2672  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2ab672)      [0]     #3 0x7f49080d252d  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2ab52d)      [0]     #4 0x7f49080fde7c  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2d6e7c)      [0]     #5 0x7f49072df88f  (/lib/x86_64-linux-gnu/libpthread.so.0+0xf88f)      [0]     #6 0x7f4906d44066  (/lib/x86_64-linux-gnu/libc.so.6+0x35066)      [0]     #7 0x7f4906d45447  (/lib/x86_64-linux-gnu/libc.so.6+0x36447)      [0]     #8 0x7f490810f2e6  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2e82e6)      [0]     #9 0x7f490810ac25  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2e3c25)      [0]     #10 0x7f49080b8d9b  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x291d9b)      [0]     #11 0x7f490810e668  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2e7668)      [0]     #12 0x7f490810e101  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2e7101)      [0]     #13 0x7f490810dfe5  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2e6fe5)      [0]     #14 0x7f4908120e8c  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2f9e8c)      [0]     #15 0x7f4908120dcb  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2f9dcb)      [0]     #16 0x7f4907f75d2f  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x14ed2f)      [0]     #17 0x7f4907f5d49d  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x13649d)      [0]     #18 0x7f4907e6bdbc  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x44dbc)      [0]     #19 0x7f4907e6b611  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x44611)      [0]     #20 0x7f4907e6b194  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x44194)      [0]     #21 0x7f49080b8a24  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x291a24)      [0]     #22 0x7f490810214d  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2db14d)      [0]     #23 0x7f490810f348  (/root/repo/rust-url/fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_target_1+0x2e8348)      [0]      [0] NOTE: libFuzzer has rudimentary signal handlers.      [0]       Combine libFuzzer with AddressSanitizer or similar for better crash reports.      [0] SUMMARY: libFuzzer: deadly signal      [0] MS: 3 PersAutoDict-CrossOver-CMP- DE: "x00x0d"-"file"-; base unit: c212d1287c727dc38d49bacaf63f8ab2927458a6      [0] 0x66,0x69,0x6c,0x65,0x3a,0x25,      [0] file:%      [0] artifact_prefix='/root/repo/rust-url/fuzz/artifacts/fuzz_target_1/'; Test unit written to /root/repo/rust-url/fuzz/artifacts/fuzz_target_1/crash-e2ce10aeea27d777611b40ef52e4db504bfa7a5f      [0] Base64: ZmlsZTol

  然後,我們就可以在目標輸出里找到我們的case: /root/repo/rust-url/fuzz/artifacts/fuzz_target_1/crash-e2ce10aeea27d777611b40ef52e4db504bfa7a5f, 文件內容:

file:%

另外說一下

  cargo fuzz run 的時候是可以指定 -j 選項的,一般開滿CPU核心即可。fuzz 可以充分的利用多核優勢,我開了 -j 20 ,幾乎是在數秒之內就得到了最終結果。

  以上,就是隨機測試的一點小小的應用。

星雲測試

http://www.teststars.cc

奇林軟體

http://www.kylinpet.com

聯合通測

http://www.quicktesting.net