Goアプリケーションでテストを作成するためのヒント

当社の開発スタックにはGo言語があります。また、Goで記述されたアプリケーションの単体テストを作成するときに、問題が発生する場合があります。この記事では、テストを作成するときに考慮に入れるいくつかのポイントについて説明します。それらを例とともにどのように使用できるかを見てみましょう。





開発時にインターフェースを使用します

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