新功能!Go 模糊测试

383次阅读  |  发布于3年以前

我们曾探讨过[如何有效地测试 Go 代码] ,完善的测试方案能让程序更加健壮与安全。

将于 2022 年 2 月发布的 Go 1.18 是一个大版本:除了备受关注的泛型之外,还有一个非常大的特性将会加入,它就是模糊测试(Fuzzing test)。

为什么需要模糊测试

撰写测试代码能够验证功能特性,提前发现程序错误。我们在设计测试用例时,一般会采用错误猜测增加一些异常用例,检测被测对象在非正常情况下处理异常的能力,以发现被测对象中潜在的缺陷。

但是,采用这种测试设计会面临一个问题:测试用例通常是模糊的。为什么这么说呢?这里列几个原因:

举个例子。假设有一个功能函数,它接收两个整数输入并做一些处理逻辑。那么我们会如何写测试用例呢?

朴素的做法是为期望函数处理的每个数字编写一个测试用例。如果用例数据有 [-100,0]、[222,33]、[-1212,22],此时就有了三个测试用例。但很明显这里漏了一大类情况,例如都是负数?

更有甚者,由于处理逻辑的特殊性,测试用例仅在输入为 [2323,444] 时发生异常,而其他输入数字都正常工作。这应该怎么测试覆盖?

开发人员无法测试所有可能的输入值。此时,模糊测试就体现出了价值。

模糊测试是一种自动化测试技术,其依赖随机函数生成测试用例,并将这些用例不断地输入程序进行测试,以发现代码可能受到影响的问题。

模糊测试的用例是不重复、随机的,这能带给我们意想不到的输入和结果,可以达到我们错过的边缘情况。

Go 模糊测试支持

关于模糊测试,早在 2017 年就已经有了提案(详见 issu #19109、#44551)。Go 模糊测试开源库 go-fuzz 作者 dvyukov 和 @kcc 建议 cmd/go 原生支持模糊测试,就像 tests、benchmarks 和 race detection 一样,是 Go 的一等公民。

提案得到了 Go 官方的支‍持,今年 6 月已将模糊测试置于 tip 上进行 beat 测试。若无意外,模糊测试将于 1.18 版本得到正式发布。

接下来,我们使用 gotip 来尝鲜模糊测试

下载安装 gotip
$ go install golang.org/dl/gotip@latest
$ gotip download

安装完毕后,gotip 作为 go 命令的替代品,现在可以运行以下命令进行模糊测试

$ gotip test -fuzz=Fuzz
编写模糊测试用例

与单元测试一样,模糊测试代码必须写在 *_test.go 的文件中,测试函数命名形如 FuzzXxx。该函数必须传递一个 *testing.F 参数,就像传递给 TestXxx 函数的 *testing.T 参数一样。

func FuzzXXX(f *testing.F) {
    f.Fuzz(func(t *testing.T, m string, n int) {
      err := doSomething(m, n)
      // check for errors in err here
    })
}

模糊测试模板函数形如上述代码。在第二行中,我们通过 f.Fuzz 函数填充一个常规的测试函数,测试函数包括了 *testing.T 类型的 t,功能函数 doSomething 所需要的 string 类型的 mint 类型的 n

Go 将会自动生成 mn 参数的随机测试用例。

下面是一个 Go 官网博客给出的测试 net/url 包函数的模糊测试示例。

//go:build go1.18
// +build go1.18

package fuzz

import (
    "net/url"
    "reflect"
    "testing"
)

func FuzzParseQuery(f *testing.F) {
    f.Add("x=1&y=2")
    f.Fuzz(func(t *testing.T, queryStr string) {
        query, err := url.ParseQuery(queryStr)
        if err != nil {
            t.Skip()
        }
        queryStr2 := query.Encode()
        query2, err := url.ParseQuery(queryStr2)
        if err != nil {
            t.Fatalf("ParseQuery failed to decode a valid encoded query %s: %v", queryStr2, err)
        }
        if !reflect.DeepEqual(query, query2) {
            t.Errorf("ParseQuery gave different query after being encoded\nbefore: %v\nafter: %v", query, query2)
        }
    })
}

其中 queryStr 参数为 string 类型,这代表模糊测试随机生成的输入用例类型。

测试结果

模糊测试

模糊测试会大量消耗机器资源。在默认情况下,它启用 GOMAXPROCS 个 worker 并行运行模糊测试,且永不停止。

我们可以通过以下运行参数,对模糊测试进行控制。

-fuzz name
    Run the fuzz test with the given regexp. Must match at most one fuzz
    test.
-fuzztime
    Run enough iterations of the fuzz test to take t, specified as a
    time.Duration (for example, -fuzztime 1h30s).
    The default is to run forever.
    The special syntax Nx means to run the fuzz test N times
    (for example, -fuzztime 100x).
-keepfuzzing
    Keep running the fuzz test if a crasher is found. (default false)
-parallel
    Allow parallel execution of a fuzz target that calls t.Parallel when
    running the seed corpus.
    While fuzzing with -fuzz, the value of this flag is the maximum number of
    workers to run the fuzz target simultaneously; by default, it is set to
    the value of GOMAXPROCS.
    Note that -parallel only applies within a single test binary.
-race
    Enable data race detection while fuzzing. (default false)
-run
    Run only those tests, examples, and fuzz tests matching the regular
    expression.
    For testing a single seed corpus entry for a fuzz test, the regular
    expression can be in the form $test/$name, where $test is the name of
    the fuzz test, and $name is the name of the file (ignoring file
    extensions) to run.

总结

模糊测试是安全领域内常见的技术,黑客们常使用类似的方法查找系统漏洞。

在测试设计中,我们人工提供的测试用例总是有限的,而错误很可能就发生在被忽略的角落。模糊测试通过自动生成随机数据输入到程序中,监视程序运行情况以发现可能的发生的错误。

在 Go 开源领域,go-fuzz 是一个非常不错的模糊测试工具(感兴趣可以通过参考链接自行学习),但是它没有像使用原生的 Go 工具,例如竞态检测和基准测试一样,那么方便灵活。

目前 Go 原生支持的模糊测试已经 beta 测试了,并将于 Go 1.18 版本发行。这是一个和泛型一样值得期待的功能,两者共同改变着我们开发和测试 Go 程序的方式,让我们拭目以待。

参考

Fuzzing is Beta Ready https://go.dev/blog/fuzz-beta

testing: add fuzz test support https://github.com/golang/go/issues/44551

Design Draft: First Class Fuzzing https://go.googlesource.com/proposal/+/master/design/draft-fuzzing.md

proposal: cmd/go: make fuzzing a first class citizen, like tests or benchmarks https://github.com/golang/go/issues/19109

go-fuzz https://github.com/dvyukov/go-fuzz

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8