Test
Geliştiriciler için, iyi testler programdaki hataları erken tespit edebilir. Bakımın zamanında yapılmaması nedeniyle Bug oluşmasından kaynaklanan zihinsel yükü önler. Bu nedenle test yazmak çok gereklidir. Go test konusunda çok basit ve kullanışlı komut satırı aracı go test sağlar. Standart kütüphane ve birçok açık kaynak framework'te testin izlerini görebiliriz. Bu araç kullanımı çok rahattır. Şu anda aşağıdaki test türlerini destekler:
- Örnek test
- Birim test
- Kıyaslama testi
- Bulanık test
Go'da API'lerin çoğu standart kütüphane testing tarafından sağlanır.
::: ipucu
Komut satırında go help testfunc komutunu çalıştırarak, Go'nun yukarıdaki dört test türü için resmi açıklamasını görebilirsiniz.
:::
Yazım Kuralları
Test yazmaya başlamadan önce, birkaç kurala dikkat etmek gerekir. Bu şekilde后续 öğrenimde daha rahat olacaktır.
- Test paketi: Test dosyaları en iyi ayrı bir pakette tutulur. Bu paket genellikle
testolarak adlandırılır. - Test dosyası: Test dosyaları genellikle
_test.goile biter. Örneğin belirli bir fonksiyonu test etmek istiyorsanız,function_test.goolarak adlandırın. Test türüne göre daha da detaylı bölmek istiyorsanız, test türünü dosya öneki olarak kullanabilirsiniz. Örneğinbenchmark_marshaling_test.goveyaexample_marshaling_test.go. - Test fonksiyonu: Her test dosyasında farklı testler için birkaç test fonksiyonu bulunur. Farklı test türleri için, test fonksiyonlarının adlandırma stili de farklıdır. Örneğin örnek test
ExampleXXXX, birim testTestXXXX, kıyaslama testiBenchmarkXXXX, bulanık testFuzzXXXXşeklindedir. Bu şekilde yorum gerekmeden ne tür bir test olduğu bilinir.
::: ipucu
Paket adı testdata olduğunda, bu paket genellikle test için yardımcı verileri depolamak içindir. Test çalıştırılırken, Go testdata adlı paketi yoksayar.
:::
Yukarıdaki kurallara uyarak iyi bir test stili geliştirmek, günlük bakım için birçok sıkıntıyı önler.
Test Çalıştırma
Test çalıştırmak için esas olarak go test komutu kullanılır. Aşağıda gerçek kod örneği ile açıklanacaktır. Şimdi test edilecek dosya /say/hello.go var, kod şu şekildedir
package say
import "fmt"
func Hello() {
fmt.Println("hello")
}
func GoodBye() {
fmt.Println("bye")
}Ve test dosyası /test/example_test.go kodu şu şekildedir
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
}Bu testleri çalıştırmanın birkaç yolu vardır. Örneğin test paketi altındaki tüm test durumlarını çalıştırmak istiyorsanız, doğrudan test dizininde aşağıdaki komutu çalıştırabilirsiniz
$ go test ./
PASS
ok golearn/test 0.422s./ mevcut dizini temsil eder. Go, test dizinindeki tüm test dosyalarını yeniden derledikten sonra, tüm test durumlarını çalıştırır. Sonuçtan tüm test durumlarının geçtiği görülebilir. Sonraki parametre birden fazla dizin takip edebilir. Aşağıdaki komut gibi. Projenin ana dizininde açıkça çalıştırılacak test dosyası yok.
$ go test ./ ../
ok golearn/test
? golearn [no test files]::: ipucu
Çalıştırma parametresi birden fazla paket olduğunda, Go başarıyla geçen test durumlarını tekrar çalıştırmaz. Çalıştırma sırasında satır sonuna (cached) ekler. Bu şekilde çıktının bir önceki önbellek olduğunu belirtir. Test bayrak parametresi aşağıdaki kümede olduğunda, Go test sonuçlarını önbelleğe alır. Aksi takdirde önbelleğe almaz.
-benchtime, -cpu,-list, -parallel, -run, -short, -timeout, -failfast, -vÖnbelleği devre dışı bırakmak istiyorsanız, -count=1 parametresini ekleyebilirsiniz.
:::
Elbette belirli bir test dosyasını tek başına çalıştırmak için de belirleyebilirsiniz.
$ go test example_test.go
ok command-line-arguments 0.457sVeya belirli bir test dosyasının belirli bir test durumunu tek başına belirleyebilirsiniz. Örneğin
$ go test -run ExampleSay
PASS
ok golearn/test 0.038sYukarıdaki üç durumun hepsi testi tamamladı. Ancak çıktı sonucu çok öz. Bu durumda -v parametresini ekleyerek çıktı sonucunu daha detaylı hale getirebilirsiniz. Örneğin
$ 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.040sArtık her test durumunun çalıştırma sırasını, süresini, çalıştırma durumunu ve toplam süreyi açıkça görebilirsiniz.
::: ipucu
go test komutu varsayılan olarak tüm birim testleri, örnek testleri, bulanık testleri çalıştırır. -bench parametresi eklenirse, tüm türlerdeki testler çalıştırılır. Aşağıdaki komut gibi
$ go test -bench .Bu nedenle -run parametresini belirtmek gerekir. Örneğin sadece tüm kıyaslama testlerini çalıştırmak için komut aşağıdaki gibidir
$ go test -bench . -run ^$:::
Yaygın Parametreler
Go testinin çok sayıda bayrak parametresi vardır. Aşağıda sadece yaygın parametreler tanıtılacaktır. Daha fazla detay öğrenmek istiyorsanız, go help testflag komutunu kullanarak kendiniz öğrenebilirsiniz.
| Parametre | Açıklama |
|---|---|
-o file | Derlemeden sonra ikili dosya adını belirt |
-c | Sadece test dosyasını derle, ancak çalıştırma |
-json | Test günlüklerini json formatında çıktı |
-exec xprog | Testi xprog ile çalıştır, go run ile eşdeğerdir |
-bench regexp | regexp ile eşleşen kıyaslama testlerini seç |
-fuzz regexp | regexp ile eşleşen bulanık testleri seç |
-fuzztime t | Bulanık testin otomatik olarak sonlanma süresi, t zaman aralığıdır. Birim x olduğunda, sayıyı temsil eder. Örneğin 200x |
-fuzzminimizetime t | Mod testi çalıştırma minimum süresi, kural yukarıdaki gibi |
-count n | Testi n kez çalıştır, varsayılan 1 kez |
-cover | Test kapsam analizini aç |
-covermode set,count,atomic | Kapsam analizi modunu ayarla |
-cpu | Test çalıştırma için GOMAXPROCS |
-failfast | İlk test başarısız olduktan sonra, yeni test başlatmaz |
-list regexp | regexp ile eşleşen test durumlarını listele |
-parallel n | t.Parallel çağıran test durumlarının paralel çalışmasına izin ver. n değeri paralel maksimum sayıdır |
-run regexp | Sadece regexp ile eşleşen test durumlarını çalıştır |
-skip regexp | regexp ile eşleşen test durumlarını atla |
-timeout d | Tek test çalıştırma süresi d zaman aralığını aşarsa, panic oluşur. d zaman aralığıdır. Örn 1s,1ms,1ns vb. |
-shuffle off,on,N | Test çalıştırma sırasını karıştır. N rastgele tohumdur. Varsayılan tohum sistem zamanıdır |
-v | Daha detaylı test günlüğü çıktısı |
-benchmem | Kıyaslama testinin bellek tahsisini istatistik |
-blockprofile block.out | Testte goroutine engelleme durumunu istatistik ve dosyaya yaz |
-blockprofilerate n | Goroutine engelleme istatistik sıklığını kontrol et. go doc runtime.SetBlockProfileRate komutu ile daha fazla detay görün |
-coverprofile cover.out | Kapsam testi durumunu istatistik ve dosyaya yaz |
-cpuprofile cpu.out | CPU durumunu istatistik ve dosyaya yaz |
-memprofile mem.out | Bellek tahsis durumunu istatistik ve dosyaya yaz |
-memprofilerate n | Bellek tahsis istatistik sıklığını kontrol et. go doc runtime.MemProfileRate komutu ile daha fazla detay görün |
-mutexprofile mutex.out | Kilit rekabet durumunu istatistik ve dosyaya yaz |
-mutexprofilefraction n | n goroutine'in bir karşılıklı kilit için rekabet durumunu istatistik ayarla |
-trace trace.out | Çalıştırma takip durumunu dosyaya yaz |
-outputdir directory | Yukarıdaki istatistik dosyalarının çıktı dizinini belirt. Varsayılan go test çalıştırma dizinidir |
Örnek Test
Örnek test, diğer üç test gibi programın sorunlarını bulmak için değildir. Daha çok belirli bir fonksiyonun kullanım yöntemini göstermek içindir. Dokümantasyon rolü oynar. Örnek test resmi bir tanımlanmış kavram değildir. Sert bir规范 de değildir. Daha çok mühendislikte geleneksel bir anlaşma gibidir. Uyulup uyulmaması geliştiriciye bağlıdır. Örnek test standart kütüphanede çok sık görülür. Genellikle resmi olarak yazılan standart kütüphane kod örnekleridir. Örneğin standart kütüphane context/example_test.go içindeki ExampleWithDeadline test fonksiyonu. Bu fonksiyon DeadlineContext'in temel kullanım yöntemini gösterir:
// 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
}Yüzeyde bu test fonksiyonu sıradan bir fonksiyon gibidir. Ancak örnek test esas olarak Output yorumu ile yansıtılır. Test edilecek fonksiyon sadece bir satır çıktıya sahip olduğunda, Output yorumu kullanılarak çıktı tespit edilir. Önce hello.go adında bir dosya oluşturun, aşağıdaki kodu yazın
package say
import "fmt"
func Hello() {
fmt.Println("hello")
}
func GoodBye() {
fmt.Println("bye")
}SayHello fonksiyonu test edilecek fonksiyondur. Ardından test dosyası example_test.go oluşturun, aşağıdaki kodu yazın
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
}Fonksiyonda Output yorumu, fonksiyon çıktısının hello olup olmadığını tespit ettiğini belirtir. Ardından test komutunu çalıştırarak sonuca bakalım.
$ 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.448sSonuçtan tüm testlerin geçtiği görülebilir. Output ile ilgili birkaç yazım şekli vardır. Birincisi sadece bir satır çıktıdır. Bu fonksiyon çıktısının hello olup olmadığını tespit etmek anlamına gelir
// Output:
// helloİkincisi çok satırlı çıktıdır. Yani sırayla çıktının eşleşip eşleşmediğini tespit eder
// Output:
// hello
// byeÜçüncüsü sırasız çıktıdır. Yani sıra olmadan çok satırlı çıktı eşleşir
// Unordered output:
// bye
// helloDikkat edilmesi gereken, test fonksiyonu için, sadece son birkaç satır Output yorumu olduğunda örnek test olarak kabul edilir. Aksi takdirde sadece sıradan bir fonksiyondur. Go tarafından çalıştırılmaz.
Birim Test
Birim test, yazılımdaki en küçük test edilebilir birimi test etmektir. Birimin boyut tanımı geliştiriciye bağlıdır. Bir yapı, bir paket, bir fonksiyon veya bir tür olabilir. Aşağıda yine örnek ile gösterilecektir. Önce /tool/math.go dosyası oluşturun, aşağıdaki kodu yazın
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
}Ardından test dosyası /tool_test/unit_test.go oluşturun. Birim test için, adlandırma unit_test olabilir veya test edilecek paket ya da fonksiyon dosya öneki olarak kullanılabilir.
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)
}
}Birim test için, her test durumunun adlandırma stili TestXXXX şeklindedir. Ve fonksiyonun girdisi t *testing.T olmalıdır. testing.T, testing paketi tarafından test için sağlanan yapıdır. Birçok kullanılabilir metod sağlar. Örnekteki t.Errorf, t.Logf ile eşdeğerdir. Test başarısız günlük bilgilerini formatlamak için kullanılır. Diğer yaygın kullanılanlar arasında t.Fail bulunur. Mevcut durumu test başarısız olarak işaretlemek için kullanılır. Benzer işlevlere sahip t.FailNow da test başarısız olarak işaretler. Ancak前者 başarısız olduktan sonra çalışmaya devam eder. İkincisi ise doğrudan çalışmayı durdurur. Aşağıdaki örnek gibi, beklenen sonucu yanlış sonuç olarak değiştirin:
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内部使用的是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内部使用的是t.FailNow()
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
t.Log("test finished")
}Yukarıdaki testi çalıştırın, çıktı aşağıdaki gibidir
$ 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.037sTest günlüğünden TestSum durumunun başarısız olmasına rağmen test finished çıktığı görülebilir. TestEqual ise çıkmadı. Aynı şekilde t.SkipNow da bulunur. Mevcut durumu SKIP olarak işaretler. Ardından çalışmayı durdurur. Bir sonraki test turunda çalışmaya devam eder.
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内部使用的是t.Fail()
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 {
// Fatalf内部使用的是t.FailNow()
t.Fatalf("Sum(%d,%d) expected %t,actual is %t", a, b, expected, actual)
}
t.Log("test finished")
}Testi çalıştırırken, test sayısını 2 olarak değiştirin
$ 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.468sYukarıdaki örnekte son satırda test finished çıktısı vardır. Testin bittiğini belirtmek için kullanılır. Aslında t.Cleanup kullanarak bir sonlandırma fonksiyonu kaydedebilirsiniz. Bu fonksiyon test durumu bittiğinde çalıştırılır. Aşağıdaki gibi.
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)
}
}Testi çalıştırdıktan sonra çıktı aşağıdaki gibidir
$ 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
t.Helper() aracılığıyla mevcut fonksiyon yardım fonksiyonu olarak işaretlenebilir. Yardım fonksiyonu çalıştırılacak ayrı bir test durumu olarak kullanılmaz. Günlük kaydederken çıktı satır numarası da yardım fonksiyonunu çağıranın satır numarasıdır. Bu şekilde günlük analizinde konumlandırma daha doğru olur. Gereksiz diğer bilgilerden kaçınır. Örneğin yukarıdaki t.Cleanup örneği yardım fonksiyonu olarak değiştirilebilir. Aşağıdaki gibi.
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)
}
}Testi çalıştırdıktan sonra çıktı bilgileri aşağıdaki gibidir. Önceki ile farkı, test finished satır numarasının çağıranın satır numarası olmasıdır.
$ 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.464s::: ipucu
Yukarıdaki işlemler sadece ana testte yapılabilir. Yani doğrudan çalıştırılan test durumu. Alt testte kullanılırsa panic oluşur.
:::
Alt Test
Bazı durumlarda, bir test durumunda başka test durumlarını test etmeniz gerekebilir. Bu iç içe test durumlarına genellikle alt test denir. t.Run() metodu aracılığıyla. Bu metodun imzası aşağıdaki gibidir
// Run metodu alt testi çalıştırmak için yeni bir goroutine açar. f fonksiyonu çalıştıktan sonra bloke olur ve bekler
// Dönüş değeri testin geçip geçmediğidir
func (t *T) Run(name string, f func(t *T)) boolAşağıda bir örnek bulunmaktadır
func TestTool(t *testing.T) {
t.Run("tool.Sum(10,101)", TestSum)
t.Run("tool.Equal(10,101)", TestEqual)
}Çalıştırdıktan sonra sonuç aşağıdaki gibidir
$ 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.449sÇıktıdan açıkça üst-alt hiyerarşik yapıyı görebilirsiniz. Yukarıdaki örnekte ilk alt test bitmeden ikinci alt test çalıştırılmaz. t.Parallel() kullanarak test durumunu paralel çalıştırılabilir olarak işaretleyebilirsiniz. Bu şekilde çıktı sırası belirsiz olacaktır.
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")
}Testi çalıştırdıktan sonra çıktı aşağıdaki gibidir
$ 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.444sTest sonucundan açıkça bir bloke bekleme süreci olduğu görülebilir. Test durumlarını eşzamanlı çalıştırırken, yukarıdaki örnek kesinlikle normal şekilde yapılamaz. Çünkü sonraki kodun senkron çalışması garanti edilemez. Bu durumda bir t.Run() daha iç içe yerleştirebilirsiniz. Aşağıdaki gibi
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")
}Tekrar çalıştırın, normal çalıştırma sonucunu görebilirsiniz.
$ 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.450sTablo Stili
Yukarıdaki birim testte, test girdi verileri manuel olarak bildirilen değişkenlerdir. Veri miktarı küçük olduğunda sorun yoktur. Ancak birden fazla veri grubunu test etmek istediğinizde, artık değişken bildirerek test verisi oluşturmak mümkün değildir. Bu nedenle genel durumda mümkün olduğunca yapı slice formunu kullanın. Yapı geçici olarak bildirilen anonim yapıdır. Çünkü bu kodlama stili tablo gibi göründüğünden, table-driven olarak adlandırılır. Aşağıda bir örnek gösterilecektir. Bu manuel olarak birden fazla değişken bildirerek test verisi oluşturan bir örnektir. Birden fazla veri grubu varsa直觀 değildir. Bu nedenle tablo stiline değiştirin
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)
}
}Değiştirildikten sonra kod aşağıdaki gibidir
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)
}
}
}Bu şekilde test verisi daha直觀 görünür.
Kıyaslama Testi
Kıyaslama testi performans testi olarak da adlandırılır. Genellikle programın bellek kullanımı, CPU kullanımı, çalıştırma süresi vb. performans göstergelerini test etmek için kullanılır. Kıyaslama testi için, test dosyası genellikle bench_test.go ile biter. Test durumu fonksiyonu BenchmarkXXXX formatında olmalıdır.
Aşağıda string birleştirme örneğinin performans karşılaştırmasını kıyaslama testi örneği olarak kullanalım. Önce /tool/strConcat.go dosyasını oluşturun. Herkesin bildiği gibi doğrudan string kullanarak + birleştirme performansı çok düşüktür. strings.Builder kullanmak ise çok daha iyidir. /tool/strings.go dosyasında iki fonksiyon oluşturun, iki şekilde string birleştirme yapın.
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)
}
}Ardından test dosyası /tool_test/bench_tool_test.go oluşturun, kod aşağıdaki gibidir
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)
}
}Test komutunu çalıştırın. Komutta detaylı günlük ve bellek analizi açılmıştır. Kullanılan CPU çekirdek sayısı listesi belirtilmiştir. Ve her test durumu iki tur çalıştırılır. Çıktı aşağıdaki gibidir
$ 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.381sAşağıda kıyaslama testinin çıktı sonucunu açıklayalım. goos çalışan işletim sistemini temsil eder. goarh CPU mimarisini temsil eder. pkg testin bulunduğu pakettir. cpu CPU ile ilgili bazı bilgilerdir. Aşağıda her test durumunun sonucu her kıyaslama testinin adı ile ayrılır. İlk sütun BenchmarkConcatDirect-2 içindeki 2 kullanılan CPU çekirdek sayısını temsil eder. İkinci sütundaki 4 koddaki b.N boyutunu temsil eder. Yani kıyaslama testindeki döngü sayısıdır. Üçüncü sütun 277771375 ns/op her döngünün tükettiği süreyi temsil eder. ns nanosaniyedir. Dördüncü sütun 4040056736 B/op her döngünün tahsis ettiği bellek byte boyutunu temsil eder. Beşinci sütun 10000 allocs/op her döngünün bellek tahsis sayısını temsil eder.
Açıkça, test sonucuna göre, strings.Builder kullanmanın performansı + ile string birleştirmekten çok daha yüksektir. Sezgisel veri karşılaştırması ile performansı kıyaslamak kıyaslama testinin amacıdır.
benchstat
benchstat açık kaynaklı bir performans testi analiz aracıdır. Yukarıdaki performans testi örnek sayısı sadece iki gruptur. Örnek sayısı arttığında manuel analiz çok zaman alıcı ve zahmetli olur. Bu araç performans analizi sorunlarını çözmek için doğmuştur.
Önce bu aracı indirmeniz gerekir
$ go install golang.org/x/perf/benchstatKıyaslama testini iki kez çalıştırın. Bu sefer örnek sayısını 5 olarak değiştirin. Ve sırasıyla old.txt ve new.txt dosyalarına çıktı alın. Karşılaştırma için. İlk çalıştırma sonucu
$ 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.742sİkinci çalıştırma sonucu
$ 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.387sArdından benchstat ile karşılaştırın
$ 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 equalSonuçtan benchstat'in bunu üç gruba ayırdığı görülebilir. Sırasıyla süre, bellek kullanımı ve bellek tahsis sayısı. geomean ortalama değerdir. p örneğin anlamlılık seviyesidir. Kritik aralık genellikle 0.05'tir. 0.05'ten yüksekse çok güvenilir değildir. Verilerden birini alın:
│ sec/op │ sec/op vs base │
ConcatDirect-4 894.7m ± ∞ ¹ 1123.2m ± ∞ ¹ +25.53% (p=0.008 n=5)old çalıştırma süresinin 894.7ms, new çalıştırma süresinin 1123.2ms olduğu görülebilir. Buna göre %25.53 süre artışı oldu.
Bulanık Test
Bulanık test GO1.18'de tanıtılan yeni bir özelliktir. Birim test ve kıyaslama testinin bir geliştirmesidir. Farkı, önce ikisinin test verilerinin geliştirici tarafından manuel olarak yazılması gerekirken, bulanık test corpus kullanarak rastgele test verisi üretebilir. Go'daki bulanık test hakkında daha fazla kavram öğrenmek için Go Fuzzing adresine gidebilirsiniz. Bulanık testin avantajı, sabit test verilerine kıyasla, rastgele verinin programın sınır koşullarını daha iyi test edebilmesidir. Aşağıda resmi öğretici örneğini kullanarak açıklayalım. Bu sefer test edilecek bir string ters çevirme fonksiyonu. Önce /tool/strings.go dosyasını oluşturun, aşağıdaki kodu yazın
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)
}Bulanık test dosyası /tool_test/fuzz_tool_test.go oluşturun, aşağıdaki kodu yazın
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)
}
})
}Bulanık testte, önce corpus tohum kütüphanesine veri eklemeniz gerekir. Örnekte f.Add() kullanarak ekleriz. Bu sonraki rastgele test verisi oluşturmaya yardımcı olur. Ardından f.Fuzz(fn) kullanarak test edin. Fonksiyon imzası aşağıdaki gibidir:
func (f *F) Fuzz(ff any)
func (f *F) Add(args ...any)fn birim test fonksiyon mantığına benzer. Fonksiyonun ilk girdisi t *testing.T olmalıdır. Ardından oluşturmak istediğiniz parametreler gelir. Girilen string öngörülemez olduğundan, iki kez ters çevirme yöntemi kullanarak doğrulama yapıyoruz. Aşağıdaki komutu çalıştırın
$ 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.539sParametre -fuzz içermediğinde, rastgele test verisi üretilmez. Sadece corpus'taki veriler test fonksiyonuna girilir. Sonuçtan tüm testlerin geçtiği görülebilir. Bu şekilde kullanmak birim test ile aynıdır. Ancak aslında sorunludur. Aşağıda -fuzz parametresini ekleyerek tekrar çalıştıralım.
$ 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.697s::: ipucu
Bulanık testte başarısız olan durumlar mevcut test klasörü altındaki testdata dizinindeki belirli bir corpus dosyasına çıktı alınır. Örneğin yukarıdaki örnekte
Failing input written to testdata\fuzz\FuzzReverse\d856c981b6266ba2
To re-run:
go test -run=FuzzReverse/d856c981b6266ba2testdata\fuzz\FuzzReverse\d856c981b6266ba2 çıktı corpus dosya yoludur. Dosya içeriği aşağıdaki gibidir
go test fuzz v1
string("𐑄"):::
Bu sefer geçmediği görülebilir. Nedeni string ters çevrildikten sonra utf8 formatı olmayan hale gelmesidir. Bu nedenle bulanık test ile bu sorun tespit edildi. Bazı karakterlerin kapladığı bir byte'tan fazla olduğundan, byte birimi olarak ters çevrilirse kesinlikle anlamsız olur. Bu nedenle test edilecek kaynak kodu aşağıdaki gibi değiştirin. String'i []rune'a dönüştürün. Bu şekilde yukarıdaki sorunun oluşmasını önleyebilirsiniz.
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)
}Ardından doğrudan bir önceki bulanık test başarısız durumunu çalıştırın
$ 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.033sBu sefer testi geçtiği görülebilir. Tekrar bulanık test çalıştırarak sorun olup olmadığına bakalım
$ 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.184sTekrar hata olduğu görülebilir. Bu sefer sorun string'i iki kez ters çevirdikten sonra eşit olmaması. Orijinal karakter \xe4, beklenen sonuç 4ex\. Ancak sonuç anlamsız. Aşağıdaki gibi
func main() {
fmt.Println("\xe4")
fmt.Println([]byte("\xe4"))
fmt.Println([]rune("\xe4"))
fmt.Printf("%q\n", "\xe4")
fmt.Printf("%x\n", "\xe4")
}Çalıştırma sonucu
[65533]
"\xe4"
e4Nedeni \xe4'ün bir byte'ı temsil etmesi. Ancak geçerli bir UTF-8 dizisi değil (UTF-8 kodlamasında \xe4 üç byte karakterin başlangıcıdır. Ancak sonraki iki byte eksik). []rune'a dönüştürüldüğünde, Golang otomatik olarak tek Unicode karakter []rune{"\uFFFD"} haline getirir. Ters çevrildikten sonra hala []rune{"\uFFFD"}'dir. string'e geri dönüştürüldüğünde bu Unicode karakter tekrar UTF-8 kodlaması \xef\xbf\xbd ile değiştirilir. Bu nedenle bir çözüm girilen utf8 olmayan string ise, doğrudan hata döndürmektir:
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
}Test kodu da biraz değiştirilmelidir
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)
}
})
}Ters çevirme fonksiyonu error döndürdüğünde, testi atla. Tekrar bulanık test çalıştır
$ 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.789sArdından bu sefer nispeten tam bir bulanık test çıktı günlüğü alabilirsiniz. Bazı kavramların açıklamaları aşağıdaki gibidir:
- elapsed: Bir tur tamamlandıktan sonra geçen süre
- execs: Çalıştırılan girdi toplam sayısı. 297796/sec saniyede kaç girdi olduğunu gösterir
- new interesting: Test sırasında, corpus'a eklenen "ilginç" girdilerin toplam sayısı. (İlginç girdi, kod kapsamını mevcut corpus'un kapsayabileceği aralığın ötesine genişletebilen girdiyi ifade eder. Kapsam alanı sürekli genişledikçe, büyüme eğilimi genel olarak yavaşlar)
::: ipucu
-fuzztime parametresi ile süre sınırlanmazsa, bulanık test sonsuza kadar çalışmaya devam eder.
:::
Tür Desteği
Go Fuzz'da desteklenen türler aşağıdaki gibidir:
string,[]byteint,int8,int16,int32/rune,int64uint,uint8/byte,uint16,uint32,uint64float32,float64bool
