确定性测试和随机性测试
- 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