在golang中测试一个函数。

huangapple go评论89阅读模式
英文:

Testing a function in golang

问题

所以我决定为我的 golang 函数编写测试。函数本身如下所示:

func Insert(slice []int, element int, index int) []int {
    n := len(slice)
    slice = slice[:(n + 1)]
    for i := index; i < n; i++ {
        slice[i+1] = slice[i]
    }
    slice[index] = element
    return slice
}

它接受一个切片,将其长度增加1,并在给定的索引处插入一个元素。现在,在调用它之前,我必须做两件事:

size := somevalue
array := make([]int, size, size+1)

然后,例如 array = Insert(array, 1, len(array)/2)。现在我编写了 insert_test.go

package sdizo

import "testing"

func benchmarkInsert(size int, b *testing.B) {    
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        array := make([]int, size, size+1)
        array = Insert(array, 1, len(array)/2)
    }
}
func BenchmarkInsert10k(b *testing.B) { benchmarkInsert(10000, b) }
func BenchmarkInsert20k(b *testing.B) { benchmarkInsert(20000, b) }
func BenchmarkInsert30k(b *testing.B) { benchmarkInsert(30000, b) }
func BenchmarkInsert40k(b *testing.B) { benchmarkInsert(40000, b) }
func BenchmarkInsert50k(b *testing.B) { benchmarkInsert(50000, b) }

问题是,在测试循环中有两个操作。我不能将 make() 移到循环上面,因为每次尝试插入时都必须创建一个新的数组(不想干扰容量)。它可以工作,它给我输出,但我想知道这个 make() 是否会影响时间测量,并且我想问你是否可以以某种方式仅测量 Insert() 的时间。

附注:是否有一种方便的方法将基准测试的结果写入文件?

英文:

So I've decided to write test for my function in golang. The function itself looks like this:

func Insert(slice []int, element int, index int) []int {
	n := len(slice)
	slice = slice[:(n + 1)]
	for i := index; i &lt; n; i++ {
		slice[i+1] = slice[i]
	}
	slice[index] = element
	return slice
} 

So it takes a slice, extends its length by 1 and inserts an element at given index. Now before I call it I have to do 2 things:

size := somevalue
array := make([]int, size, size+1)

