首页 > 其他分享 >Go - Generating Random Test Inputs for Tests

Go - Generating Random Test Inputs for Tests

时间:2023-10-18 18:55:08浏览次数:35  
标签:Inputs Generating elements Random FuzzHeap fuzz PASS test seed

Problem: You want to generate random test data for running your test functions.

 

Solution: Use fuzzing , which is an automated testing technique to generate random test data for your test functions.

 

Fuzzing , or fuzz testing, is an automated testing technique that generates random, unexpected data for your program in order to detect bugs. Fuzzing has been around for quite a while; the first paper on fuzzing was published in 1990. Go has had fuzzing libraries for a while as well, but in Go 1.18, fuzzing was added as a feature. The feature was added as part of the go test tool as well as the standard library.

You can use fuzzing to test the max heap implementation:

type Heap struct {
    elements []int
}

func (h *Heap) Push(ele int) {
    h.elements = append(h.elements, ele)
    i := len(h.elements) - 1
    for ; h.elements[i] > h.elements[parent(i)]; i = parent(i) {
        h.swap(i, parent(i))
    }
}

func (h *Heap) Pop() (ele int) {
    ele = h.elements[0]
    h.elements[0] = h.elements[len(h.elements)-1]
    h.elements = h.elements[:len(h.elements)-1]
    h.rearrange(0)
    return
}

func (h *Heap) rearrange(i int) {
    largest := i
    left, right, size := leftChild(i), rightChild(i), len(h.elements)
    if left < size && h.elements[left] > h.elements[largest] {
        largest = left
    }
    if right < size && h.elements[right] > h.elements[largest] {
        largest = right
    }
    if largest != i {
        h.swap(i, largest)
        h.rearrange(largest)
    }
}

Fuzzing is useful because it automates input data into your test functions such that it tests unexpected cases. If you were to test the max heap implementation discussed earlier, this is a typical test function you might write, which will test the Push and Pop functions:

func TestHeap(t *testing.T) {
    var h *Heap = &Heap{}
    h.elements = []int{452, 23, 6515, 55, 313, 6}
    h.Build()
    testCases := []int{51, 634, 9, 8941, 354}

    for _, tc := range testCases {
        h.Push(tc)
        //  make  a  copy  of  the  elements  in  the  slice  and  sort  it  in
        //  descending  order
        elements := make([]int, len(h.elements))
        copy(elements, h.elements)
        sort.Slice(elements, func(i, j int) bool {
            return elements[i] > elements[j]
        })

        //  pop  the  heap  and  check  if  the  top  of  heap  is  the  largest
        //  element
        popped := h.Pop()
        if elements[0] != popped {
            t.Errorf("Top  of  heap  %d  is  not  the  one  popped  %d\n  heap is  %v",
                elements[0], popped, elements)
        }
    }
}

First, create a max heap and prepopulate the heap with data. Next, use a set of test cases (which are just a bunch of integers), and push them into the heap. You want to pop the heap, which will give you the largest integer in the heap.

To check if this is the case, take the slice of elements that is the data for the heap and sort it in descending order. The first element of the slice is the largest integer and should be the same as the integer you get from popping the heap.

When you run the test function with these test cases, everything works fine:
% go test - run=TestHeap - v

=== RUN TestHeap

- - - PASS: TestHeap (0.00s)

PASS

ok github.com/sausheong/gocookbook/ch18_testing 0.229s

As you can see, you test only with this input data into the heap. This is where fuzzing comes in. In Go 1.18, fuzzing was introduced in the go test toolset. Fuzz tests are added as fuzz functions in the same _test.go files you use for the test functions.

Each fuzz function must start with Fuzz , similar to how test functions start with Test ; and each takes only one parameter, which is a pointer to testing.F .

There are two parts to creating a fuzz function:
• Seeding the input to the fuzz function using the f.Add function.
• Running the fuzz test itself by calling the f.Fuzz function and passing it a fuzz target , which is a function that has a pointer to the testing.T parameter, as well as a set of fuzzing arguments .

Take a look at how you can convert your test function to a fuzz function:

