Golang之模糊测试工具的使用

目录
  • 背景
  • 开发环境
  • go-fuzzing
  • fuzz tests规则
  • 如何使用go-fuzzing
  • 生产环境项目Go版本问题

背景

我们经常调侃程序员每天都在写bug,这确实是事实,没有测出bug不代表程序就真的不存在问题。传统的代码review、静态分析、人工测试和自动化的单元测试无法穷尽所有输入组合,尤其是难以模拟一些随机的、边缘的数据。

去年6月,Go官方发布称gotip版本已经原生支持Fuzzing并开始了公测,将与[Go 1.18版本]一起在2022年中发布,go-fuzzing至今已经发现了Go标准库超过200个bug(https://github.com/dvyukov/go-fuzz#trophies )。即将发布的[Go 1.18版本]就提供了一个代码自测的绝佳工具go-fuzzing。

说到[Go 1.18版本],大家最关注的应该是泛型,但是我个人觉得go-fuzzing也是其中的一个亮点,Go 1.18将fuzz testing纳入了go test工具链,与单元测试、性能基准测试等一起成为了Go原生测试工具链中的重要成员。

本次就来说下go-fuzzing这个工具。

开发环境

升级到Go 1.18
Go 1.18虽然还没正式发布,但可以下载RC版本,而且即使你生产环境用是Go的老版本,你个人的本地开发环境也可以升级到1.18,还可以使用go-fuzzing更好的自测

go-fuzzing

官方文档:go fuzzing是通过持续给一个程序不同的输入来自动化测试,并通过分析代码覆盖率来智能的寻找失败的例子。这种方法可以尽可能的找到一些边界问题,亲测确实发现的都是些平时比较难发现的问题。

fuzzing,又叫fuzz testing,中文叫做模糊测试或随机测试。其本质上是一种自动化测试技术,更具体一点,它是一种基于随机输入的自动化测试技术,常被用于发现处理用户输入的代码中存在的bug和问题。

fuzz tests规则

func FuzzFoo(f *testing.F) {
    f.Add(5, "hello")

    f.Fuzz(func(t *testing.T, i int, s string) {
        out, err := Foo(i, s)
        if err != nil && out != "" {
            t.Errorf("%q, %v", out, err)
        }
    })
}
  • 函数必须是Fuzz开头,唯一的参数只有*testing.F,没有返回值
  • Fuzz tests必须在名为*_test.go的文件下才能执行
  • fuzz target是个方法,它调用(*testing.F).Fuzz,第一个参数是 *testing.T,之后的参数就是称之为fuzzing arguments的参数,方法没有返回值
  • 每个fuzz test中只能有一个fuzz target
  • 调用f.Add()的时候需要参数类型跟fuzzing arguments顺序和类型都保持一致
  • fuzzing arguments只支持以下类型:
    • int, int8, int16, int32/rune, int64
    • uint, uint8/byte, uint16, uint32, uint64
    • string, []byte
    • float32, float64
    • bool

如何使用go-fuzzing

1、首先要先定义fuzzing arguments,并通过fuzzing arguments写fuzzing target

2、思考fuzzing target怎么写,重点是怎么验证结果的正确性,因为fuzzing arguments是随机给的,所以要有个验证结果的方法

3、遇到失败的例子怎么去打印出错误结果

4、根据错误结果去生成新的测试用例,这个新的测试用例会被用来调试发现的bug,并且可以留下给CI使用

下面是一个切片中数字求和的例子:

// slice_sum.go
func SliceSum(arr []int64) int64 {
  var sum int64

  for _, val := range arr {
    if val % 100000 != 0 {
      sum += val
    }
  }

  return sum
}

第一步:定义fuzzing arguments模糊参数

至少需要给出一个fuzzing arguments,不然go-fuzzing没法生成测试代码。
这是切片中元素求和的方法,那我们可以把切片的元素个数n(自行模拟个数即可)作为fuzzing arguments,然后go-fuzzing会根据运行的代码覆盖率自动生成不同的参数来模拟测试。

// slice_sum_test.go
func FuzzSliceSum(f *testing.F) {
  // 10,go-fuzzing称之为语料,10这个值就是让go fuzzing冷启动的一个值,具体多少不重要
  f.Add(10)

  f.Fuzz(func(t *testing.T, n int) {
      // 限制20个元素
    n %= 20

    // 剩余处理

  })
}

第二步:编写fuzzing target

重点是编写可以验证的fuzzing target,不仅要根据给定的模糊参数写出测试代码,而且还需要生成可以验证结果正确性的数据。
对这个切片元素求和的方法来说,就是随机生成n个元素的切片,然后进行求和得到正确的结果。

package fuzz

import (
    "github.com/stretchr/testify/assert"
    "math/rand"
    "testing"
    "time"
)

// slice_sum_test.go
func FuzzSliceSum(f *testing.F) {
  // 初始化随机数种子
  rand.Seed(time.Now().UnixNano())
  // 语料
  f.Add(10)

  f.Fuzz(func(t *testing.T, n int) {
    n %= 20

    var arr []int64
    var expect int64 // 期望值

    for i := 0; i < n; i++ {
      val := rand.Int63() % 1000000
      arr = append(arr, val)
      expect += val
    }

    // 自己求和的结果和调用函数求和的结果比对
    assert.Equal(t, expect, SliceSum(arr))
  })
}

执行模糊测试

➜  fuzz go test -fuzz=SliceSum
fuzz: elapsed: 0s, gathering baseline coverage: 0/52 completed
fuzz: elapsed: 0s, gathering baseline coverage: 52/52 completed, now fuzzing with 8 workers
fuzz: elapsed: 0s, execs: 9438 (34179/sec), new interesting: 2 (total: 54)
--- FAIL: FuzzSliceSum (0.28s)
    --- FAIL: FuzzSliceSum (0.00s)
        slice_sum_test.go:32: 
                Error Trace:    slice_sum_test.go:32
                                                        value.go:556
                                                        value.go:339
                                                        fuzz.go:337
                Error:          Not equal: 
                                expected: 5715923
                                actual  : 5315923
                Test:           FuzzSliceSum
    
    Failing input written to testdata/fuzz/FuzzSliceSum/8e8981ffa4ee4d93f475c807563f9d63854a6c913cdfb10a73191549318a2a51
    To re-run:
    go test -run=FuzzSliceSum/8e8981ffa4ee4d93f475c807563f9d63854a6c913cdfb10a73191549318a2a51
FAIL
exit status 1
FAIL    demo/fuzz       0.287s

上面这段输出,你只能看出预期值和实际值不一样,但是很难分析错误。

第三步:打印出错误的例子

上面的错误输出,如果能打印出造成错误的例子的话,就可以直接作为测试用例进行单测。我们总不能一个个去试吧,而且错误的例子未必只有一个。

修改下模糊测试代码,增加打印:

package fuzz

import (
    "github.com/stretchr/testify/assert"
    "math/rand"
    "testing"
    "time"
)

// slice_sum_test.go
func FuzzSliceSum(f *testing.F) {
    // 初始化随机数种子
    rand.Seed(time.Now().UnixNano())
    // 语料
    f.Add(10)

    f.Fuzz(func(t *testing.T, n int) {
        n %= 20

        var arr []int64
        var expect int64 // 期望值
        var buf strings.Builder
        buf.WriteString("\n")

        for i := 0; i < n; i++ {
            val := rand.Int63() % 1000000
            arr = append(arr, val)
            expect += val
            buf.WriteString(fmt.Sprintf("%d,\n", val))
        }

        // 自己求和的结果和调用函数求和的结果比对
        assert.Equal(t, expect, SliceSum(arr), buf.String())
    })
}

再次执行模糊测试

➜  fuzz go test -fuzz=SliceSum
fuzz: elapsed: 0s, gathering baseline coverage: 0/47 completed
fuzz: elapsed: 0s, gathering baseline coverage: 47/47 completed, now fuzzing with 8 workers
fuzz: elapsed: 0s, execs: 17109 (42507/sec), new interesting: 2 (total: 49)
--- FAIL: FuzzSliceSum (0.41s)
    --- FAIL: FuzzSliceSum (0.00s)
        slice_sum_test.go:34: 
                Error Trace:    slice_sum_test.go:34
                                                        value.go:556
                                                        value.go:339
                                                        fuzz.go:337
                Error:          Not equal: 
                                expected: 7575516
                                actual  : 7175516
                Test:           FuzzSliceSum
                Messages:       
                                92016,
                                642504,
                                400000,
                                489403,
                                472011,
                                811028,
                                315130,
                                298207,
                                57765,
                                542614,
                                136594,
                                351360,
                                867104,
                                918715,
                                515092,
                                665973,
    
    Failing input written to testdata/fuzz/FuzzSliceSum/9191ba4d7ea5420a9a76661d4e7d6a7a4e69ad4d5d8ef306ff78161a2acf1416
    To re-run:
    go test -run=FuzzSliceSum/9191ba4d7ea5420a9a76661d4e7d6a7a4e69ad4d5d8ef306ff78161a2acf1416
FAIL
exit status 1
FAIL    demo/fuzz       0.413s

第四步:根据输出的错误例子,编写新的测试用例进行单测

// 单测通过后,再执行模糊测试,看看有没有其他边缘问题出现
func TestSliceSumFuzzCase1(t *testing.T) {
    arr := []int64{
        92016,
        642504,
        400000,
        489403,
        472011,
        811028,
        315130,
        298207,
        57765,
        542614,
        136594,
        351360,
        867104,
        918715,
        515092,
        665973,
    }
    // 期望值从第三步的输出中获取
    assert.Equal(t, int64(7575516), SliceSum(arr))
}

这样就可以很方便的进行调试了,并且能够增加有效的测试用例进行单测,确保这个bug不会出现了。

生产环境项目Go版本问题

线上项目的Go版本不能升级到1.18怎么办?

线上的版本不升级到1.18,但是我们本地开发升级没有问题,可以在文件的头部增加如下命令注释:

slice_sum_test.go

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

这样我们在线上不管用哪个版本都不会报错,而且我们一般都是在本地进行模糊测试

注意:第三行必须是空行,不然就会变成package的注释了

有些还无法复现的问题,比如协程死锁,输出一直在执行或者卡住然后过一会才结束,这类的长时间执行的模糊测试,我还没有摸透。如果有大佬知道的话麻烦也告诉我下。

参考
https://github.com/dvyukov/go-fuzz#trophies
https://go.dev/blog/fuzz-beta

到此这篇关于Golang之模糊测试工具的使用的文章就介绍到这了,更多相关Golang 模糊测试 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go1.18新特性工作区模糊测试及泛型的使用详解

    目录 前言 Go工作区模式(Go Workspace Mode) 现实的情况 多仓库同时开发 多个新仓库开始开发 工作区模式是什么 推荐的使用方法 使用时的注意点 Go模糊测试(Go Fuzzing Test) 为什么Golang要支持模糊测试 模糊测试是什么 Golang的模糊测试如何使用 最简单的实践例子 提供自定义语料 使用时的注意点 Go的泛型 类型参数(Type Parameters) 类型集合(Type Sets) 类型推导(Type Inference) 类型统一化(Type Un

  • Go中的新增对模糊测试的支持

    目录 什么是模糊测试? Go 中的软件测试 新增对模糊测试的支持 安装 gotip 来获取最新的功能 社区对于模糊测试的观点 现实中的模糊测试 为什么在 Go 中新增对模糊测试的原生支持 模糊测试工具 Go 团队接受了新增对模糊测试的支持的提议. Go 的应用越来越广泛.现在它是云原生软件.容器软件.命令行工具和数据库等等的首选语言.Go 很早之前就已经有了内建的 对测试的支持.这使得写测试代码和运行都相当简单. 什么是模糊测试? 模糊测试fuzz testing(fuzzing)是指向你的软件

  • Golang之模糊测试工具的使用

    目录 背景 开发环境 go-fuzzing fuzz tests规则 如何使用go-fuzzing 生产环境项目Go版本问题 背景 我们经常调侃程序员每天都在写bug,这确实是事实,没有测出bug不代表程序就真的不存在问题.传统的代码review.静态分析.人工测试和自动化的单元测试无法穷尽所有输入组合,尤其是难以模拟一些随机的.边缘的数据. 去年6月,Go官方发布称gotip版本已经原生支持Fuzzing并开始了公测,将与[Go 1.18版本]一起在2022年中发布,go-fuzzing至今已

  • Golang 官方依赖注入工具wire示例详解

    目录 依赖注入是什么 开源选型 wire providers injectors 类型区分 总结 依赖注入是什么 Dependency Injection is the idea that your components (usually structs in go) should receive their dependencies when being created. 在 Golang 中,构造一个结构体常见的有两种方式: 在结构体初始化过程中,构建它的依赖: 将依赖作为构造器入参,传入进

  • 在一个浏览器里呈现所有浏览器测试结果的前端测试工具的思路

    作为一个标准的绝顶的懒人,我想做一个测试工具,可以同时把所有浏览器的测试结果都显示在一个浏览器窗口里,并且列成清晰的表格,便于比较. 这一定会是一个可爱的工具,节省时间不说,而且可以清晰的记录和比较数据.下面说一下我的思路.(之后我将使用这个工具测试一个js的兼容性问题,敬请关注) 本工具已经制作完成,但是不具通用性,需要和后台配合使用,需要和数据库交互,而且后台交互的性能不太好,普通的电脑无法承受(我的3GHZ的cpu,2G内存都不能直接打开多个浏览器窗口,可能是我数据库的操作过于频繁).虽然

  • web压力测试工具_动力节点Java 学院整理

    0. Grinder –  Grinder是一个开源的JVM负载测试框架,它通过很多负载注射器来为分布式测试提供了便利. 支持用于执行测试脚本的Jython脚本引擎HTTP测试可通过HTTP代理进行管理.根据项目网站的说法,Grinder的 主要目标用户是"理解他们所测代码的人--Grinder不仅仅是带有一组相关响应时间的'黑盒'测试.由于测试过程可以进行编码--而不是简单地脚本 化,所以程序员能测试应用中内部的各个层次,而不仅仅是通过用户界面测试响应时间. 1. Pylot -Pylot 是

  • 基于Python的XSS测试工具XSStrike使用方法

    简介 XSStrike 是一款用于探测并利用XSS漏洞的脚本 XSStrike目前所提供的产品特性: 对参数进行模糊测试之后构建合适的payload 使用payload对参数进行穷举匹配 内置爬虫功能 检测并尝试绕过WAF 同时支持GET及POST方式 大多数payload都是由作者精心构造 误报率极低 debian及kali系统可直接下载 本.deb安装包 通用安装方法 使用如下命令进行下载: git clone https://github.com/UltimateHackers/XSStr

  • Mysqlslap MySQL压力测试工具 简单教程

    MySQL从5.1.4版开始带有一个压力测试工具mysqlslap,通过模拟多个并发客户端访问mysql来执行测试,使用起来非常的简单.通过mysqlslap –help可以获得可用的选项,这里列一些主要的参数,更详细的说明参考官方手册. 复制代码 代码如下: –auto-generate-sql, -a 自动生成测试表和数据 –auto-generate-sql-load-type=type 测试语句的类型.取值包括:read,key,write,update和mixed(默认). –numb

  • Web压力测试工具:http_load、webbench、ab、Siege使用方法

    前言:常在网络飘,哪有不挨刀?做网站的站长说不准哪天得罪了XX人,二话不说直接操着DDOS就上来搞你了,黑不了你,哥就D死你,遇到这样的无赖,还真没办法,因此经常性的给自己的网站做做压力测试,看看服务器的承载能力是很有必要的一件事. 一.http_load 程序非常小,解压后也不到100K http_load以并行复用的方式运行,用以测试web服务器的吞吐量与负载.但是它不同于大多数压力测试工 具,它可以以一个单一的进程运行,一般不会把客户机搞死.还可以测试HTTPS类的网站请求. 下载地址:h

  • Android自动测试工具Monkey

    前言: 最近开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括android测试框架.CTS.Monkey.Monkeyrunner.benchmark.其它test tool等等.因接触时间很短,很多地方有不足之处,希望能和大家多多交流. 一.Monkey定义 探索软件测试工具有哪些,本文主要介绍Monkey工具.Monkey测试是Android平台自动化测试的一种手段,通过Monkey程序模拟用户触摸屏幕.滑动.按键等操作来对设备上的程序进行压力测

随机推荐