and then f.e array = Insert(array, 1, len(array)/2. Now I wrote insert_test.go:

package sdizo

import &quot;testing&quot;

func benchmarkInsert(size int, b *testing.B) {	
	b.ResetTimer()
	for n := 0; n &lt; b.N; n++ {
		array := make([]int, size, size+1)
		array = Insert(array, 1, len(array)/2)
	}
}
func BenchmarkInsert10k(b *testing.B) { benchmarkInsert(10000, b) }
func BenchmarkInsert20k(b *testing.B) { benchmarkInsert(20000, b) }
func BenchmarkInsert30k(b *testing.B) { benchmarkInsert(30000, b) }
func BenchmarkInsert40k(b *testing.B) { benchmarkInsert(40000, b) }
func BenchmarkInsert50k(b *testing.B) { benchmarkInsert(50000, b) }

The thing is, there are 2 operations in testing loop. I cannot move the make() above the loop, cuz it has to make a new array everytime it tries to insert something to it (don't want to mess with capacity). It works, it gives me output, but I am curious if this make() doesn't mess up with time measurement, and I would like to ask you if I can somehow measure the time only for Insert()

P.S Is there a convenient way to write down the results of benchmark test to a file?

答案1

得分: 1

编辑:你所寻求的真正答案。(终于)

当你进行基准测试时,确保只测试你想要测试的内容。你的使用情况不会每次都进行创建和插入,所以只需创建一次,然后在循环中测试插入操作。重点是只测试瓶颈部分。

在这里,你只需要测试Insert操作。你可以通过每次调整数组大小来实现,这个操作的成本基本上可以忽略不计(至少与makeInsert相比)。以下是示例代码:

func benchmarkInsert(size int, b *testing.B) { 
    array := make([]int, size, size+1) 
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        array = Insert(array, 1, len(array)/2)
        array = array[0:size]
    }
}

要解释为什么这是最小化的,你必须意识到,在深层次上,切片本质上是一个类似于以下结构的结构体:

type Slice struct {
    contents []interface{}
    length   int
    capacity int
}

使用array = array[0:size]来调整大小的操作类似于将结构体的length设置为size。详细信息请参阅:https://blog.golang.org/go-slices-usage-and-internals

至于将结果写入文件,这取决于你的操作系统。在任何Unix系统上,只需在基准测试命令的末尾添加> file.txt即可将结果导入到file.txt中。关于Windows系统我不太确定。

编辑:添加更多的测试结果显示这是线性扩展的。

BenchmarkInsert10k-8     	  200000	      7889 ns/op
BenchmarkInsert20k-8     	  100000	     16131 ns/op
BenchmarkInsert30k-8     	  100000	     24184 ns/op
BenchmarkInsert40k-8     	   50000	     31767 ns/op
BenchmarkInsert50k-8     	   50000	     39721 ns/op
BenchmarkInsert100k-8    	   20000	     79711 ns/op
BenchmarkInsert200k-8    	   10000	    159411 ns/op
BenchmarkInsert300k-8    	    5000	    237188 ns/op
BenchmarkInsert400k-8    	    5000	    316270 ns/op
BenchmarkInsert500k-8    	    3000	    399146 ns/op
BenchmarkInsert1000k-8   	    2000	    793845 ns/op

使用以下代码进行测试:https://play.golang.org/p/6fWHwpzUJE

英文:

EDIT: The real answer you seek. (Finally)

When you benchmark, actually benchmark what you want to benchmark. Your use case isn't going to be a make, then insert every time, so just make once, then test insert in the loop. The point is to test ONLY the chokepoint.

What you want to do here is just test Insert. You can do this by simply resizing the array each time, the cost of which is basically nothing (at least in comparison to make or Insert).

func benchmarkInsert(size int, b *testing.B) { 
    array := make([]int, size, size+1) 
    b.ResetTimer()
    for n := 0; n &lt; b.N; n++ {
        array = Insert(array, 1, len(array)/2)
        array = array[0:size]
    }
}

To explain why it is minimal, you have to realize that deep down, a slice is essentially a struct that looks something like this:

type Slice struct {
    contents []interface{}
    length   int
    capacity int
}

Performing the operation to resize it using array = array[0:size] is analogous to setting the struct's length=size. See https://blog.golang.org/go-slices-usage-and-internals

As far as writing the results to file, that depends on your operating system. On any Unix system, simply running the benchmark command with &gt; file.txt at the end will pipe the results to file.txt. Not sure about Windows.

EDIT: Adding more tests shows this scales linearly.

BenchmarkInsert10k-8     	  200000	      7889 ns/op
BenchmarkInsert20k-8     	  100000	     16131 ns/op
BenchmarkInsert30k-8     	  100000	     24184 ns/op
BenchmarkInsert40k-8     	   50000	     31767 ns/op
BenchmarkInsert50k-8     	   50000	     39721 ns/op
BenchmarkInsert100k-8    	   20000	     79711 ns/op
BenchmarkInsert200k-8    	   10000	    159411 ns/op
BenchmarkInsert300k-8    	    5000	    237188 ns/op
BenchmarkInsert400k-8    	    5000	    316270 ns/op
BenchmarkInsert500k-8    	    3000	    399146 ns/op
BenchmarkInsert1000k-8   	    2000	    793845 ns/op

Using this code: https://play.golang.org/p/6fWHwpzUJE

答案2

得分: 1

例如(从Go 1.7开始),使用您的Insert算法:

package sdizo

import (
	"strconv"
	"testing"
)

func Insert(slice []int, element int, index int) []int {
	n := len(slice)
	slice = slice[:(n + 1)]
	for i := index; i < n; i++ {
		slice[i+1] = slice[i]
	}
	slice[index] = element
	return slice
}

func BenchmarkInsert(b *testing.B) {
	for size := 10000; size <= 50000; size += 10000 {

		b.Run(strconv.Itoa(size/1000)+"k",

			func(b *testing.B) {
				a := make([]int, size, size+1)
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					a = a[:size]
					a = Insert(a, 1, len(a)/2)
				}
				b.StopTimer()
			},
		)
	}
}

