Conseils pour rédiger des tests dans les applications Go

Notre société a un langage Go dans la pile de développement. Et parfois, lors de l'écriture de tests unitaires pour des applications écrites en Go, nous avons des difficultés. Dans cet article, nous parlerons de certains des points que nous prenons en compte lors de l'écriture des tests. Voyons comment ils peuvent être utilisés avec des exemples.





Nous utilisons des interfaces lors du développement

, . . , . , , 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