文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

关于 Golang 的模糊测试实践

2024-11-28 16:55

关注

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 := 0, len(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 函数的模糊测试

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

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

解释:

构建用于数据持久化的 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.
 })
}

解释:

执行模糊测试:

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

在 Go 中验证和存储 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

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

说明:

测试似乎一直在进行

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.考虑手动中断

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

来源:DeepNoMind内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