输出:

$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4         50000     32502 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/20k-4         20000     64364 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/30k-4         20000     97310 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/40k-4         10000    129196 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/50k-4         10000    161427 ns/op   0 B/op   0 allocs/op
PASS
ok   so/sdizo 9.778s
$ 

现在,我们可以对您的`Insert`算法进行基准测试,然后利用这些知识来改进算法。例如:

```go
package sdizo

import (
	"strconv"
	"testing"
)

func Insert(slice []int, element int, index int) []int {
	slice = slice[:len(slice)+1]
	copy(slice[index+1:], slice[index:])
	slice[index] = element
	return slice
}

func BenchmarkInsert(b *testing.B) {
	for size := 10000; size <= 50000; size += 10000 {

		b.Run(strconv.Itoa(size/1000)+"k",

			func(b *testing.B) {
				a := make([]int, size, size+1)
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i < b.N; i++ {
					a = a[:size]
					a = Insert(a, 1, len(a)/2)
				}
				b.StopTimer()
			},
		)
	}
}

输出:

$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4        200000      7664 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/20k-4        100000     15208 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/30k-4        100000     22959 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/40k-4         50000     35181 ns/op   0 B/op   0 allocs/op
BenchmarkInsert/50k-4         50000     39658 ns/op   0 B/op   0 allocs/op
PASS
ok   so/sdizo 10.331s
$ 
英文:

For example (from Go 1.7 forward), using your Insert algorithm,

package sdizo

import (
	&quot;strconv&quot;
	&quot;testing&quot;
)

func Insert(slice []int, element int, index int) []int {
	n := len(slice)
	slice = slice[:(n + 1)]
	for i := index; i &lt; n; i++ {
		slice[i+1] = slice[i]
	}
	slice[index] = element
	return slice
}

func BenchmarkInsert(b *testing.B) {
	for size := 10000; size &lt;= 50000; size += 10000 {

		b.Run(strconv.Itoa(size/1000)+&quot;k&quot;,

			func(b *testing.B) {
				a := make([]int, size, size+1)
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i &lt; b.N; i++ {
					a = a[:size]
					a = Insert(a, 1, len(a)/2)
				}
				b.StopTimer()
			},
		)
	}
}

Output:

$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4         50000	     32502 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/20k-4         20000	     64364 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/30k-4         20000	     97310 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/40k-4         10000	    129196 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/50k-4         10000	    161427 ns/op	   0 B/op	   0 allocs/op
PASS
ok  	so/sdizo	9.778s
$ 

Now that we can benchmark your Insert algorithm, we can use that knowledge to improve the algorithm. For example,

package sdizo

import (
	&quot;strconv&quot;
	&quot;testing&quot;
)

func Insert(slice []int, element int, index int) []int {
	slice = slice[:len(slice)+1]
	copy(slice[index+1:], slice[index:])
	slice[index] = element
	return slice
}

func BenchmarkInsert(b *testing.B) {
	for size := 10000; size &lt;= 50000; size += 10000 {

		b.Run(strconv.Itoa(size/1000)+&quot;k&quot;,

			func(b *testing.B) {
				a := make([]int, size, size+1)
				b.ReportAllocs()
				b.ResetTimer()
				for i := 0; i &lt; b.N; i++ {
					a = a[:size]
					a = Insert(a, 1, len(a)/2)
				}
				b.StopTimer()
			},
		)
	}
}

Output:

$ go test -bench=.
goos: linux
goarch: amd64
pkg: sdizo
BenchmarkInsert/10k-4        200000	      7664 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/20k-4        100000	     15208 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/30k-4        100000	     22959 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/40k-4         50000	     35181 ns/op	   0 B/op	   0 allocs/op
BenchmarkInsert/50k-4         50000	     39658 ns/op	   0 B/op	   0 allocs/op
PASS
ok  	so/sdizo	10.331s
$

huangapple
  • 本文由 发表于 2017年3月26日 08:47:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/43023702.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定