Sök…
Introduktion
Go har sina egna testanläggningar som har allt som krävs för att köra tester och riktmärken. Till skillnad från i de flesta andra programmeringsspråk finns det ofta inget behov av en separat testram, även om vissa finns.
Grundläggande test
main.go
:
package main
import (
"fmt"
)
func main() {
fmt.Println(Sum(4,5))
}
func Sum(a, b int) int {
return a + b
}
main_test.go
:
package main
import (
"testing"
)
// Test methods start with `Test`
func TestSum(t *testing.T) {
got := Sum(1, 2)
want := 3
if got != want {
t.Errorf("Sum(1, 2) == %d, want %d", got, want)
}
}
För att köra testet använder du bara kommandot go test
:
$ go test
ok test_app 0.005s
Använd -v
flaggan för att se resultaten från varje test:
$ go test -v
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
ok _/tmp 0.000s
Använd sökvägen ./...
att testa underkataloger rekursivt:
$ go test -v ./...
ok github.com/me/project/dir1 0.008s
=== RUN TestSum
--- PASS: TestSum (0.00s)
PASS
ok github.com/me/project/dir2 0.008s
=== RUN TestDiff
--- PASS: TestDiff (0.00s)
PASS
Kör ett särskilt test:
Om det finns flera tester och du vill köra ett specifikt test kan det göras så här:
go test -v -run=<TestName> // will execute only test with this name
Exempel:
go test -v run=TestSum
Benchmark-test
Om du vill mäta riktmärken lägger du till en testmetod som denna:
sum.go
:
package sum
// Sum calculates the sum of two integers
func Sum(a, b int) int {
return a + b
}
sum_test.go
:
package sum
import "testing"
func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Sum(2, 3)
}
}
För att få ett enkelt riktmärke:
$ go test -bench=.
BenchmarkSum-8 2000000000 0.49 ns/op
ok so/sum 1.027s
Tabelldrivna enhetstester
Denna typ av testning är populär teknik för testning med fördefinierade ingångs- och utgångsvärden.
Skapa en fil som heter main.go
med innehåll:
package main
import (
"fmt"
)
func main() {
fmt.Println(Sum(4, 5))
}
func Sum(a, b int) int {
return a + b
}
När du kör det med ser du att utgången är 9
. Även om Sum
funktionen ser ganska enkel ut, är det en bra idé att testa din kod. För att göra detta skapar vi en annan fil med namnet main_test.go
i samma mapp som main.go
, som innehåller följande kod:
package main
import (
"testing"
)
// Test methods start with Test
func TestSum(t *testing.T) {
// Note that the data variable is of type array of anonymous struct,
// which is very handy for writing table-driven unit tests.
data := []struct {
a, b, res int
}{
{1, 2, 3},
{0, 0, 0},
{1, -1, 0},
{2, 3, 5},
{1000, 234, 1234},
}
for _, d := range data {
if got := Sum(d.a, d.b); got != d.res {
t.Errorf("Sum(%d, %d) == %d, want %d", d.a, d.b, got, d.res)
}
}
}
Som ni ser skapas ett segment av anonyma strukturer, var och en med en uppsättning input och det förväntade resultatet. Detta gör att ett stort antal testfall kan skapas tillsammans på ett ställe och sedan köras i en slinga, vilket minskar kodrepetitionen och förbättrar tydligheten.
Exempelstester (självdokumenterande test)
Denna typ av tester ser till att din kod sammanställs korrekt och kommer att visas i den genererade dokumentationen för ditt projekt. Utöver detta kan exempletesterna hävda att ditt test ger rätt resultat.
sum.go
:
package sum
// Sum calculates the sum of two integers
func Sum(a, b int) int {
return a + b
}
sum_test.go
:
package sum
import "fmt"
func ExampleSum() {
x := Sum(1, 2)
fmt.Println(x)
fmt.Println(Sum(-1, -1))
fmt.Println(Sum(0, 0))
// Output:
// 3
// -2
// 0
}
För att köra ditt test, kör go test
i mappen som innehåller dessa filer ELLER lägg de två filerna i en undermapp med namnet sum
och sedan från modermappen köra go test ./sum
. I båda fallen får du en utgång som liknar detta:
ok so/sum 0.005s
Om du undrar hur det här testar din kod är här en annan exempelfunktion som faktiskt misslyckas med testet:
func ExampleSum_fail() {
x := Sum(1, 2)
fmt.Println(x)
// Output:
// 5
}
När du kör go test
får du följande utgång:
$ go test
--- FAIL: ExampleSum_fail (0.00s)
got:
3
want:
5
FAIL
exit status 1
FAIL so/sum 0.006s
Om du vill se dokumentationen för sum
paket - bara köra:
go doc -http=:6060
och navigera till http: // localhost: 6060 / pkg / FOLDER / sum / , där FOLDER är den mapp som innehåller sum
paketet (i detta exempel so
). Dokumentationen för summanivån ser ut så här:
Testar HTTP-förfrågningar
main.go:
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func fetchContent(url string) (string, error) {
res, err := http.Get(url)
if err != nil {
return "", nil
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func main() {
url := "https://example.com/"
content, err := fetchContent(url)
if err != nil {
log.Fatal(err)
}
fmt.Println("Content:", content)
}
main_test.go:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func Test_fetchContent(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "hello world")
}))
defer ts.Close()
content, err := fetchContent(ts.URL)
if err != nil {
t.Error(err)
}
want := "hello world"
if content != want {
t.Errorf("Got %q, want %q", content, want)
}
}
Ställ in / återställ håravfunktion i tester
Det här exemplet visar hur man kan förlora ett funktionssamtal som är irrelevant för vårt enhetstest och sedan använda defer
för att tilldela återlämnat funktionssamtal till sin ursprungliga funktion.
var validate = validateDTD
// ParseXML parses b for XML elements and values, and returns them as a map of
// string key/value pairs.
func ParseXML(b []byte) (map[string]string, error) {
// we don't care about validating against DTD in our unit test
if err := validate(b); err != nil {
return err
}
// code to parse b etc.
}
func validateDTD(b []byte) error {
// get the DTD from some external storage, use it to validate b etc.
}
I vårt enhetstest,
func TestParseXML(t *testing.T) {
// assign the original validate function to a variable.
originalValidate = validate
// use the mockValidate function in this test.
validate = mockValidate
// defer the re-assignment back to the original validate function.
defer func() {
validate = originalValidate
}()
var input []byte
actual, err := ParseXML(input)
// assertion etc.
}
func mockValidate(b []byte) error {
return nil // always return nil since we don't care
}
Testning med funktionen setUp och tearDown
Du kan ställa in en SetUp- och tearDown-funktion.
- En setUp-funktion förbereder din miljö för tester.
- En tearDown-funktion gör en rollback.
Detta är ett bra alternativ när du inte kan modifiera din databas och du måste skapa ett objekt som simulerar ett objekt med databas eller behöver initiera en konfiguration i varje test.
Ett dumt exempel skulle vara:
// Standard numbers map
var numbers map[string]int = map[string]int{"zero": 0, "three": 3}
// TestMain will exec each test, one by one
func TestMain(m *testing.M) {
// exec setUp function
setUp("one", 1)
// exec test and this returns an exit code to pass to os
retCode := m.Run()
// exec tearDown function
tearDown("one")
// If exit code is distinct of zero,
// the test will be failed (red)
os.Exit(retCode)
}
// setUp function, add a number to numbers slice
func setUp(key string, value int) {
numbers[key] = value
}
// tearDown function, delete a number to numbers slice
func tearDown(key string) {
delete(numbers, key)
}
// First test
func TestOnePlusOne(t *testing.T) {
numbers["one"] = numbers["one"] + 1
if numbers["one"] != 2 {
t.Error("1 plus 1 = 2, not %v", value)
}
}
// Second test
func TestOnePlusTwo(t *testing.T) {
numbers["one"] = numbers["one"] + 2
if numbers["one"] != 3 {
t.Error("1 plus 2 = 3, not %v", value)
}
}
Ett annat exempel skulle vara att förbereda databasen för att testa och göra rollback
// ID of Person will be saved in database
personID := 12345
// Name of Person will be saved in database
personName := "Toni"
func TestMain(m *testing.M) {
// You create an Person and you save in database
setUp(&Person{
ID: personID,
Name: personName,
Age: 19,
})
retCode := m.Run()
// When you have executed the test, the Person is deleted from database
tearDown(personID)
os.Exit(retCode)
}
func setUp(P *Person) {
// ...
db.add(P)
// ...
}
func tearDown(id int) {
// ...
db.delete(id)
// ...
}
func getPerson(t *testing.T) {
P := Get(personID)
if P.Name != personName {
t.Error("P.Name is %s and it must be Toni", P.Name)
}
}
Visa kodtäckning i HTML-format
Kör go test
som normalt, men med coverprofile
flaggan. Använd sedan go tool
att se resultaten som HTML.
go test -coverprofile=c.out
go tool cover -html=c.out