Golang模糊测试实践

模糊测试可以简单快速的自动化构建测试用例,尽量遍历各种可能的输入场景,从而保证函数代码覆盖尽可能多的边缘场景。Go原生内置了模糊测试的支持,如果善加利用,可以有效提升Go代码的质量。原文: Fuzz Testing in Golang

题图由Lexica生成
题图由Lexica生成
导言

在 Go 编程领域,有一个提升代码安全性的秘密武器:模糊测试(fuzz testing)。想象一下,有个机器人不知疲倦的向你的 Go 程序扔出除了厨房水槽以外的所有东西,以确保它们坚如磐石。模糊测试不是常规、可预测的测试,而是测试意料之外的、离奇的场景,用随机数据挑战代码,以发现隐藏 bug。

Go 的出现让模糊测试变得轻而易举。由于工具链内置了支持,Go 开发人员可以轻松的将这种强大的测试方法自动化。这就像为代码配备了时刻保持警惕的守护者,不断查找那些可能会漏掉的偷偷摸摸的 bug。

Go 模糊测试就是要将代码推向极限,甚至超越极限,以确保代码在现实世界中能够抵御任何奇特而美妙的输入。这证明了 Go 对可靠性和安全性的承诺,在一个软件需要坚如磐石的世界里,它能让人高枕无忧。

因此,如果你发现应用程序即使在最意想不到的情况下也能流畅运行时,请记住模糊测试所发挥的作用,它作为无名英雄在幕后为 Go 应用的顺利运行而努力。

种子语料库(Seed Corpus):高效模糊测试的基础

种子语料库是提供给模糊测试流程的初始输入集合,用于启动生成测试用例,可以把它想象成锁匠用来制作万能钥匙的初始钥匙集。在模糊测试中,这些种子作为起点,模糊器从中衍生出多种变体,探索大量可能的输入以发现错误。通过精心挑选一组具有代表性的多样化种子,可以确保模糊器从一开始就能覆盖更多领域,从而使测试过程更高效且有效。种子可以是典型用例数据,也可以是边缘用例或以前发现的可诱发错误的输入,从而为彻底测试软件的可靠性奠定基础。

示例:对 Go 字符串反转函数进行模糊测试

我们用 Go 编写一个简单的字符串反转函数,然后创建一个模糊测试。这个示例将有助于说明模糊测试如何在看似简单的函数中发现意想不到的行为或错误。

Go 函数:反转字符串

package main

// ReverseString takes a string as input and returns its reverse.
func ReverseString(s string) string {
    // Convert the string to a rune slice to properly handle multi-byte characters.
    runes := []rune(s)
    for i, j := 0len(runes)-1; i < j; i, j = i+1, j-1 {
        // Swap the runes.
        runes[i], runes[j] = runes[j], runes[i]
    }
    // Convert the rune slice back to a string and return it.
    return string(runes)
}

解释:

  • ReverseString 函数:该函数接收一个字符串参数并返回其反转值。它将字符串作为 rune 切片而不是字节来处理,这对于正确处理大小可能超过一个字节的 Unicode 字符至关重要。
  • Rune 切片:通过将字符串转换为 rune 切片,可以确保正确处理多字节字符,保持字符编码的完整性。
  • 交换:该函数迭代 rune 切片,从两端开始交换元素,然后向中心移动,从而有效反转切片。

ReverseString 函数的模糊测试

我们为这个函数编写模糊测试:

package main

import (
    "testing"
    "unicode/utf8"
)

