確定性測試和隨機性測試
- 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