Testing
Bagi developer, testing yang baik dapat menemukan error dalam program lebih awal, menghindari beban mental yang disebabkan oleh Bug karena tidak tepat waktu maintenance, jadi menulis testing dengan baik sangat perlu. Go menyediakan tool command line go test yang sangat mudah digunakan dan praktis dalam aspek testing, dapat melihat testing di standard library dan banyak framework open source, tool ini sangat mudah digunakan, saat ini mendukung beberapa jenis testing berikut:
- Example testing
- Unit testing
- Benchmark testing
- Fuzz testing
Sebagian besar API di Go disediakan oleh paket standar testing.
TIP
Jalankan command go help testfunc di command line, dapat melihat penjelasan官方 Go untuk empat jenis testing di atas.
Menulis Spesifikasi
Sebelum mulai menulis testing, pertama perlu memperhatikan beberapa spesifikasi, ini akan lebih mudah untuk pembelajaran selanjutnya.
- Paket testing: File testing sebaiknya terpisah di paket sendiri, paket ini biasanya dinamai
test. - File testing: File testing biasanya berakhiran
_test.go, misalnya jika ingin menguji suatu fungsi, namaifunction_test.go, jika ingin membagi lebih detail menurut jenis testing juga dapat menggunakan jenis testing sebagai prefix file, misalnyabenchmark_marshaling_test.go, atauexample_marshaling_test.go. - Fungsi testing: Setiap file testing akan memiliki beberapa fungsi testing untuk testing yang berbeda. Untuk jenis testing yang berbeda, gaya penamaan fungsi testing juga berbeda. Misalnya example testing adalah
ExampleXXXX, unit testing adalahTestXXXX, benchmark testing adalahBenchmarkXXXX, fuzz testing adalahFuzzXXXX, dengan demikian meskipun tanpa komentar juga dapat mengetahui ini adalah jenis testing apa.
TIP
Saat nama paket adalah testdata, paket ini biasanya untuk menyimpan data auxiliary untuk testing, saat menjalankan testing, Go akan mengabaikan paket bernama testdata.
Mengikuti spesifikasi di atas, membiasakan gaya testing yang baik, dapat menghemat banyak trouble untuk maintenance di masa depan.
Menjalankan Testing
Menjalankan testing terutama menggunakan command go test, berikut menggunakan kode aktual sebagai contoh, sekarang ada file yang akan diuji /say/hello.go kode sebagai berikut
package say
import "fmt"
func Hello() {
fmt.Println("hello")
}
func GoodBye() {
fmt.Println("bye")
}Dan file testing /test/example_test.go kode sebagai berikut
package test
import (
"golearn/say"
)
func ExampleHello() {
say.Hello()
// Output:
// hello
}
func ExampleGoodBye() {
say.GoodBye()
// Output:
// bye
}
func ExampleSay() {
say.Hello()
say.GoodBye()
// Output:
// hello
// bye
}Ada beberapa cara untuk menjalankan testing ini, misalnya ingin menjalankan semua test case di paket test, dapat langsung jalankan command berikut di direktori test
$ go test ./
PASS
ok golearn/test 0.422s./表示 direktori saat ini, Go akan mengkompilasi ulang semua file testing di direktori test, lalu menjalankan semua test case, dari hasil dapat dilihat semua test case lulus. Parameter setelahnya juga dapat mengikuti beberapa direktori, misalnya command di bawah, jelas direktori utama proyek tidak ada file testing untuk dijalankan.
$ go test ./ ../
ok golearn/test
? golearn [no test files]TIP
Saat parameter yang dijalankan adalah beberapa paket, Go tidak akan menjalankan ulang test case yang sudah berhasil lulus, saat menjalankan akan menambahkan (cached) di akhir baris untuk表示 output hasil adalah cache dari sebelumnya. Saat flag parameter testing berada di集合 berikut, Go akan cache hasil testing, jika tidak maka tidak.
-benchtime, -cpu,-list, -parallel, -run, -short, -timeout, -failfast, -vJika ingin menonaktifkan cache, dapat menambahkan parameter -count=1.
Tentu juga dapat menentukan file testing tertentu untuk dijalankan.
$ go test example_test.go
ok command-line-arguments 0.457sAtau dapat menentukan test case tertentu dari file testing tertentu, misalnya
$ go test -run ExampleSay
PASS
ok golearn/test 0.038sKetiga situasi di atas meskipun menyelesaikan testing, tetapi hasil output terlalu ringkas, saat ini dapat menambahkan parameter -v, untuk membuat hasil output lebih detail, misalnya
$ go test ./ -v
=== RUN ExampleHello
--- PASS: ExampleHello (0.00s)
=== RUN ExampleGoodBye
--- PASS: ExampleGoodBye (0.00s)
=== RUN ExampleSay
--- PASS: ExampleSay (0.00s)
PASS
ok golearn/test 0.040sSekarang dapat dengan jelas melihat urutan eksekusi setiap test case, waktu yang dikonsumsi, situasi eksekusi, dan total waktu yang dikonsumsi.
TIP
Command go test secara default menjalankan semua unit testing, example testing, fuzz testing, jika menambahkan parameter -bench maka akan menjalankan semua jenis testing, misalnya command di bawah
$ go test -bench .Jadi perlu menggunakan parameter -run untuk menentukan, misalnya hanya menjalankan semua benchmark testing command sebagai berikut
$ go test -bench . -run ^$Parameter Umum
Go testing memiliki sangat banyak flag parameter, di bawah hanya akan memperkenalkan parameter yang umum digunakan, ingin了解 lebih banyak detail disarankan menggunakan command go help testflag untuk查阅 sendiri.
| Parameter | Penjelasan |
|---|---|
-o file | Menentukan nama file biner setelah kompilasi |
-c | Hanya mengkompilasi file testing, tetapi tidak menjalankan |
-json | Output log testing dalam format json |
-exec xprog | Menjalankan testing menggunakan xprog, setara dengan go run |
-bench regexp | Memilih benchmark testing yang cocok dengan regexp |
-fuzz regexp | Memilih fuzz testing yang cocok dengan regexp |
-fuzztime t | Waktu otomatis结束 fuzz testing, t adalah interval waktu, saat satuan x,表示 jumlah, misalnya 200x |
-fuzzminimizetime t | Waktu minimum mode testing berjalan, aturan sama di atas |
-count n | Menjalankan testing n kali, default 1 kali |
-cover | Mengaktifkan analisis coverage testing |
-covermode set,count,atomic | Menset mode analisis coverage |
-cpu | Untuk testing menjalankan GOMAXPROCS |
-failfast | Setelah testing gagal pertama kali, tidak akan memulai testing baru |
-list regexp | Mendaftar test case yang cocok dengan regexp |
-parallel n | Mengizinkan test case yang memanggil t.Parallel berjalan paralel, n adalah jumlah maksimum paralel |
-run regexp | Hanya menjalankan test case yang cocok dengan regexp |
-skip regexp | Melewati test case yang cocok dengan regexp |
-timeout d | Jika waktu eksekusi single testing melebihi interval waktu d, akan panic. d adalah interval waktu, misalnya 1s,1ms,1ns dll |
-shuffle off,on,N | Mengacak urutan eksekusi testing, N adalah seed random, default seed adalah waktu sistem |
-v | Output log testing lebih detail |
-benchmem | Statistik alokasi memori benchmark testing |
-blockprofile block.out | Statistik situasi blocking goroutine dalam testing dan menulis ke file |
-blockprofilerate n | Mengontrol frekuensi statistik blocking goroutine, melalui command go doc runtime.SetBlockProfileRate lihat lebih banyak detail |
-coverprofile cover.out | Statistik situasi coverage testing dan menulis ke file |
-cpuprofile cpu.out | Statistik situasi cpu dan menulis ke file |
-memprofile mem.out | Statistik situasi alokasi memori dan menulis ke file |
-memprofilerate n | Mengontrol frekuensi statistik alokasi memori, melalui command go doc runtime.MemProfileRate lihat lebih banyak detail |
-mutexprofile mutex.out | Statistik situasi lock contention dan menulis ke file |
-mutexprofilefraction n | Menset statistik n goroutine contend satu mutex lock |
-trace trace.out | Menulis situasi execution tracking ke file |
-outputdir directory | Menentukan direktori output file statistik di atas, default adalah direktori running go test |
Example Testing
Example testing tidak seperti tiga jenis testing lainnya untuk menemukan masalah program, ia lebih untuk menampilkan cara penggunaan suatu fungsi, berperan sebagai dokumentasi. Example testing bukan konsep yang didefinisikan官方, juga bukan spesifikasi yang keras, lebih seperti konvensi dalam engineering, apakah mengikuti hanya tergantung pada developer. Example testing muncul sangat banyak di standard library, biasanya adalah contoh kode standard library yang ditulis官方, misalnya fungsi testing ExampleWithDeadline di context/example_test.go standard library, fungsi ini menampilkan cara penggunaan dasar DeadlineContext:
// This example passes a context with an arbitrary deadline to tell a blocking
// function that it should abandon its work as soon as it gets to it.
func ExampleWithDeadline() {
d := time.Now().Add(shortDuration)
ctx, cancel := context.WithDeadline(context.Background(), d)
// Even though ctx will be expired, it is good practice to call its
// cancellation function in any case. Failure to do so may keep the
// context and its parent alive longer than necessary.
defer cancel()
select {
case <-time.After(1 * time.Second):
fmt.Println("overslept")
case <-ctx.Done():
fmt.Println(ctx.Err())
}
// Output:
// context deadline exceeded
}Secara permukaan fungsi testing ini adalah fungsi biasa, tetapi example testing terutama tercermin dari komentar Output, fungsi yang akan diuji hanya memiliki satu baris output, menggunakan komentar Output untuk mendeteksi output. Pertama buat file bernama hello.go, tulis kode sebagai berikut
package say
import "fmt"
func Hello() {
fmt.Println("hello")
}
func GoodBye() {
fmt.Println("bye")
}Fungsi SayHello adalah fungsi yang akan diuji, lalu buat file testing example_test.go, tulis kode sebagai berikut
package test
import (
"golearn/say"
)
func ExampleHello() {
say.Hello()
// Output:
// hello
}
func ExampleGoodBye() {
say.GoodBye()
// Output:
// bye
}
func ExampleSay() {
say.Hello()
say.GoodBye()
// Output:
// hello
// bye
}Komentar Output di fungsi menunjukkan mendeteksi output fungsi apakah hello, selanjutnya jalankan command testing lihat hasilnya.
$ go test -v
=== RUN ExampleHello
--- PASS: ExampleHello (0.00s)
=== RUN ExampleGoodBye
--- PASS: ExampleGoodBye (0.00s)
=== RUN ExampleSay
--- PASS: ExampleSay (0.00s)
PASS
ok golearn/test 0.448sDari hasil dapat dilihat semua testing sudah lulus, tentang Output ada beberapa cara penulisan, pertama adalah hanya satu baris output, berarti mendeteksi output fungsi ini apakah hello
// Output:
// helloKedua adalah multi-baris output, yaitu mendeteksi output apakah cocok sesuai urutan
// Output:
// hello
// byeKetiga adalah unordered output, yaitu tidak按照 urutan multi-baris output matching
// Unordered output:
// bye
// helloPerlu diperhatikan, untuk fungsi testing而言, hanya ketika beberapa baris terakhir adalah komentar Output akan dianggap sebagai example testing, jika tidak hanya fungsi biasa, tidak akan dijalankan oleh Go.
Unit Testing
Unit testing adalah testing terhadap unit testable terkecil dalam software, ukuran unit didefinisikan oleh developer, mungkin adalah struct, atau paket, juga mungkin adalah fungsi, atau tipe. Berikut masih melalui contoh untuk mendemonstrasikan, pertama buat file /tool/math.go, tulis kode sebagai berikut
package tool
type Number interface {
~int8 | ~int16 | ~int32 | ~int64 | ~int
}
func SumInt[T Number](a, b T) T {
return a + b
}
func Equal[T Number](a, b T) bool {
return a == b
}Lalu buat file testing /tool_test/unit_test.go, untuk unit testing而言, penamaan dapat menggunakan unit_test atau paket yang ingin diuji atau fungsi sebagai prefix file.
package test_test
import (
"golearn/tool"
"testing"
)
func TestSum(t *testing.T) {
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Errorf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Errorf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}Untuk unit testing而言, gaya penamaan setiap test case adalah TestXXXX, dan parameter input fungsi harus t *testing.T, testing.T adalah struct yang disediakan paket testing untuk memudahkan testing, menyediakan banyak method yang dapat digunakan, t.Errorf di contoh setara dengan t.Logf, untuk output informasi log testing yang gagal dengan format, lainnya yang umum digunakan还有 t.Fail untuk menandai test case saat ini sebagai testing gagal, fungsi serupa还有 t.FailNow juga akan menandai sebagai testing gagal, tetapi yang前者 setelah gagal masih akan继续 menjalankan, yang后者 akan langsung berhenti menjalankan, seperti contoh di bawah, modifikasi hasil expected menjadi hasil yang salah:
package tool_test
import (
"golearn/tool"
"testing"
)
func TestSum(t *testing.T) {
a, b := 10, 101
expected := 110
actual := tool.SumInt(a, b)
if actual != expected {
// Errorf internal menggunakan t.Fail()
t.Errorf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
t.Log("test finished")
}
func TestEqual(t *testing.T) {
a, b := 10, 101
expected := true
actual := tool.Equal(a, b)
if actual != expected {
// Fatalf internal menggunakan t.FailNow()
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
t.Log("test finished")
}Jalankan testing di atas output sebagai berikut
$ go test tool_test.go -v
=== RUN TestSum
tool_test.go:14: Sum(10,101) expected 110,actual is 111
tool_test.go:16: test finished
--- FAIL: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:25: Sum(10,101) expected true,actual is false
--- FAIL: TestEqual (0.00s)
FAIL command-line-arguments 0.037sDari log testing dapat dilihat TestSum meskipun gagal masih output test finished, sedangkan TestEqual tidak, sama还有 t.SkipNow, akan menandai test case saat ini sebagai SKIP, lalu berhenti menjalankan, di testing putaran selanjutnya akan继续 menjalankan.
package tool_test
import (
"golearn/tool"
"testing"
)
func TestSum(t *testing.T) {
a, b := 10, 101
expected := 110
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
t.Log("test finished")
}
func TestEqual(t *testing.T) {
a, b := 10, 101
expected := true
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
t.Log("test finished")
}Saat menjalankan testing, modifikasi jumlah testing menjadi 2
$ go test tool_test.go -v -count=2
=== RUN TestSum
tool_test.go:14: Sum(10,101) expected 110,actual is 111
--- SKIP: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:25: Sum(10,101) expected true,actual is false
--- FAIL: TestEqual (0.00s)
=== RUN TestSum
tool_test.go:14: Sum(10,101) expected 110,actual is 111
--- SKIP: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:25: Sum(10,101) expected true,actual is false
--- FAIL: TestEqual (0.00s)
FAIL command-line-arguments 0.468sContoh di数 output test finished di baris terakhir, untuk表示 testing selesai, sebenarnya dapat menggunakan t.Cleanup untuk mendaftarkan fungsi cleanup khusus melakukan hal ini, fungsi ini akan dijalankan saat test case结束, sebagai berikut.
package tool_test
import (
"golearn/tool"
"testing"
)
func finished(t *testing.T) {
t.Log("test finished")
}
func TestSum(t *testing.T) {
t.Cleanup(func() {
finished(t)
})
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
t.Cleanup(func() {
finished(t)
})
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}Setelah menjalankan testing output sebagai berikut
$ go test tool_test.go -v
=== RUN TestSum
tool_test.go:9: test finished
--- PASS: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:9: test finished
--- PASS: TestEqual (0.00s)
PASS
ok command-line-arguments 0.462sHelper
Melalui t.Helper() dapat menandai fungsi saat ini sebagai helper function, helper function tidak akan单独 sebagai test case untuk dijalankan, saat mencatat log nomor baris yang output juga adalah nomor baris caller helper function, ini dapat membuat analisis log lebih akurat定位, menghindari informasi冗杂 lainnya. Misalnya contoh t.Cleanup di atas dapat dimodifikasi menjadi helper function, sebagai berikut.
package tool_test
import (
"golearn/tool"
"testing"
)
func CleanupHelper(t *testing.T) {
t.Helper()
t.Log("test finished")
}
func TestSum(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := false
t.Helper()
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}Setelah menjalankan testing output informasi sebagai berikut, perbedaan dengan sebelumnya adalah nomor baris test finished menjadi nomor baris caller.
$ go test tool_test.go -v
=== RUN TestSum
tool_test.go:15: test finished
--- PASS: TestSum (0.00s)
=== RUN TestEqual
tool_test.go:30: test finished
--- PASS: TestEqual (0.00s)
PASS
ok command-line-arguments 0.464sTIP
Operasi di atas hanya dapat dilakukan di main testing, yaitu test case yang langsung dijalankan, jika digunakan di sub testing akan panic.
Sub Testing
Dalam beberapa situasi, akan perlu menguji test case lain di dalam satu test case, test case nested seperti ini umumnya disebut sub testing, melalui method t.Run(), signature method sebagai berikut
// Method Run akan开启 goroutine baru untuk menjalankan sub testing, blocking menunggu fungsi f selesai baru akan return
// Nilai return adalah apakah testing lulus
func (t *T) Run(name string, f func(t *T)) boolBerikut adalah contoh
func TestTool(t *testing.T) {
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
}Setelah menjalankan hasil sebagai berikut
$ go test -run TestTool -v
=== RUN TestTool
=== RUN TestTool/tool.Sum(10,101)
tool_test.go:15: test finished
=== RUN TestTool/tool.Equal(10,101)
tool_test.go:30: test finished
--- PASS: TestTool (0.00s)
--- PASS: TestTool/tool.Sum(10,101) (0.00s)
--- PASS: TestTool/tool.Equal(10,101) (0.00s)
PASS
ok golearn/tool_test 0.449sMelalui output dapat dengan jelas melihat struktur hierarki parent-child, di contoh di atas sub testing pertama belum selesai sub testing kedua tidak akan dijalankan, dapat menggunakan t.Parallel() untuk menandai test case dapat berjalan paralel, dengan demikian urutan output akan tidak dapat ditentukan.
package tool_test
import (
"golearn/tool"
"testing"
)
func CleanupHelper(t *testing.T) {
t.Helper()
t.Log("test finished")
}
func TestSum(t *testing.T) {
t.Parallel()
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := 111
actual := tool.SumInt(a, b)
if actual != expected {
t.Skipf("Sum(%d,%d) expected %d,actual is %d", a, b, expected, actual)
}
}
func TestEqual(t *testing.T) {
t.Parallel()
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}
func TestToolParallel(t *testing.T) {
t.Log("setup")
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
t.Log("teardown")
}Setelah menjalankan testing output sebagai berikut
$ go test -run TestTool -v
=== RUN TestToolParallel
tool_test.go:46: setup
=== RUN TestToolParallel/tool.Sum(10,101)
=== PAUSE TestToolParallel/tool.Sum(10,101)
=== RUN TestToolParallel/tool.Equal(10,101)
=== PAUSE TestToolParallel/tool.Equal(10,101)
=== NAME TestToolParallel
tool_test.go:49: teardown
=== CONT TestToolParallel/tool.Sum(10,101)
=== CONT TestToolParallel/tool.Equal(10,101)
=== NAME TestToolParallel/tool.Sum(10,101)
tool_test.go:16: test finished
=== NAME TestToolParallel/tool.Equal(10,101)
tool_test.go:32: test finished
--- PASS: TestToolParallel (0.00s)
--- PASS: TestToolParallel/tool.Sum(10,101) (0.00s)
--- PASS: TestToolParallel/tool.Equal(10,101) (0.00s)
PASS
ok golearn/tool_test 0.444sDari hasil testing dapat dengan jelas melihat ada proses blocking menunggu, saat menjalankan test case secara konkuren, seperti contoh di atas jelas tidak dapat berjalan normal, karena kode selanjutnya tidak dapat menjamin sinkron berjalan, saat ini dapat memilih nested satu lapis t.Run() lagi, sebagai berikut
func TestToolParallel(t *testing.T) {
t.Log("setup")
t.Run("process", func(t *testing.T) {
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
})
t.Log("teardown")
}Jalankan lagi, dapat melihat hasil eksekusi yang normal.
$ go test -run TestTool -v
=== RUN TestToolParallel
tool_test.go:46: setup
=== RUN TestToolParallel/process
=== RUN TestToolParallel/process/tool.Sum(10,101)
=== PAUSE TestToolParallel/process/tool.Sum(10,101)
=== RUN TestToolParallel/process/tool.Equal(10,101)
=== PAUSE TestToolParallel/process/tool.Equal(10,101)
=== CONT TestToolParallel/process/tool.Sum(10,101)
=== CONT TestToolParallel/process/tool.Equal(10,101)
=== NAME TestToolParallel/process/tool.Sum(10,101)
tool_test.go:16: test finished
=== NAME TestToolParallel/process/tool.Equal(10,101)
tool_test.go:32: test finished
=== NAME TestToolParallel
tool_test.go:51: teardown
--- PASS: TestToolParallel (0.00s)
--- PASS: TestToolParallel/process (0.00s)
--- PASS: TestToolParallel/process/tool.Sum(10,101) (0.00s)
--- PASS: TestToolParallel/process/tool.Equal(10,101) (0.00s)
PASS
ok golearn/tool_test 0.450sGaya Tabel
Di unit testing di atas, data input testing semuanya adalah variabel yang dideklarasikan manual, saat ukuran data kecil tidak masalah, tetapi jika ingin menguji banyak grup data, tidak mungkin lagi mendeklarasikan variabel untuk membuat data testing, jadi umumnya sebisa mungkin menggunakan slice struct, struct adalah struct anonim yang dideklarasikan sementara, karena gaya coding seperti ini terlihat seperti tabel, jadi disebut table-driven. Berikut contoh, ini adalah contoh mendeklarasikan beberapa variabel manual untuk membuat data testing, jika ada banyak grup data terlihat tidak terlalu intuitif, jadi dimodifikasi menjadi gaya tabel
func TestEqual(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
a, b := 10, 101
expected := false
actual := tool.Equal(a, b)
if actual != expected {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
}Kode setelah dimodifikasi sebagai berikut
func TestEqual(t *testing.T) {
t.Cleanup(func() {
CleanupHelper(t)
})
// table driven style
testData := []struct {
a, b int
exp bool
}{
{10, 101, false},
{5, 5, true},
{30, 32, false},
{100, 101, false},
{2, 3, false},
{4, 4, true},
}
for _, data := range testData {
if actual := tool.Equal(data.a, data.b); actual != data.exp {
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", data.a, data.b, data.exp, actual)
}
}
}Data testing seperti ini terlihat lebih intuitif.
Benchmark Testing
Benchmark testing juga disebut performance testing, biasanya用于 testing okupansi memori program, situasi penggunaan CPU, waktu eksekusi dll indikator performa. Untuk benchmark testing而言, file testing biasanya berakhiran bench_test.go, dan nama fungsi test case harus format BenchmarkXXXX.
Berikut menggunakan contoh performa string concatenation sebagai contoh benchmark testing. Pertama buat file /tool/strConcat.go, seperti diketahui langsung menggunakan string进行 + concatenation performa sangat rendah, sedangkan menggunakan strings.Builder jauh lebih baik, di file /tool/strings.go masing-masing buat dua fungsi untuk dua cara string concatenation.
package tool
import "strings"
func ConcatStringDirect(longString string) {
res := ""
for i := 0; i < 100_000.; i++ {
res += longString
}
}
func ConcatStringWithBuilder(longString string) {
var res strings.Builder
for i := 0; i < 100_000.; i++ {
res.WriteString(longString)
}
}Lalu buat file testing /tool_test/bench_tool_test.go , kode sebagai berikut
package tool_test
import (
"golearn/tool"
"testing"
)
var longString = "longStringlongStringlongStringlongStringlongStringlongStringlongStringlongString"
func BenchmarkConcatDirect(b *testing.B) {
for i := 0; i < b.N; i++ {
tool.ConcatStringDirect(longString)
}
}
func BenchmarkConcatBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
tool.ConcatStringWithBuilder(longString)
}
}Jalankan command testing, command mengaktifkan log detail dan analisis memori, menentukan jumlah core CPU yang digunakan, dan setiap test case dijalankan dua putaran, output sebagai berikut
$ go test -v -benchmem -bench . -run bench_tool_test.go -cpu=2,4,8 -count=2
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkConcatDirect
BenchmarkConcatDirect-2 4 277771375 ns/op 4040056736 B/op 10000 allocs/op
BenchmarkConcatDirect-2 4 278500125 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-4 1 1153796000 ns/op 4040068784 B/op 10126 allocs/op
BenchmarkConcatDirect-4 1 1211017600 ns/op 4040073104 B/op 10171 allocs/op
BenchmarkConcatDirect-8 2 665460800 ns/op 4040077760 B/op 10219 allocs/op
BenchmarkConcatDirect-8 2 679774450 ns/op 4040080064 B/op 10243 allocs/op
BenchmarkConcatBuilder
BenchmarkConcatBuilder-2 3428 344530 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3579 351858 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-4 2448 736177 ns/op 4128185 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1688 662993 ns/op 4128185 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1958 550333 ns/op 4128199 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2174 552113 ns/op 4128196 B/op 29 allocs/op
PASS
ok golearn/tool_test 21.381sDi bawah menjelaskan hasil output benchmark testing, goos mewakili sistem operasi yang dijalankan, goarh mewakili arsitektur CPU, pkg adalah paket tempat testing berada, cpu adalah beberapa informasi tentang CPU. Hasil setiap test case dipisahkan oleh nama setiap benchmark testing, kolom pertama BenchmarkConcatDirect-2 angka 2 mewakili jumlah core CPU yang digunakan, kolom kedua 4 mewakili ukuran b.N di kode, yaitu jumlah loop di benchmark testing, kolom ketiga 277771375 ns/op mewakili waktu yang dikonsumsi setiap loop, ns adalah nanodetik, kolom keempat 4040056736 B/op mewakili ukuran byte memori yang dialokasikan setiap loop, kolom kelima 10000 allocs/op mewakili jumlah alokasi memori setiap loop.
Jelas, dari hasil testing, performa menggunakan strings.Builder jauh lebih tinggi daripada menggunakan + untuk concatenation string, melalui perbandingan data yang intuitif这正是 tujuan benchmark testing.
benchstat
benchstat adalah tool analisis performa testing open source, sampel testing performa di atas hanya dua grup, sekali sampel banyak起来 analisis manual akan sangat memakan waktu dan tenaga, tool ini lahir untuk menyelesaikan masalah analisis performa.
Pertama perlu download tool ini
$ go install golang.org/x/perf/benchstatJalankan benchmark testing dua kali, kali ini modifikasi jumlah sampel menjadi 5, dan masing-masing output ke file old.txt dan new.txt untuk perbandingan, hasil eksekusi pertama
$ go test -v -benchmem -bench . -run bench_tool_test.go -cpu=2,4,8 -count=5 | tee -a old.txt
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkConcatDirect
BenchmarkConcatDirect-2 4 290535650 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 298974625 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 299637800 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 276487000 ns/op 4040056784 B/op 10001 allocs/op
BenchmarkConcatDirect-2 4 356465275 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-4 2 894723200 ns/op 4040077424 B/op 10216 allocs/op
BenchmarkConcatDirect-4 2 785830400 ns/op 4040078288 B/op 10225 allocs/op
BenchmarkConcatDirect-4 2 743634000 ns/op 4040077568 B/op 10217 allocs/op
BenchmarkConcatDirect-4 2 953802700 ns/op 4040075408 B/op 10195 allocs/op
BenchmarkConcatDirect-4 2 953028750 ns/op 4040077520 B/op 10217 allocs/op
BenchmarkConcatDirect-8 2 684023150 ns/op 4040086784 B/op 10313 allocs/op
BenchmarkConcatDirect-8 2 634380250 ns/op 4040090528 B/op 10352 allocs/op
BenchmarkConcatDirect-8 2 685030600 ns/op 4040090768 B/op 10355 allocs/op
BenchmarkConcatDirect-8 2 817909650 ns/op 4040089808 B/op 10345 allocs/op
BenchmarkConcatDirect-8 2 600078100 ns/op 4040095664 B/op 10406 allocs/op
BenchmarkConcatBuilder
BenchmarkConcatBuilder-2 2925 419651 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2961 423899 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2714 422275 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2848 452255 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 2612 454452 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-4 974 1158000 ns/op 4128189 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1098 1068682 ns/op 4128192 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1042 1056570 ns/op 4128194 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1280 978213 ns/op 4128191 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1538 1162108 ns/op 4128190 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1744 700824 ns/op 4128203 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2235 759537 ns/op 4128201 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1556 736455 ns/op 4128204 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1592 825794 ns/op 4128201 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2263 717285 ns/op 4128203 B/op 29 allocs/op
PASS
ok golearn/tool_test 56.742sHasil eksekusi kedua
$ go test -v -benchmem -bench . -run bench_tool_test.go -cpu=2,4,8 -count=5 | tee -a new.txt
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
BenchmarkConcatDirect
BenchmarkConcatDirect-2 4 285074900 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 291517150 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 281901975 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 292320625 ns/op 4040056592 B/op 9999 allocs/op
BenchmarkConcatDirect-2 4 286723000 ns/op 4040056952 B/op 10002 allocs/op
BenchmarkConcatDirect-4 1 1188983000 ns/op 4040071856 B/op 10158 allocs/op
BenchmarkConcatDirect-4 1 1080713900 ns/op 4040070800 B/op 10147 allocs/op
BenchmarkConcatDirect-4 1 1203622300 ns/op 4040067344 B/op 10111 allocs/op
BenchmarkConcatDirect-4 1 1045291300 ns/op 4040070224 B/op 10141 allocs/op
BenchmarkConcatDirect-4 1 1123163300 ns/op 4040070032 B/op 10139 allocs/op
BenchmarkConcatDirect-8 2 790421300 ns/op 4040076656 B/op 10208 allocs/op
BenchmarkConcatDirect-8 2 659047300 ns/op 4040079488 B/op 10237 allocs/op
BenchmarkConcatDirect-8 2 712991800 ns/op 4040077184 B/op 10213 allocs/op
BenchmarkConcatDirect-8 2 706605350 ns/op 4040078000 B/op 10222 allocs/op
BenchmarkConcatDirect-8 2 656195700 ns/op 4040085248 B/op 10297 allocs/op
BenchmarkConcatBuilder
BenchmarkConcatBuilder-2 2726 386412 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3439 335358 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3376 338957 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 3870 326301 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-2 4285 339596 ns/op 4128176 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1663 671535 ns/op 4128187 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1507 744885 ns/op 4128191 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1353 1097800 ns/op 4128187 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1388 1006019 ns/op 4128189 B/op 29 allocs/op
BenchmarkConcatBuilder-4 1635 993764 ns/op 4128189 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1332 783599 ns/op 4128198 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1818 729821 ns/op 4128202 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1398 780614 ns/op 4128202 B/op 29 allocs/op
BenchmarkConcatBuilder-8 1526 750513 ns/op 4128204 B/op 29 allocs/op
BenchmarkConcatBuilder-8 2164 704798 ns/op 4128204 B/op 29 allocs/op
PASS
ok golearn/tool_test 50.387sLalu gunakan benchstat untuk perbandingan
$ benchstat old.txt new.txt
goos: windows
goarch: amd64
pkg: golearn/tool_test
cpu: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ConcatDirect-2 299.0m ± ∞ ¹ 286.7m ± ∞ ¹ ~ (p=0.310 n=5)
ConcatDirect-4 894.7m ± ∞ ¹ 1123.2m ± ∞ ¹ +25.53% (p=0.008 n=5)
ConcatDirect-8 684.0m ± ∞ ¹ 706.6m ± ∞ ¹ ~ (p=0.548 n=5)
ConcatBuilder-2 423.9µ ± ∞ ¹ 339.0µ ± ∞ ¹ -20.04% (p=0.008 n=5)
ConcatBuilder-4 1068.7µ ± ∞ ¹ 993.8µ ± ∞ ¹ ~ (p=0.151 n=5)
ConcatBuilder-8 736.5µ ± ∞ ¹ 750.5µ ± ∞ ¹ ~ (p=0.841 n=5)
geomean 19.84m 19.65m -0.98%
¹ need >= 6 samples for confidence interval at level 0.95
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
ConcatDirect-2 3.763Gi ± ∞ ¹ 3.763Gi ± ∞ ¹ ~ (p=1.000 n=5)
ConcatDirect-4 3.763Gi ± ∞ ¹ 3.763Gi ± ∞ ¹ -0.00% (p=0.008 n=5)
ConcatDirect-8 3.763Gi ± ∞ ¹ 3.763Gi ± ∞ ¹ -0.00% (p=0.008 n=5)
ConcatBuilder-2 3.937Mi ± ∞ ¹ 3.937Mi ± ∞ ¹ ~ (p=1.000 n=5) ²
ConcatBuilder-4 3.937Mi ± ∞ ¹ 3.937Mi ± ∞ ¹ ~ (p=0.079 n=5)
ConcatBuilder-8 3.937Mi ± ∞ ¹ 3.937Mi ± ∞ ¹ ~ (p=0.952 n=5)
geomean 123.2Mi 123.2Mi -0.00%
¹ need >= 6 samples for confidence interval at level 0.95
² all samples are equal
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
ConcatDirect-2 9.999k ± ∞ ¹ 9.999k ± ∞ ¹ ~ (p=1.000 n=5)
ConcatDirect-4 10.22k ± ∞ ¹ 10.14k ± ∞ ¹ -0.74% (p=0.008 n=5)
ConcatDirect-8 10.35k ± ∞ ¹ 10.22k ± ∞ ¹ -1.26% (p=0.008 n=5)
ConcatBuilder-2 29.00 ± ∞ ¹ 29.00 ± ∞ ¹ ~ (p=1.000 n=5) ²
ConcatBuilder-4 29.00 ± ∞ ¹ 29.00 ± ∞ ¹ ~ (p=1.000 n=5) ²
ConcatBuilder-8 29.00 ± ∞ ¹ 29.00 ± ∞ ¹ ~ (p=1.000 n=5) ²
geomean 543.6 541.7 -0.33%
¹ need >= 6 samples for confidence interval at level 0.95
² all samples are equalDari hasil dapat dilihat benchstat membaginya menjadi tiga grup, masing-masing adalah waktu, okupansi memori dan jumlah alokasi memori, di antaranya geomean adalah nilai rata-rata, p adalah tingkat signifikansi sampel, interval kritis biasanya 0.05, di atas 0.05 tidak terlalu可信, ambil satu data di antaranya sebagai berikut:
│ sec/op │ sec/op vs base │
ConcatDirect-4 894.7m ± ∞ ¹ 1123.2m ± ∞ ¹ +25.53% (p=0.008 n=5)Dapat melihat waktu eksekusi old adalah 894.7ms, waktu eksekusi new 1123.2ms, dibandingkan masih增加了 25.53% waktu.
Fuzz Testing
Fuzz testing adalah fitur baru yang diluncurkan GO1.18, termasuk增强 dari unit testing dan benchmark testing, perbedaannya adalah data testing keduanya perlu developer tulis manual, sedangkan fuzz testing dapat menghasilkan data testing random melalui corpus, tentang fuzz testing di Go dapat前往 Go Fuzzing untuk了解 lebih banyak konsep. Keuntungan fuzz testing adalah, dibandingkan dengan data testing tetap, data random dapat lebih baik menguji kondisi batas program. Berikut menggunakan contoh tutorial官方 untuk menjelaskan, kali ini perlu menguji fungsi reverse string, pertama buat file /tool/strings.go, tulis kode sebagai berikut
package tool
func Reverse(s string) string {
b := []byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}Buat file fuzz testing /tool_test/fuzz_tool_test.go, tulis kode sebagai berikut
package tool
import (
"golearn/tool"
"testing"
"unicode/utf8"
)
func FuzzReverse(f *testing.F) {
testdata := []string{"hello world!", "nice to meet you", "good bye!"}
for _, data := range testdata {
f.Add(data)
}
f.Fuzz(func(t *testing.T, str string) {
first := tool.Reverse(str)
second := tool.Reverse(first)
t.Logf("str:%q,first:%q,second:%q", str, first, second)
if str != second {
t.Errorf("before: %q, after: %q", str, second)
}
if utf8.ValidString(str) && !utf8.ValidString(first) {
t.Errorf("Reverse produced invalid UTF-8 string %q %q", str, first)
}
})
}Di fuzz testing, pertama perlu menambahkan data ke seed corpus, contoh menggunakan f.Add() untuk menambahkan, membantu menghasilkan data testing random selanjutnya. Lalu menggunakan f.Fuzz(fn) untuk testing, signature fungsi sebagai berikut:
func (f *F) Fuzz(ff any)
func (f *F) Add(args ...any)fn mirip seperti logika fungsi unit testing, parameter input pertama harus t *testing.T, diikuti parameter yang ingin dihasilkan. Karena string yang传入 tidak dapat diprediksi, di sini menggunakan metode reverse dua kali untuk verifikasi. Jalankan command berikut
$ go test -run Fuzz -v
=== RUN FuzzReverse
=== RUN FuzzReverse/seed#0
fuzz_tool_test.go:18: str:"hello world!",first:"!dlrow olleh",second:"hello world!"
=== RUN FuzzReverse/seed#1
fuzz_tool_test.go:18: str:"nice to meet you",first:"uoy teem ot ecin",second:"nice to meet you"
=== RUN FuzzReverse/seed#2
fuzz_tool_test.go:18: str:"good bye!",first:"!eyb doog",second:"good bye!"
--- PASS: FuzzReverse (0.00s)
--- PASS: FuzzReverse/seed#0 (0.00s)
--- PASS: FuzzReverse/seed#1 (0.00s)
--- PASS: FuzzReverse/seed#2 (0.00s)
PASS
ok golearn/tool_test 0.539sSaat parameter tidak带 -fuzz, tidak akan menghasilkan data testing random, hanya akan传入 data di corpus ke fungsi testing, dari hasil dapat dilihat semua testing lulus, menggunakan seperti ini setara dengan unit testing, tetapi sebenarnya ada masalah, di bawah tambahkan parameter -fuzz jalankan lagi.
$ go test -fuzz . -fuzztime 30s -run Fuzz -v
=== RUN FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/217 completed
fuzz: minimizing 91-byte failing input file
fuzz: elapsed: 0s, gathering baseline coverage: 15/217 completed
--- FAIL: FuzzReverse (0.13s)
--- FAIL: FuzzReverse (0.00s)
fuzz_tool_test.go:18: str:"𐑄",first:"\x84\x91\x90\xf0",second:"𐑄"
fuzz_tool_test.go:23: Reverse produced invalid UTF-8 string "𐑄" "\x84\x91\x90\xf0"
Failing input written to testdata\fuzz\FuzzReverse\d856c981b6266ba2
To re-run:
go test -run=FuzzReverse/d856c981b6266ba2
=== NAME
FAIL
exit status 1
FAIL golearn/tool_test 0.697sTIP
Test case yang gagal di fuzz testing akan output ke file corpus tertentu di direktori testdata di folder testing saat ini, misalnya contoh di atas
Failing input written to testdata\fuzz\FuzzReverse\d856c981b6266ba2
To re-run:
go test -run=FuzzReverse/d856c981b6266ba2testdata\fuzz\FuzzReverse\d856c981b6266ba2 adalah path file corpus yang output, konten file sebagai berikut
go test fuzz v1
string("𐑄")Dapat dilihat kali ini tidak lulus, alasannya adalah string setelah reverse menjadi format non-utf8, jadi melalui fuzz testing menemukan masalah ini. Karena beberapa karakter menempati tidak hanya satu byte, jika reverse dengan satuan byte pasti乱码, jadi modifikasi source code yang akan diuji menjadi sebagai berikut, mengkonversi string menjadi []rune, dengan demikian dapat menghindari masalah di atas.
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}Selanjutnya langsung jalankan test case yang gagal dari fuzz testing sebelumnya
$ go test -run=FuzzReverse/d856c981b6266ba2 -v
=== RUN FuzzReverse
=== RUN FuzzReverse/d856c981b6266ba2
fuzz_tool_test.go:18: str:"𐑄",first:"𐑄",second:"𐑄"
--- PASS: FuzzReverse (0.00s)
--- PASS: FuzzReverse/d856c981b6266ba2 (0.00s)
PASS
ok golearn/tool_test 0.033sDapat dilihat kali ini lulus testing, jalankan lagi fuzz testing lihat masih ada masalah atau tidak
$ go test -fuzz . -fuzztime 30s -run Fuzz -v
=== RUN FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/219 completed
fuzz: minimizing 70-byte failing input file
failure while testing seed corpus entry: FuzzReverse/d97214ce235bfcf5
fuzz: elapsed: 0s, gathering baseline coverage: 2/219 completed
--- FAIL: FuzzReverse (0.15s)
--- FAIL: FuzzReverse (0.00s)
fuzz_tool_test.go:18: str:"\xe4",first:"",second:""
fuzz_tool_test.go:20: before: "\xe4", after: ""
=== NAME
FAIL
exit status 1
FAIL golearn/tool_test 0.184sDapat dilihat error lagi, kali ini masalah adalah setelah reverse string dua kali tidak sama, karakter asli adalah \xe4, hasil yang diharapkan adalah 4ex\, tetapi hasilnya乱码, sebagai berikut
func main() {
fmt.Println("\xe4")
fmt.Println([]byte("\xe4"))
fmt.Println([]rune("\xe4"))
fmt.Printf("%q\n", "\xe4")
fmt.Printf("%x\n", "\xe4")
}Hasil eksekusinya adalah
[65533]
"\xe4"
e4Menelusuri penyebabnya adalah \xe4 mewakili satu byte, tetapi bukan urutan UTF-8 yang valid (dalam encoding UTF-8 \xe4 adalah awal karakter tiga byte, tetapi缺少 dua byte berikutnya). Saat dikonversi ke []rune, Golang otomatis mengubahnya menjadi []rune yang berisi单个 karakter Unicode []rune{"\uFFFD"}, setelah reverse masih []rune{"\uFFFD"}, saat dikonversi kembali ke string karakter Unicode ini diganti dengan encoding UTF-8-nya \xef\xbf\xbd. Oleh karena itu satu solusi adalah jika yang传入 adalah string non-utf8, langsung return error:
func Reverse(s string) (string, error) {
if !utf8.ValidString(s) {
return s, errors.New("input is not valid UTF-8")
}
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r), nil
}Kode testing juga perlu dimodifikasi sedikit
func FuzzReverse(f *testing.F) {
testdata := []string{"hello world!", "nice to meet you", "good bye!"}
for _, data := range testdata {
f.Add(data)
}
f.Fuzz(func(t *testing.T, str string) {
first, err := tool.Reverse(str)
if err != nil {
t.Skip()
}
second, err := tool.Reverse(first)
if err != nil {
t.Skip()
}
t.Logf("str:%q,first:%q,second:%q", str, first, second)
if str != second {
t.Errorf("before: %q, after: %q", str, second)
}
if utf8.ValidString(str) && !utf8.ValidString(first) {
t.Errorf("Reverse produced invalid UTF-8 string %q %q", str, first)
}
})
}Saat fungsi reverse mengembalikan error, skip testing, jalankan fuzz testing lagi
$ go test -fuzz . -fuzztime 30s -run Fuzz -v
=== RUN FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/219 completed
fuzz: elapsed: 0s, gathering baseline coverage: 219/219 completed, now fuzzing with 16 workers
fuzz: elapsed: 3s, execs: 895571 (297796/sec), new interesting: 32 (total: 251)
fuzz: elapsed: 6s, execs: 1985543 (363120/sec), new interesting: 37 (total: 256)
fuzz: elapsed: 9s, execs: 3087837 (367225/sec), new interesting: 38 (total: 257)
fuzz: elapsed: 12s, execs: 4090817 (335167/sec), new interesting: 40 (total: 259)
fuzz: elapsed: 15s, execs: 5132580 (346408/sec), new interesting: 44 (total: 263)
fuzz: elapsed: 18s, execs: 6248486 (372185/sec), new interesting: 45 (total: 264)
fuzz: elapsed: 21s, execs: 7366827 (373305/sec), new interesting: 46 (total: 265)
fuzz: elapsed: 24s, execs: 8439803 (358059/sec), new interesting: 47 (total: 266)
fuzz: elapsed: 27s, execs: 9527671 (361408/sec), new interesting: 47 (total: 266)
fuzz: elapsed: 30s, execs: 10569473 (348056/sec), new interesting: 48 (total: 267)
fuzz: elapsed: 30s, execs: 10569473 (0/sec), new interesting: 48 (total: 267)
--- PASS: FuzzReverse (30.16s)
=== NAME
PASS
ok golearn/tool_test 30.789sLalu kali ini dapat mendapatkan log output fuzz testing yang lebih lengkap, di antaranya penjelasan beberapa konsep sebagai berikut:
- elapsed: waktu yang telah berlalu setelah satu putaran selesai
- execs: total input yang dijalankan, 297796/sec表示 berapa banyak input per detik
- new interesting: di testing, total input "interesting" yang telah ditambahkan ke corpus. (Input interesting mengacu pada input yang dapat memperluas coverage kode di luar jangkauan corpus yang ada, seiring dengan perluasan coverage, tren pertumbuhannya secara umum akan terus melambat)
TIP
Jika tidak ada parameter -fuzztime untuk membatasi waktu, fuzz testing akan berjalan selamanya.
Dukungan Tipe
Tipe yang didukung di Go Fuzz sebagai berikut:
string,[]byteint,int8,int16,int32/rune,int64uint,uint8/byte,uint16,uint32,uint64float32,float64bool