// FuzzReverseString tests the ReverseString function with fuzzing.
func FuzzReverseString(f *testing.F) {
    // Seed corpus with examples, including a case with Unicode characters.
    f.Add("hello")
    f.Add("world")
    f.Add("こんにちは"// "Hello" in Japanese

    f.Fuzz(func(t *testing.T, original string) {
        // Reverse the string twice should give us the original string back.
        reversed := ReverseString(original)
        doubleReversed := ReverseString(reversed)
        if original != doubleReversed {
            t.Errorf("Double reversing '%s' did not give original string, got '%s'", original, doubleReversed)
        }

        // The length of the original and the reversed string should be the same.
        if utf8.RuneCountInString(original) != utf8.RuneCountInString(reversed) {
            t.Errorf("The length of the original and reversed string does not match for '%s'", original)
        }
    })
}

解释:

  • 种子语料库:我们从一组种子输入开始,包括简单的 ASCII 字符串和一个 Unicode 字符串,以确保模糊测试涵盖一系列字符编码。
  • 模糊函数:模糊函数反转字符串,然后再反转回来,期望得到原始字符串。这是一个简单的不变量,如果反转函数正确的话,就应该总是成立的。它还会检查原始字符串和反转字符串的长度是否相同,以检查多字节字符可能出现的问题。
  • 运行测试:要运行该模糊测试,请使用带有 -fuzz 标志的 go test 命令,如: go test -fuzz=Fuzz
构建用于数据持久化的 Go REST API 的模糊测试

要在 Go 中创建一个接受 POST 请求并将接收到的数据存储到文件中的 REST API,可以使用 net/http 软件包。我们将为处理 POST 请求数据的函数编写模糊测试。请注意,由于模糊测试的性质及其适用性,此处的模糊测试将重点测试数据处理逻辑,而非 HTTP 服务器本身。

步骤 1:处理 POST 请求的 REST API 函数

首先需要设置一个简单的 HTTP 服务器,保证其路由可以处理 POST 请求。该服务器将把 POST 请求正文保存到文件中。

package main

import (
 "io/ioutil"
 "log"
 "net/http"
)

func main() {
 http.HandleFunc("/save", saveDataHandler) // Set up the route
 log.Println("Server starting on port 8080...")
 log.Fatal(http.ListenAndServe(":8080"nil))
}

// saveDataHandler saves the POST request body into a file.
func saveDataHandler(w http.ResponseWriter, r *http.Request) {
 if r.Method != http.MethodPost {
  http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
  return
 }

 // Read the body of the POST request
 body, err := ioutil.ReadAll(r.Body)
 if err != nil {
  http.Error(w, "Error reading request body", http.StatusInternalServerError)
  return
 }
 defer r.Body.Close()

 // Save the data into a file
 err = ioutil.WriteFile("data.txt", body, 0644)
 if err != nil {
  http.Error(w, "Error saving file", http.StatusInternalServerError)
  return
 }

 w.WriteHeader(http.StatusOK)
 w.Write([]byte("Data saved successfully"))
}

这个简单的服务监听 8080 端口,并有一个接受 POST 请求的路由 /save。该路由的处理程序 saveDataHandler 会读取请求正文并将其写入名为 data.txt 的文件中。

步骤 2:编写模糊测试

在模糊测试中,我们将重点关注将数据保存到文件中的功能。由于无法直接对 HTTP 服务器进行模糊测试,我们把处理数据的逻辑提取到单独的函数中,并对其进行模糊测试。

package main

import (
 "bytes"
 "net/http"
 "net/http/httptest"
 "testing"
)

// FuzzSaveDataHandler uses f.Fuzz to fuzz the body of POST requests sent to saveDataHandler.
func FuzzSaveDataHandler(f *testing.F) {
 // Seed corpus with examples, including different types and lengths of data.
 f.Add([]byte("example data")) // Example seed
 f.Add([]byte(""))             // Empty seed

 f.Fuzz(func(t *testing.T, data []byte) {
  // Construct a new HTTP POST request with fuzzed data as the body.
  req, err := http.NewRequest(http.MethodPost, "/save", bytes.NewReader(data))
  if err != nil {
   t.Fatalf("Failed to create request: %v", err)
  }

  // Create a ResponseRecorder to act as the target of the HTTP request.
  rr := httptest.NewRecorder()

  // Invoke the saveDataHandler with our request and recorder.
  saveDataHandler(rr, req)

  // Here, you can add assertions based on the expected behavior of your handler.
  // For example, checking that the response status code is http.StatusOK.
  if rr.Code != http.StatusOK {
   t.Errorf("Expected status OK for input %v, got %v", data, rr.Code)
  }

  // Additional assertions can be added here, such as verifying the response body
  // or the content of the "data.txt" file if necessary.
 })
}

解释:

  • FuzzSaveDataHandler 函数:该函数测试 saveDataHandler 如何处理不同 POST 请求体,基于模糊测试来尝试各种输入数据。
  • 种子语料库:测试从一些示例数据("example data"和空字符串)开始,以指导模糊处理过程。

执行模糊测试

  • 对于每个模糊输入,都会向处理程序发出 POST 请求。
  • ResponseRecorder会捕捉处理程序对这些请求的响应。
  • 测试将检查处理程序是否对所有输入都响应 http.StatusOK 状态,从而判断是否已成功处理这些输入。

运行测试使用 go test -fuzz=FuzzSaveDataHandler 运行模糊测试。测试从种子数据中生成各种输入,并检查处理程序响应。

在 Go 中验证和存储 CSV 数据:模糊测试方法

要创建一个读取 CSV 文件、验证其值并将验证后的数据存储到文件中的函数,我们将按照以下步骤进行操作:

  • 处理 CSV 的函数:该函数将读取 CSV 数据,根据预定义规则验证其内容(为简单起见,假设我们期望两列具有特定的数据类型),然后将验证后的数据存储到新文件中。
  • 模糊测试:我们将为验证 CSV 数据的函数部分编写模糊测试。这是因为模糊测试非常适合测试代码如何处理各种输入,而我们将重点关注验证逻辑。

步骤 1:处理和验证 CSV 数据的功能

package main

import (
 "encoding/csv"
 "fmt"
 "io"
 "os"
 "strconv"
)

// validateAndSaveData reads CSV data from an io.Reader, validates it, and saves valid rows to a file.
func validateAndSaveData(r io.Reader, outputFile string) error {
 csvReader := csv.NewReader(r)
 validData := [][]string{}

 for {
  record, err := csvReader.Read()
  if err == io.EOF {
   break
  }
  if err != nil {
   return fmt.Errorf("error reading CSV data: %w", err)
  }

  if validateRecord(record) {
   validData = append(validData, record)
  }
 }

 return saveValidData(validData, outputFile)
}

// validateRecord checks if a CSV record is valid. For simplicity, let's assume the first column should be an integer and the second a non-empty string.
func validateRecord(record []string) bool {
 if len(record) != 2 {
  return false
 }

 if _, err := strconv.Atoi(record[0]); err != nil {
  return false
 }

 if record[1] == "" {
  return false
 }

 return true
}

// saveValidData writes the validated data to a file.
func saveValidData(data [][]string, outputFile string) error {
 file, err := os.Create(outputFile)
 if err != nil {
  return fmt.Errorf("error creating output file: %w", err)
 }
 defer file.Close()

 csvWriter := csv.NewWriter(file)
 for _, record := range data {
  if err := csvWriter.Write(record); err != nil {
   return fmt.Errorf("error writing record to file: %w", err)
  }
 }
 csvWriter.Flush()
 return csvWriter.Error()
}

步骤 2:验证逻辑的模糊测试

在模糊测试中,我们将重点关注 validateRecord 函数,该函数负责验证 CSV 数据的各个行。

package main

import (
 "strings"
 "testing"
)

// FuzzValidateRecord tests the validateRecord function with fuzzing.
func FuzzValidateRecord(f *testing.F) {
 // Seed corpus with examples, joined as single strings
 f.Add("123,validString")   // valid record
 f.Add("invalidInt,string"// invalid integer
 f.Add("123,")              // invalid string

 f.Fuzz(func(t *testing.T, recordStr string) {
  // Split the string back into a slice
  record := strings.Split(recordStr, ",")

  // Now you can call validateRecord with the slice
  _ = validateRecord(record)
  // Here you can add checks to verify the behavior of validateRecord
 })
}

运行模糊测试

要运行这个模糊测试,需要使用带有 -fuzz 标志的 go test 命令:

go test -fuzz=Fuzz

该命令将启动模糊处理过程,根据提供的种子自动生成和测试各种输入。

说明

  • validateAndSaveData 函数从 io.Reader读取数据,从而可以处理来自任何实现此接口的数据源(如文件或内存缓冲区)的数据。该函数通过 csv.Reader 解析 CSV 数据,使用 validateRecord 验证每条记录,并存储有效记录。
  • validateRecord 函数旨在根据简单的规则验证每条 CSV 记录:第一列必须可转换为整数,第二列必须是非空字符串。
  • saveValidData 函数获取经过验证的数据,并以 CSV 格式将其写入指定的输出文件。
  • validateRecord 的模糊测试使用种子输入来启动模糊处理过程,用大量生成的输入值来测试验证逻辑,以发现潜在的边缘情况或意外行为。
测试似乎一直在进行
fuzz: elapsed: 45s, execs: 7257 (0/sec), new interesting: 0 (total: 2)
fuzz: elapsed: 48s, execs: 7257 (0/sec), new interesting: 0 (total: 2)
fuzz: elapsed: 51s, execs: 7257 (0/sec), new interesting: 0 (total: 2)
fuzz: elapsed: 54s, execs: 7257 (0/sec), new interesting: 0 (total: 2)
fuzz: elapsed: 57s, execs: 7257 (0/sec), new interesting: 0 (total: 2)
fuzz: elapsed: 1m0s, execs: 7257 (0/sec), new interesting: 0 (total: 2)
fuzz: elapsed: 1m3s, execs: 7848 (197/sec), new interesting: 4 (total: 6)
fuzz: elapsed: 1m6s, execs: 9301 (484/sec), new interesting: 4 (total: 6)
fuzz: elapsed: 1m9s, execs: 11457 (718/sec), new interesting: 4 (total: 6)
fuzz: elapsed: 1m12s, execs: 14485 (1009/sec), new interesting: 4 (total: 6)
fuzz: elapsed: 1m15s, execs: 16927 (814/sec), new interesting: 4 (total: 6)

当模糊测试似乎无限期或长时间运行时,通常意味着它在不断生成和测试新的输入。模糊测试是一个密集的过程,会消耗大量时间和资源,尤其是当被测功能涉及复杂操作或模糊器发现许多"有趣"的输入,从而探索出新的代码路径时。

以下是可以采取的几个步骤,用于管理和减少长时间运行的模糊测试:

1.限制模糊测试时间

可以在运行模糊测试时使用 -fuzztime 标志来限制模糊测试的持续时间。例如,要使模糊测试最多运行 1 分钟,可以使用:

go test -fuzz=FuzzSaveDataHandler -fuzztime=1m
2.审查和优化测试代码

如果代码的某些部分特别慢或消耗资源,请考虑尽可能对其进行优化。由于模糊测试会产生大量请求,即使代码效率稍微低一点,也会被放大。

3.调整种子语料库

检查提供给模糊器的种子语料库,确保其多样性足以探索各种代码路径,但又不会过于宽泛,导致模糊器陷入过多路径。有时,过于通用的种子会导致模糊器在无益路径上花费过多时间。

4.监控"有趣的"输入

模糊器会报告覆盖新代码路径或触发独特行为的"有趣"输入。如果"有趣"输入的数量大幅增加,则可能表明模糊器正在不断发现新的探索场景。查看这些输入可以深入了解代码中的潜在边缘情况或意外行为。

5.分析模糊器性能

输出显示了每秒执行次数,可以让我们了解模糊器的运行效率。如果执行率很低,可能说明模糊器设置或被测代码存在性能瓶颈。调查并解决这些瓶颈有助于提高模糊器的效率。

6.考虑手动中断

如果模糊测试运行时间过长而没有提供额外价值(例如,没有发现新的有趣案例,或者已经从当前运行中获得了足够信息),可以手动停止该进程,然后查看迄今为止获得的结果,以决定下一步行动(例如调整模糊参数或调查已发现的案例)。


你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/435752.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hadoop配置日志的聚集——jobhistory不显示任务问题

问题&#xff1a; 一开始job history是正常的&#xff0c;配置了日志的聚集以后不管做什么任务都不显示任务&#xff0c;hdfs是正常运行&#xff0c;而且根据配置步骤都重启过了。 下面先po出日志聚集的操作步骤&#xff0c;再讲问题 1.配置yarn-site.xml cd $HADOOP_HOME/e…

0基础跨考408|一战上岸复盘及经验分享

基础阶段‼️ 王道的四本书的选择题部分要都做完、订正完。 王道的四门视频课要一轮刷完&#xff08;或者题主在B站看了其他的老师&#xff0c;这其实也是算一轮的&#xff0c;只要题主是认真学习了的&#xff0c;题主说自己不知道看什么课&#xff0c;王道就好了&#xff09;…

kibana配置 dashbord,做可视化展示

一、环境介绍 这里我使用的kibana版本为7.17版本。 语言选择为中文。 需要已经有es&#xff0c;已经有kibana&#xff0c;并且都能正常访问。 二、背景介绍 kibana的可视化界面&#xff0c;可以配置很多监控统计界面。非常方便&#xff0c;做数据的可视化展示。 这篇文章&…

【四】【SQL Server】如何运用SQL Server中查询设计器通关数据库期末查询大题

数据库学生选择1122 数据库展示 course表展示 SC表展示 student表展示 数据库学生选课1122_3 第十一题 第十二题 第十三题 第十四题 第十五题 数据库学生选课1122_4 第十六题 第十七题 第十八题 第十九题 第二十题 数据库学生选课1122_5 第二十一题 第二十二题 结尾 最后&…

恒驰上云规划实施解决方案上线华为云官网

华为云与伙伴共同打造联合解决方案 已成为更多企业的数字化转型利器 1月恒驰上云规划实施解决方案 完成上市宣讲并正式上架华为云官网 恒驰上云规划实施解决方案能力全景图&#xff1a;融合厂商云服务能力&#xff0c;一站式高效云迁移 从深入了解企业的本地IT环境、业务特点…

查看kafka消息消费堆积情况

查看主题命令 展示topic列表 ./kafka-topics.sh --list --zookeeper zookeeper_ip:2181描述topic ./kafka-topics.sh --describe --zookeeper zookeeper_ip:2181 --topic topic_name查看topic某分区偏移量最大&#xff08;小&#xff09;值 ./kafka-run-class.sh kafka.too…

Git——Upload your open store

0.default config ssh-keygen -t rsa #之后一路回车,当前目录.ssh/下产生公私钥 cat ~/.ssh/id_rsa.pub #复制公钥到账号 git config --global user.email account_email git config --global user.name account_name1. 上传一个公开仓库 查看当前分支&#xff1a; git branc…

JavaSE——基础小项目-模拟ATM系统(项目主要目标、技术选型、架构搭建、具体实现、完整代码注释)

目录 项目主要目标 技术选型 面向对象编程 使用集合容器 程序流程控制 使用常见API 系统架构搭建与欢迎页设计 Account ATM Test 用户开户功能实现 录入账户名称与性别 录入账户密码与取现额度 生成新卡号 存入账户 登录功能实现 登录后操作实现 退出账户 存…

python基础(11)《Allure报告中的组件用法》

使用 官方教程&#xff1a;https://docs.qameta.io/allure 入门 想要看到allure报告&#xff0c;需要做2个步骤&#xff1a; 1、pytest执行时关联allure&#xff1a;pytest命令带上--alluredir 结果存放目录或--alluredir结果存放目录&#xff1b; 2、打开执行报告&#xff…

通过勒索病毒攻击案例,思考勒索病毒攻击现象与趋势

前言 2019年针对企业的勒索病毒攻击越来越多&#xff0c;仿佛全球都在被勒索&#xff0c;基本上每天都会有关于勒索病毒攻击的案例被曝光&#xff0c;勒索病毒攻击已经成为全球最大的网络安全威胁&#xff0c;同时也被国际刑警组织认定为全球危害最大的网络犯罪组织活动&#…

nginx代理参数proxy_pass

proxy_pass参数用于配置反向代理&#xff0c;指定客户端请求被转发到后端服务器&#xff0c;后端地址可以是域名、ip端口URI 代理后端报错提示本地找不到CSS文件、JavaScript文件或图片 例如&#xff1a; nginx &#xff1a;10.1.74.109 后端服务&#xff1a;http://10.1.74.…

钡铼技术R40工业路由器连接智慧交通助力城市智慧化建设

随着信息技术与交通行业的深度融合&#xff0c;智慧交通作为智慧城市的重要组成部分&#xff0c;正在全球范围内加速推进。在此进程中&#xff0c;钡铼技术推出的R40工业路由器以其独特的4G WiFi一体化设计&#xff0c;成为连接智慧交通各环节&#xff0c;助力城市智慧化建设的…

C++小记 -链表

链表 文章目录 链表链表基础理论链表的类型单链表双链表循环链表 链表的存储方式链表的定义链表的操作添加节点删除节点 性能分析构建链表删除节点&#xff08;内存泄漏的坑&#xff09;1.直接移除2.使用虚拟头结点3.delete指针后&#xff0c;要将指针置为NULL&#xff01;&…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型

专属领域论文订阅 VX关注{晓理紫}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新论文。 》》 由于精力有限&#xff0c;今后就不在CSDN上更…

selenium-java 通过配置xml文件并发运行类或者方法

1、打开idea允许某个class类&#xff0c;可以在控制台看到运行路径的下的配置文件如下图&#xff1a; 2、将路径复制到本地路径中找到temp-testng-customsuite.xml文件 3、复制该文件到项目的根目录下&#xff0c;可以修改文件名称&#xff0c;如下图 4、如图所示&#xff0c;通…

37、VMware虚拟机分配的对外访问IP地址修改为静态

99、VMware虚拟机分配的对外访问IP地址修改为静态 一、前言二、修改IP分配策略1、打开网卡配置文件&#xff08;配置文件ens33表示所在主机网卡名称&#xff09;2、信息修改3、查看网卡ip4、刷新网络服务5、验证&#xff08;ping下百度看看网络是否正常&#xff09; 一、前言 …

【vue.js】文档解读【day 2】 | 计算属性

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 计算属性计算属性 vs 方法可计算属性Getter不应有副作用避免直接修改计算属性值 计算属性 我们已经学习…

[Redis]——数据一致性,先操作数据库,还是先更新缓存?

目录 一、操作缓存和数据库时有三个问题需要考虑&#xff1a; 1.删除缓存还是更新缓存&#xff1f; 2.如何保证缓存与数据库的操作同时成功或失效 3.先操作缓存还是先操作数据库&#xff08;多线程并发问题&#xff09; 二、 缓存更新的最佳策略 一、操作缓存和数据库时有…

el-table 插入单选并进行校验

<template><div><el-form :model"list" ref"ruleForm"><el-table :data"list.tableData" style"width: 100%"><el-table-column prop"time" label"日期" width"180"><…

深入理解swap交换分区理解及扩存

一、什么是swap交换分区? 定义&#xff1a;Swap space交换空间&#xff0c;是虚拟内存的表现形式。系统为了应付一些需要大量内存的应用&#xff0c;而将磁盘上的空间做内存使用&#xff0c;当物理内存不够用时&#xff0c;将其中一些暂时不需要的数据交换到交换空间&#xf…