欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > 【Go语言成长之路】 模糊测试

【Go语言成长之路】 模糊测试

2024/12/1 0:39:49 来源:https://blog.csdn.net/pzslongyutianxia/article/details/141689316  浏览:    关键词:【Go语言成长之路】 模糊测试

文章目录

  • 模糊测试
    • 一、前提
    • 二、创建项目
    • 三、添加待测试代码
    • 四、添加单元测试
    • 五、添加模糊测试

模糊测试

​ 本教程介绍了 Go 中模糊测试的基础知识。通过模糊测试,随机数据会针对您的测试运行,以尝试找到漏洞或导致崩溃的输入。可以通过模糊测试发现的漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击。

注:Go语言中模糊测试已经内置,具体可以参考: Go Fuzzing docs, 将来还会添加更多功能。

一、前提

  • Go1.18以及之后。
  • 支持模糊测试的环境。目前,使用覆盖率检测进行模糊测试仅适用于 AMD64 和 ARM64 架构。

二、创建项目

~$ mkdir fuzz
~$ cd fuzz/
~/fuzz$ go mod init example/fuzz
go: creating new go.mod: module example/fuzz

三、添加待测试代码

package mainimport "fmt"// accept a string, loop over it a byte at a time, and return the reversed string at the end.
func Reverse(s string) string {b := []byte(s)for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {b[i], b[j] = b[j], b[i]}return string(b)
}func main() {input := "The quick brown fox jumped over the lazy dog"rev := Reverse(input)doubleRev := Reverse(rev)fmt.Printf("original: %q\n", input)fmt.Printf("reversed: %q\n", rev)fmt.Printf("reversed again: %q\n", doubleRev)
}

运行代码:

~/fuzz$ go run ./fuzz
original: "The quick brown fox jumped over the lazy dog"
reversed: "god yzal eht revo depmuj xof nworb kciuq ehT"
reversed again: "The quick brown fox jumped over the lazy dog"

四、添加单元测试

为 Reverse 函数编写基本单元测试,在fuzz文件夹内新建一个测试文件reverse_test.go

package mainimport ("testing"
)func TestReverse(t *testing.T) {testcases := []struct {in, want string}{{"Hello, world", "dlrow ,olleH"},{" ", " "},{"!12345", "54321!"},}for _, tc := range testcases {rev := Reverse(tc.in)if rev != tc.want {t.Errorf("Reverse: %q, want %q", rev, tc.want)}}
}

这个简单的测试将断言列出的输入字符串将被正确反转。

之后运行测试代码:

~/fuzz$ go test . -v
=== RUN   TestReverse
--- PASS: TestReverse (0.00s)
PASS
ok      example/fuzz    0.003s

五、添加模糊测试

​ 单元测试有局限,即每个输入都必须由开发人员添加到测试中,使用模糊测试的好处就是可以为代码提供输入,并且可以识别测试用例没有达到边缘情况。

注:单元测试、基准测试和模糊测试可以保留在同一个*_test.go文件中。

​ 编写的模糊测试函数代码如下:

