Dicas para escrever testes em aplicativos Go

Nossa empresa tem uma linguagem Go na pilha de desenvolvimento. E às vezes, ao escrever testes de unidade para aplicativos escritos em Go, temos dificuldades. Neste artigo, falaremos sobre alguns dos pontos que levamos em consideração ao escrever testes. Vamos dar uma olhada em como eles podem ser usados ​​com exemplos.





Usamos interfaces ao desenvolver

, . . , . , , Redis - :





package yourpackage
 
import (
    "context"
 
    "github.com/go-redis/redis/v8"
)
 
func CheckLen(ctx context.Context, client *redis.Client, key string) bool {
    val, err := client.Get(ctx, key).Result()
    if err != nil {
   	 return false
    }
    return len(val) < 10
  }
      
      







package yourpackage
 
import (
    "context"
    "testing"
 
    "github.com/go-redis/redis/v8"
)
 
func TestCheckLen(t *testing.T) {
    ctx := context.Background()
    rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
    err := rdb.Set(ctx, "some_key", "value", 0).Err()
    if err != nil {
   	 t.Fatalf("redis return error: %s", err)
    }
 
    got := CheckLen(ctx, rdb, "some_key")
    if !got {
   	 t.Errorf("CheckLen return %v; want true", got)
    }
}
      
      



, Redis ? , Redis CI? Redis? — !





:





package yourpackage
 
import (
    "context"
 
    "github.com/go-redis/redis/v8"
)
 
type Storage interface {
    Set(ctx context.Context, key string, v interface{}) error
    Get(ctx context.Context, key string) (string, error)
}
 
type RedisStorage struct {
    Redis *redis.Client
}
 
func (rs *RedisStorage) Set(ctx context.Context, key string, v interface{}) error {
    return rs.Redis.Set(ctx, key, v, 0).Err()
}
 
func (rs *RedisStorage) Get(ctx context.Context, key string) (string, error) {
    return rs.Redis.Get(ctx, key).Result()
}
 
func CheckLen(ctx context.Context, storage Storage, key string) bool {
    val, err := storage.Get(ctx, key)
    if err != nil {
   	 return false
    }
    return len(val) < 10
}
      
      



, , , Redis Memcached. :





package yourpackage
 
import (
    "context"
    "testing"
)
 
type testRedis struct{}
 
func (t *testRedis) Get(ctx context.Context, key string) (string, error) {
    return "value", nil
}
func (t *testRedis) Set(ctx context.Context, key string, v interface{}) error {
    return nil
}
 
func TestCheckLen(t *testing.T) {
	   ctx := context.Background()
    storage := &testRedis{}
 
    got := CheckLen(ctx, storage, "some_key")
    if !got {
   	 t.Errorf("CheckLen return %v; want true", got)
    }
}
      
      



, . . . . mockery.





. :





mockery --recursive=true --inpackage --name=Storage
      
      



:





package yourpackage
import (
    "context"
    "testing"
 
    mock "github.com/stretchr/testify/mock"
)
 