func FuzzHeap(f *testing.F) {
    var h *Heap = &Heap{}
    h.elements = []int{452, 23, 6515, 55, 313, 6}
    h.Build()
    testCases := []int{51, 634, 9, 8941, 354}
    for _, tc := range testCases {
        f.Add(tc)
    }
    f.Fuzz(func(t *testing.T, i int) {
        h.Push(i)
        //  make  a  copy  of  the  elements  in  the  slice  and  sort  it  in
        //  descending  order
        elements := make([]int, len(h.elements))
        copy(elements, h.elements)
        sort.Slice(elements, func(i, j int) bool {
            return elements[i] > elements[j]
        })
        //  pop  the  heap  and  check  if  the  top  of  heap  is  the  largest
        //  element
        popped := h.Pop()
        if elements[0] != popped {
            t.Errorf("Top  of  heap  %d  is  not  the  one  popped  %d\n  heap is  %v", elements[0], popped, elements)
        }
    })
}

You create a function named FuzzHeap that accepts a pointer to testing.F . In this function, you start off by setting up the max heap as before. Then you take the test cases and add them to the seed corpus , the collection of seed input for the fuzz tests, using f.Add .

The fuzz target has a pointer testing.T as well as a single integer. The fuzzing arguments must be the same and also in the same sequence as the parameters you pass into f.Add as you register the inputs into the seed corpus. In your fuzz function, you pass a single integer into the f.Add function, so you will have only a single integer as the fuzzing argument.

The fuzz target body is the same as the earlier test function, and you’re done! Run it!

To run a fuzz function, you need to use the - fuzz flag, passing it a part of the function name (or simply a period to indicate everything). You can also pass in a - fuzztime parameter to indicate how long you want to run the fuzz function, because fuzz functions will run forever if they can’t find any bugs!
% go test - v - fuzz=Heap - fuzztime=30s

=== RUN TestHeap

- - - PASS: TestHeap (0.00s)

=== FUZZ FuzzHeap

fuzz: elapsed: 0s, gathering baseline coverage: 0/1484 completed

fuzz: elapsed: 0s, gathering baseline coverage: 1484/1484 completed, now fuzzing

with 10 workers