func FuzzReverse(f *testing.F) {testcases := []string{"Hello, world", " ", "!12345"}for _, tc := range testcases {f.Add(tc)  // Use f.Add to provide a seed corpus}f.Fuzz(func(t *testing.T, orig string) {rev := Reverse(orig)doubleRev := Reverse(rev)if orig != doubleRev {t.Errorf("Before: %q, after: %q", orig, doubleRev)}if utf8.ValidString(orig) && !utf8.ValidString(rev) {t.Errorf("Reverse produced invalid UTF-8 string %q", rev)}})
}

使用模糊测试有如下几个特点:

  • 无法预测结果。因为无法控制模糊测试的输入

  • 可以验证函数的属性,在本例中可以检查的属性有:

    • 将字符串反转两次可以保留原始值
    • 反转的字符串将其状态保留为有效的 UTF-8。

请注意单元测试和模糊测试之间的语法差异:

  • 该函数以 FuzzXxx 而不是TestXxx开头,并采用 *testing.F 而不是 *testing.T
  • 将 t.Run 替换为 f.Fuzz,它采用了一个模糊目标函数,其参数为 *testing.T 和要模糊的类型。使用 f.Add 将单元测试的输入作为种子语料库输入提供。

之后就可以运行模糊测试并查看结果:

~/fuzz$ go test ./fuzz -v
=== RUN   TestReverse
--- PASS: TestReverse (0.00s)
=== RUN   FuzzReverse
=== RUN   FuzzReverse/seed#0
=== RUN   FuzzReverse/seed#1
=== RUN   FuzzReverse/seed#2
--- PASS: FuzzReverse (0.00s)--- PASS: FuzzReverse/seed#0 (0.00s)--- PASS: FuzzReverse/seed#1 (0.00s)--- PASS: FuzzReverse/seed#2 (0.00s)
PASS
ok      example/fuzz    0.003s

如果你只想运行模糊测试,那么可以执行如下命令:

~/fuzz$ go test . -run=FuzzReverse -v # 运行特定的函数
=== RUN   FuzzReverse
=== RUN   FuzzReverse/seed#0
=== RUN   FuzzReverse/seed#1
=== RUN   FuzzReverse/seed#2
--- PASS: FuzzReverse (0.00s)--- PASS: FuzzReverse/seed#0 (0.00s)--- PASS: FuzzReverse/seed#1 (0.00s)--- PASS: FuzzReverse/seed#2 (0.00s)
PASS
ok      example/fuzz    0.003s 
~/fuzz$go test -fuzz=Fuzz . -v # 只运行模糊测试
=== RUN   TestReverse
--- PASS: TestReverse (0.00s)
=== RUN   FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
fuzz: elapsed: 0s, execs: 253 (10168/sec), new interesting: 2 (total: 5)
--- FAIL: FuzzReverse (0.03s)--- FAIL: FuzzReverse (0.00s)reverse_test.go:36: Reverse produced invalid UTF-8 string "\xa3\xd6"Failing input written to testdata/fuzz/FuzzReverse/14a85158d50021f3To re-run:go test -run=FuzzReverse/14a85158d50021f3
=== NAME  
FAIL
exit status 1
FAIL    example/fuzz    0.029s

​ 另一个有用的标志是 -fuzztime,它限制模糊测试所需的时间, 若没有指定模糊测试的运行时间,那么它将会一直运行下去,直到发生错误。

​ 模糊测试时发生故障,导致问题的输入被写入种子语料库文件,该文件将在下次调用 go test 时运行,即使没有 -fuzz 标志也是如此。要查看导致失败的输入,请在文本编辑器中打开写入 testdata/fuzz/FuzzReverse目录的语料库文件。您的种子语料库文件可能包含不同的字符串,但格式将相同, 类型如下:

go test fuzz v1
string("֣")

​ 语料库文件的第一行表示编码版本。接下来的每一行代表构成语料库条目的每种类型的值。由于模糊目标仅需要 1 个输入,因此版本后只有 1 个值。

​ 再次运行 go test,不带 -fuzz 标志;将使用新的失败种子语料库条目:

~/fuzz$ go test ./fuzz
--- FAIL: FuzzReverse (0.00s)--- FAIL: FuzzReverse/14a85158d50021f3 (0.00s)reverse_test.go:36: Reverse produced invalid UTF-8 string "\xa3\xd6"
FAIL
FAIL    example/fuzz    0.003s
FAIL

若想使用原来失败的语料种子,可以执行如下命令:

~/fuzz$ go test -run=FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0
input: "\x91"
runes: ['�']
input: "�"
runes: ['�']
--- FAIL: FuzzReverse (0.00s)--- FAIL: FuzzReverse/28f36ef487f23e6c7a81ebdaa9feffe2f2b02b4cddaa6252e87f69863046a5e0 (0.00s)reverse_test.go:16: Number of runes: orig=1, rev=1, doubleRev=1reverse_test.go:18: Before: "\x91", after: "�"
FAIL
exit status 1
FAIL    example/fuzz    0.145s

之后就是定位问题并解决了。

遇到错误的几点建议:

  • 诊断错误

    有如下几种方法:

    • 通过VS Code设置调试器进行调查(遇到比较棘手的错误可以使用此方法)

    • 将把有用的调试信息记录到您的终端

      比如说使用t.LogF将错误相关的内容打印出来,或者如果发生错误,或者使用 -v 执行测试,此 t.Logf 行将打印到命令行,这可以帮助您调试此特定问题。

  • 解决错误

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com