func TestCheckLen(t *testing.T) {
    ctx := context.Background()
 
    storage := new(MockStorage)
    storage.On("Get", mock.Anything, "some_key").Return("value", nil)
 
    got := CheckLen(ctx, storage, "some_key")
    if !got {
   	 t.Errorf("CheckLen return %v; want true", got)
    }
      
      



, - , , Logrus.





package yourpackage
 
import (
    log "github.com/sirupsen/logrus"
)
 
func Minus(a, b int) int {
    log.Infof("Minus(%v, %v)", a, b)
    return a - b
}
 
func Plus(a, b int) int {
    log.Infof("Plus(%v, %v)", a, b)
    return a + b
}
 
func Mul(a, b int) int {
    log.Infof("Mul(%v, %v)", a, b)
    return a + b //  
}
      
      



:





package yourpackage
 
import "testing"
 
func TestPlus(t *testing.T) {
    a, b, expected := 3, 2, 5
    got := Plus(a, b)
    if got != expected {
   	 t.Errorf("Plus(%v, %v) return %v; want %v", a, b, got, expected)
    }
}
 
func TestMinus(t *testing.T) {
    a, b, expected := 3, 2, 1
    got := Minus(a, b)
    if got != expected {
   	 t.Errorf("Minus(%v, %v) return %v; want %v", a, b, got, expected)
    }
}
 
func TestMul(t *testing.T) {
    a, b, expected := 3, 2, 6
    got := Mul(a, b)
    if got != expected {
   	 t.Errorf("Mul(%v, %v) return %v; want %v", a, b, got, expected)
    }
}
      
      



, , :





time="2021-03-22T22:09:54+03:00" level=info msg="Plus(3, 2)"
time="2021-03-22T22:09:54+03:00" level=info msg="Minus(3, 2)"
time="2021-03-22T22:09:54+03:00" level=info msg="Mul(3, 2)"
--- FAIL: TestMul (0.00s)
	yourpackage_test.go:55: Mul(3, 2) return 5; want 6
FAIL
FAIL	gotest2/yourpackage 	0.002s
FAIL
      
      



, . , . :





package yourpackage
 
import (
    "io"
    "testing"
 
    "github.com/sirupsen/logrus"
)
 
type logCapturer struct {
    *testing.T
    origOut io.Writer
}
 
func (tl logCapturer) Write(p []byte) (n int, err error) {
    tl.Logf((string)(p))
    return len(p), nil
}
 
func (tl logCapturer) Release() {
    logrus.SetOutput(tl.origOut)
}
 
func CaptureLog(t *testing.T) *logCapturer {
    lc := logCapturer{T: t, origOut: logrus.StandardLogger().Out}
    if !testing.Verbose() {
   	 logrus.SetOutput(lc)
    }
    return &lc
}
 
func TestPlus(t *testing.T) {
    defer CaptureLog(t).Release()
    a, b, expected := 3, 2, 5
    got := Plus(a, b)
    if got != expected {
   	 t.Errorf("Plus(%v, %v) return %v; want %v", a, b, got, expected)
    }
}
 
func TestMinus(t *testing.T) {
    defer CaptureLog(t).Release()
    a, b, expected := 3, 2, 5
    got := Minus(a, b)
    if got != expected {
   	 t.Errorf("Minus(%v, %v) return %v; want %v", a, b, got, expected)
    }
}
 
func TestMul(t *testing.T) {
    defer CaptureLog(t).Release()
    a, b, expected := 3, 2, 5
    got := Mul(a, b)
    if got != expected {
   	 t.Errorf("Mul(%v, %v) return %v; want %v", a, b, got, expected)
    }
}
      
      



, , :





--- FAIL: TestMul (0.00s)
	yourpackage_test.go:16: time="2021-03-22T22:10:52+03:00" level=info msg="Mul(3, 2)"
	yourpackage_test.go:55: Mul(3, 2) return 5; want 6
FAIL
FAIL	gotest2/yourpackage 	0.002s
FAIL
      
      



Logrus, . , Zap , .





Go - . , . , , . , . 





, . . cover, :





$ go tool cover -help
Usage of 'go tool cover':
Given a coverage profile produced by 'go test':
    	go test -coverprofile=c.out
...
Display coverage percentages to stdout for each function:
    	go tool cover -func=c.out
      
      



:





$ go test -coverprofile=c.out ./...
ok  	gotestcover/minus   	0.001s  coverage: 100.0% of statements
?   	gotestcover/mul [no test files]
ok  	gotestcover/plus    	0.001s  coverage: 100.0% of statements
      
      



, 100 % . :





$ go tool cover -func=c.out
gotestcover/minus/minus.go:4:   Minus       	100.0%
gotestcover/plus/plus.go:4: 	Plus        	100.0%
total:                      	(statements)	100.0%
      
      



- . . , . , , . HTML-. , , , , . , :





go test -coverpkg=./... -coverprofile=c.out ./…
      
      



:





$ go tool cover -func=c.out
gotestcover/minus/minus.go:4:   Minus       	100.0%
gotestcover/mul/mul.go:4:   	Mul         	0.0%
gotestcover/plus/plus.go:4: 	Plus        	100.0%
total:                      	(statements)	66.7%
      
      



Go - . - -, , , Python, « ». 





, ? , . , . : 





func TestRunMain(t *testing.T) {
    	main()
}
      
      



, , . , . , . , . main



. main



, . web- graceful shutdown, , . web-, curl, . 





( https://gobyexample.com/http-servers):





package main
 
import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "time"
)
 
func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(w, "hello\n")
}
 
func headers(w http.ResponseWriter, req *http.Request) {
    for name, headers := range req.Header {
   	 for _, h := range headers {
   		 fmt.Fprintf(w, "%v: %v\n", name, h)
   	 }
    }
}
 
func main() {
    http.HandleFunc("/hello", hello)
    http.HandleFunc("/headers", headers)
 
    //   ,       
    //    ,    
    server := &http.Server{Addr: ":8090", Handler: nil}
    //     
    go func() {
   	 server.ListenAndServe()
    }()
 
    //        
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    server.Shutdown(ctx)
}
      
      



:





// +build testrunmain
 
package main
 
import "testing"
 
func TestRunMain(t *testing.T) {
    main()
}
      
      



+build testrunmain



, , tag. :





$ go test -v -tags testrunmain -coverpkg=./... -coverprofile=c.out  ./...
=== RUN   TestRunMain
      
      



curl:





$ curl 127.0.0.1:8090/hello
hello
      
      



, Ctrl+C:





$ go test -v -tags testrunmain -coverpkg=./... -coverprofile=c.out  ./...
=== RUN   TestRunMain
^C--- PASS: TestRunMain (100.92s)
PASS
coverage: 80.0% of statements in ./...
ok  	gobintest   	100.926s    	coverage: 80.0% of statements in ./…
      
      



, headers



:





$ go tool cover -func=c.out
gobintest/main.go:12:   hello       	100.0%
gobintest/main.go:16:   headers     	0.0%
gobintest/main.go:24:   main        	100.0%
total:              	(statements)	80.0%
      
      



, , Go. , . 





Go? : , , .








All Articles