美文网首页
go 模糊测试

go 模糊测试

作者: 护念 | 来源:发表于2023-07-15 13:27 被阅读0次

版本要求:go 1.18(至少)

什么是模糊测试?

模糊顾名思义它不是指定某个具体测试数据,然后去测试;它的原理是根据我们给了一个基本的种子测试数据,自动随机生成一系列未知的测试数据,去测试。

用途:通常用于测试一些普通测试无法覆盖到的临界测试条件,它的覆盖更广、更全面

PS:启动模糊测试后,如果未发生测试失败的情况下,测试会一直运行下去,除非人为终止(当然我们可以手动指定一个允许测试的耗费时间,比如10s)

初始化项目代码

mkdir fuzz
cd fuzz
go mod init example/fuzz
touch main.go
  • 写main.go文件
// main.go
package main

import "fmt"

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)
}

// 将字符串进行反转
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)
}
  • 运行
dongmingyan@pro ⮀ ~/go_playground/fuzz ⮀ go run .
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"

添加模糊测试

模糊测试的文件和普通测试一样

  • 测试代码
    在当前项目列表下,添加文件touch reverse_test.go
// reverse_test.go
package main

import (
    "testing"
    "unicode/utf8"
)

// 模糊测试函数以Fuzz开头
// *testing.go 代表的是指针变量
func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    
    for _, tc := range testcases {
        f.Add(tc) // 添加种子测试数据,模糊测试将以此种子数据为基础生成测试数据
    }

    // 模糊测试部分
    f.Fuzz(func(t *testing.T, orig string) {
        rev := Reverse(orig)
        doubleRev := Reverse(rev)
        // 由于模糊测试具有随机性,我们事先并不清楚,输入是什么,因此
        // 只能按照某些规则去写测试,这里按照生成
        // 1. 生成前后都是uft8
        // 2. 反转之后再反转应该和原来相同进行测试
        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)
        }
    })
}
  • 运行测试go test -fuzz=Fuzz
✘ dongmingyan@pro ⮀ ~/go_playground/fuzz ⮀ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/38 completed
fuzz: minimizing 47-byte failing input file
fuzz: elapsed: 0s, gathering baseline coverage: 5/38 completed
--- FAIL: FuzzReverse (0.02s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:21: Reverse produced invalid UTF-8 string "\xb8\xca"

    Failing input written to testdata/fuzz/FuzzReverse/0463d8535940015d
    To re-run:
    go test -run=FuzzReverse/0463d8535940015d
FAIL
exit status 1
FAIL    example/fuzz    0.639s
  • 修复错误
    注意这时候如果你仔细观察会发现在项目下生成了一个testdata/fuzz/FuzzReverse/xxxxxxxx文件,这个文件记录的是模糊测试失败的用例

从上面的报错中我们可以看出,这个是由于reverse后产生了非utf8字符导致的。
为了调试这里的错误,我们可以通过 打印fmt.Printf 或者 日志t.Logf等方式找出错误原因,这里省略此过程

  1. 修复main.go文件
// main.go
package main

import (
    "errors"
    "fmt"
    "unicode/utf8"
)

func main() {
    input := "The quick brown fox jumped over the lazy dog"
    rev, revErr := Reverse(input)
    doubleRev, doubleRevErr := Reverse(rev)
    fmt.Printf("original: %q\n", input)
    fmt.Printf("reversed: %q, err: %v\n", rev, revErr)
    fmt.Printf("reversed again: %q, err: %v\n", doubleRev, doubleRevErr)
}

func Reverse(s string) (string, error) {
    // 防止输入过程中包含 无效uft8字符
    if !utf8.ValidString(s) {
        return s, errors.New("input is not valid UTF-8")
    }

    r := []rune(s) // 对于有些字符,可能需要几个字节,比如中文,所以这里不能用字节类型,用rune-32位
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r), nil
}
  1. 修复测试文件
// reverse_test.go
package main

import (
    "testing"
    "unicode/utf8"
)

func FuzzReverse(f *testing.F) {
    testcases := []string{"Hello, world", " ", "!12345"}
    for _, tc := range testcases {
        f.Add(tc) // 
    }
    f.Fuzz(func(t *testing.T, orig string) {
        rev, err1 := Reverse(orig)
        // 有错误直接跳过
        if err1 != nil {
            return
        }

        doubleRev, err2 := Reverse(rev)
        if err2 != nil {
            return
        }
        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)
        }
    })
}
  • 修复后再次运行
 dongmingyan@pro ⮀ ~/go_playground/fuzz ⮀ go test -fuzz=Fuzz
fuzz: elapsed: 0s, gathering baseline coverage: 0/39 completed
fuzz: elapsed: 0s, gathering baseline coverage: 39/39 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 365386 (121788/sec), new interesting: 1 (total: 40)
fuzz: elapsed: 6s, execs: 744288 (126305/sec), new interesting: 1 (total: 40)
fuzz: elapsed: 9s, execs: 1114466 (123388/sec), new interesting: 1 (total: 40)
^Cfuzz: elapsed: 10s, execs: 1237156 (121777/sec), new interesting: 1 (total: 40)
PASS
ok      example/fuzz    10.532s

另外也可以通过go test -fuzz=Fuzz -fuzztime=10s指定运行时间为10s

小结:

  1. 模糊测试和普通测试在同一个文件
  2. 模糊测试命令go test -fuzz=Fuzz
  3. 指定运行时间go test -fuzz=Fuzz -fuzztime=10s
  4. 模糊测试失败后会在项目下生成一个testdata文件夹用于存放失败用例

相关文章

网友评论

      本文标题:go 模糊测试

      本文链接:https://www.haomeiwen.com/subject/ekobudtx.html