fuzz: elapsed: 3s, execs: 692916 (230887/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 6s, execs: 1343416 (216901/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 9s, execs: 2078265 (244901/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 12s, execs: 2827429 (249737/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 15s, execs: 3527717 (233462/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 18s, execs: 4256457 (242874/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 21s, execs: 5014656 (252735/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 24s, execs: 5757659 (247697/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 27s, execs: 6447953 (230105/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 30s, execs: 7175096 (242388/sec), new interesting: 1 (total: 1485)

fuzz: elapsed: 30s, execs: 7175096 (0/sec), new interesting: 1 (total: 1485)

- - - PASS: FuzzHeap (30.30s)

PASS

ok github.com/sausheong/gocookbook/ch18_testing 30.935s

The first line indicates that the baseline coverage is gathered by executing the test with the seed corpus and the generated corpus before fuzzing begins. If the test doesn’t work in the first place, there’s no point doing fuzzing.

The number of workers indicates how many fuzz targets are run in parallel. You can actually specify this using the -parallel flag, but if you leave it empty, it will use GOMAXPROCS , which by default is the number of cores available.

In the following lines, elapsed shows how long the fuzzing has been running, execs shows the total number of inputs that have been run against the fuzz target, while new interesting shows how many inputs have expanded the code coverage beyond existing corpora, with the size of the entire corpus.

The fuzz function itself can be run as a normal test function with the seed corpus. If you run it with go test as you would any test function, you should get these results:
% go test - run=FuzzHeap - v

=== RUN FuzzHeap

=== RUN FuzzHeap/seed#0

=== RUN FuzzHeap/seed#1

=== RUN FuzzHeap/seed#2

=== RUN FuzzHeap/seed#3

=== RUN FuzzHeap/seed#4

- - - PASS: FuzzHeap (0.00s)

- - - PASS: FuzzHeap/seed#0 (0.00s)

- - - PASS: FuzzHeap/seed#1 (0.00s)

- - - PASS: FuzzHeap/seed#2 (0.00s)

- - - PASS: FuzzHeap/seed#3 (0.00s)

- - - PASS: FuzzHeap/seed#4 (0.00s)

PASS

ok github.com/sausheong/gocookbook/ch18_testing 0.246s

As you can see, you have five runs of the fuzz target against the five seed inputs in the seed corpus, and all of them pass.

This is all good, but it doesn’t really show how fuzzing helps make the software more robust. A simple example can show this. Change your rearrange function a bit. Instead of comparing h.elements[left] you compare h.elements[left - 1] . It’s a small change that can result in an error, and it can easily go undetected:

func (h *Heap) rearrange(i int) {
    ...
    if left < size && h.elements[left-1] > h.elements[largest] {
        largest = left
    }
    ...
}

To prove this, run it against your TestHeap test function. You should see that it runs perfectly well and the test case passes. Now run it against the FuzzHeap fuzz function:
% go test - v - fuzz=Heap - fuzztime=30s

=== RUN TestHeap

- - - PASS: TestHeap (0.00s)

=== FUZZ FuzzHeap

fuzz: elapsed: 0s, gathering baseline coverage: 0/1484 completed

fuzz: elapsed: 0s, gathering baseline coverage: 19/1484 completed

- - - FAIL: FuzzHeap (0.28s)

- - - FAIL: FuzzHeap (0.00s)

testing_test.go:260: Top of heap 313 is not the one popped 158

heap is [313 158 55 23 6 - 327 - 349]

 

Failing input written to testdata/fuzz/FuzzHeap/03b1c861389a9c041082690dc8b

25528f6ff6debab2a7fc99524a738895bea1f

To re - run:

go test - run=FuzzHeap/03b1c861389a9c041082690dc8b25528f6ff6debab2a7fc99524a

738895bea1f

FAIL

exit status 1

FAIL github.com/sausheong/gocookbook/ch18_testing 0.540s

As you can see, it fails at the baseline coverage, and the element that was popped from the heap wasn’t the maximum. You can also see that the input to the failed test case is written to a test data file. If you open it, you should see something like this:
go test fuzz v1

int( - 349)

And if you run the FuzzHeap function as a normal test function, you will immediately see that the other test cases pass with the other input, but with - 349 the max heap doesn’t work any more:
% go test - run=FuzzHeap - v

=== RUN FuzzHeap

=== RUN FuzzHeap/seed#0

=== RUN FuzzHeap/seed#1

=== RUN FuzzHeap/seed#2

=== RUN FuzzHeap/seed#3

=== RUN FuzzHeap/seed#4

=== RUN FuzzHeap/03363930589906b56680eea723dd29e2744bd87e28b0995dd65209094

ef3080d

testing_test.go:260: Top of heap 313 is not the one popped 51

heap is [313 55 51 48 23 9 6]

- - - FAIL: FuzzHeap (0.00s)

- - - PASS: FuzzHeap/seed#0 (0.00s)

- - - PASS: FuzzHeap/seed#1 (0.00s)

- - - PASS: FuzzHeap/seed#2 (0.00s)

- - - PASS: FuzzHeap/seed#3 (0.00s)

- - - PASS: FuzzHeap/seed#4 (0.00s)

- - - FAIL: FuzzHeap/03363930589906b56680eea723dd29e2744bd87e28b0995dd65209094

ef3080d (0.00s)

FAIL

exit status 1

FAIL github.com/sausheong/gocookbook/ch18_testing 0.475s
You can imagine this can be pretty hard to detect! If you fix the code, you can run the same FuzzHeap test again and see that it has passed all the tests, including a regression one that was automatically generated from a failed fuzz test:
% go test - run=FuzzHeap - v

=== RUN FuzzHeap

=== RUN FuzzHeap/seed#0

=== RUN FuzzHeap/seed#1

=== RUN FuzzHeap/seed#2

=== RUN FuzzHeap/seed#3

=== RUN FuzzHeap/seed#4

=== RUN FuzzHeap/03b1c861389a9c041082690dc8b25528f6ff6debab2a7fc99524a

738895bea1f

- - - PASS: FuzzHeap (0.00s)

- - - PASS: FuzzHeap/seed#0 (0.00s)

- - - PASS: FuzzHeap/seed#1 (0.00s)

- - - PASS: FuzzHeap/seed#2 (0.00s)

- - - PASS: FuzzHeap/seed#3 (0.00s)

- - - PASS: FuzzHeap/seed#4 (0.00s)

- - - PASS: FuzzHeap/03b1c861389a9c041082690dc8b25528f6ff6debab2a7fc99524a

738895bea1f (0.00s)

PASS

ok github.com/sausheong/gocookbook/ch18_testing 0.283s

Fuzzing is a powerful tool. However, it can be pretty expensive to run, especially in an automated continuous integration pipeline, since it can be CPU intensive.

 

标签:Inputs,Generating,elements,Random,FuzzHeap,fuzz,PASS,test,seed
From: https://www.cnblogs.com/zhangzhihui/p/17773045.html

相关文章

  • Math.random() 用法
    Math.random()可以随机产生一个[0,1)(左闭右开)之间的随机数double类型intrandom=(int)(Math.random()*10)   随机产生0-9之间的数字,包括0和9Math.random()*(n-m)+m     随机产生n-m之间的数字包括m不包nMath.random()*(n+1-m)+m  随机产生n......
  • C语言数据类型占用字节大小+rand_mode/randomize_mode/static constraint+I2C和SPI的
    C语言数据类型占用字节大小https://blog.csdn.net/sinan1995/article/details/79577106对于整形,最大8字节,超出8字节的计算,要么用库,要么不用。64位编译器:char/unsignedchar:1字节char*:8字节shortint:2字节int/unsignedint:4字节longint:8字节float:4字节double:8字节lon......
  • InputStream类的read()方法返回的int值是如何计算的
    InputStream类有一个read()方法,它的返回类型是int。InputStream类本身是抽象类,它的一些子类的read()方法每次读取一个字节,也就是8个二进制位。比如读到如下二进制数据:111111111以上二进制数据如果按照byte类型来转换,是负数-1。而read()方法会把它先变成32位的二进制数据:000000000......
  • R语言随机森林RandomForest、逻辑回归Logisitc预测心脏病数据和可视化分析|附代码数据
    全文链接:http://tecdat.cn/?p=22596最近我们被客户要求撰写关于预测心脏病的研究报告,包括一些图形和统计输出。本报告是对心脏研究的机器学习/数据科学调查分析。更具体地说,我们的目标是在心脏研究的数据集上建立一些预测模型,并建立探索性和建模方法。但什么是心脏研究?研究大纲......
  • 在java中将InputStream对象转换为File对象(不生成本地文件)
    importorg.apache.commons.io.IOUtils;importjava.io.File;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.InputStream;publicclassStreamUtil{staticfinalStringPREFIX="stream2file";//前缀字符串定义文件名;必须至少三个字符......
  • java中如何对特大文件做断点续传RandomAccessFile
    Java中可以使用 RandomAccessFile 类来实现特大文件的断点续传功能。importjava.io.File;importjava.io.IOException;importjava.io.RandomAccessFile;importjava.net.URL;importjava.net.HttpURLConnection;publicclassResumeDownloadExample{publicstaticvoi......
  • sv的LSB 使用+SV的protect类型+RAL模型的lock原因+C语言结构体中的冒号用法+uvm版本在
    sv的LSB使用https://blog.csdn.net/gsjthxy/article/details/90722378等价关系[LSB+:STEP]=[LSB+STEP:LSB]伪代码:bit[1023:0]mem;bit[7:0]data;j=0..100mem[j*8+:8]=data;//[7:0],[15:8],[23:16]SV的protect类型https://blog.csdn.net/qq_37573794/ar......
  • 随机文件访问RandomAccessFile
    RandomAccessFile 是Java标准库中提供的一个文件访问类,可以用于读取和写入文件。与其他输入/输出流不同,RandomAccessFile 允许直接访问文件的任意位置,可以在文件中随机定位读写数据。 为什么使用它而不是传统的IO流: 1.RandomAccessFile 允许直接跳转到文件的任意位置进......
  • Python模块之 random
    作用:random.randint函数是Python内置的随机数生成函数之一,用于生成一个指定范围内的整数。必要操作:>>>importrandom安装:python内置函数,无需安装导入包:>>>importrandom帮助查看:>>>help(random)或单独查看某个子方法(函数)>>>help(random.randint)方......
  • random模块os模块
    random模块os模块random模块importrandom#print(random.random())#o-1的小数0.654381741577838#print(random.uniform(1,3))#大于1小于32.1890586235082763#print(random.randint(1,10))#31-10的整数随机#print(random.randrange(0,30,2))#8偶数随机#print(random.r......