poniedziałek, 19 marca 2012

Gopher test

W standardowej bibliotece Go nie ma klasycznej wersji Unit Testów. Nie załamujcie się przeto wyznawcy TDD, nie popadajcie w marazm, niech nie pochłonie was rozpacz, albowiem istnieje światełko nadziei dla Was. Wielki i wszechmocny Gopher Was nie opuści.

Daje on narzędzia do prostego testowania i uruchamiania tychże testów. Są proste, a jednocześnie dość efektowne, jak cały język.

W niniejszym wpisie postaram się wyłożyć prosty przykład Gophowego pakietu wraz z klasą i przykładami testów.

Po pierwsze, tworzymy pakiet strdict, a w nim tworzymy Gopherowy typ Dict wraz z jego metodami.
Wszystko to zapisujemy w pliki strdict.go w katalogu GOPATH/src/strdict.

--------------
package strdict

import (
    "fmt"
)
// new type
type Dict map[string]string

// get value with key
// if key not present then raise panic
func(m Dict) Get(key string) string {
    k, ok := m[key]
    if !ok{
        panic(fmt.Sprintf("No key '%s' in Dict", key))
    }
    return k
}
// get value with key
// if key not present then return def
func(m Dict) GetDef(key, def string) string {
    k, ok := m[key]
    if !ok{
        return def
    }
    return k
}

// check if m has key value
func (m Dict) HasKey(key string) bool{
    _, ok := m[key]
    return ok
}
// set value with key
func(m Dict) Set(key, value string)  {
    m[key] = value
} 
// print all keys and values 
func (m Dict) Print(){
    for k, v := range(m){
        fmt.Println(k, "  ", v)
    }
}

--------------

Teraz tworzymy plik z testami do pakietu. Nazywamy go strdict_test.go i zapisujemy w tym samym katalogu co strdict.go.

--------------
package strdict

import (
    "testing"
    "fmt"
)

func TestSet(t *testing.T){
    m1 := Dict{}
    m2 := Dict{}
    m1.Set("a", "a")
    m1.Set("a", "b")
    m1.Set("a", "v")
    m1.Set("d", "f")
    m2.Set("a", "zzz")
    if m1["a"] != "v"{
        t.Error("Set error. No value in map")
    }
}

func TestGet(t *testing.T){
    m := make(Dict)
    m["a"] = "b"
    m["b"] = "c"
    m["c"] = "d"
    if m.Get("a") != "b"{
        t.Error("Get error. Wrong value for key ")
    }
    defer func(){
        if err := recover(); err == nil{
            t.Error("Get error. Getting non existing value should raise panic")
        }
    }()
    m.Get("non existing key")
}

func TestHasKey(t *testing.T){
    m := make(Dict)
    m["a"] = "a"
    m["b"] = "b"
    if !m.HasKey("a"){
        t.Error("Error checking existing key")
    }
    if m.HasKey("c"){
        t.Error("Error checking none existing key")
    }
}
func ExampleGet(){
    m := Dict{}
    m["a"] = "123"
    fmt.Println(m.Get("a"))
    //Output:
    // 123
}

func BenchmarkGet(b *testing.B){
    b.StopTimer()
    m := Dict{}
    m["aa"] = "aa"
    b.StartTimer()
    for i := 0; i < b.N; i++ {
        _ =m.Get("aa")
    }
}

func BenchmarkSet(b *testing.B){
    b.StopTimer()
    m := Dict{}
    b.StartTimer()
    for i := 0; i < b.N; i++ {
        m.Set("aa", "1")
    }
}
    

--------------

Parę słów objaśniających testy:

TestSet, TestGet, TestHasKey - klasyczne przykłady testów jednostkowych. Ich nazwy muszą rozpoczynać się od Test... 
W tym jednak wypadku nie mamy instrukcji assert, lecz używamy instrukcji if i t.Error(). 

ExampleGet - ta funkcja testująca porównuje wyjście standardowe, czyli w tym przypadku wynik instrukcji fmt.Println ze spodziewanym wyjściem które umieściliśmy w komentarzu. Jeśli będą różne wówczas testy zakończą się niepowodzeniem. Musi się zaczynać od Example.

Innymi ciekawymi przykładami są funkcje BenchmarkSet i BenchmarkGet. Mierzą one średni czas wykonania operacji.



Sposób wywołania testów w konsoli:

----------------------------------------------------------------------------
user@user-home:~$ go test strdict
ok   strdict 0.006s
----------------------------------------------------------------------------

Sposób wywołania testów wraz z benchmarkami:

----------------------------------------------------------------------------
go test -test.bench="." strdict
PASS
BenchmarkGet 20000000        109 ns/op
BenchmarkSet 20000000        114 ns/op
ok   strdict 4.718s
----------------------------------------------------------------------------

Brak komentarzy: