출처: https://qiita.com/higasgt/items/145ad74c83fcb3dcf6c2
Redis와 MySQL을 다루는 기술적 구현은 interface로 추상화 하는 등 사용 코드 측의 단위 테스트 노하우는 잘 볼 수 있지만, 기술적 구현 자체를 테스트하는 방법은 노하우가 별로 보이지 않는다 라고 생각해서, 이번에는 이런 경우 테스트 방법을 소개하려고 한다.
또한, 이번 소개하는 코드는 https://github.com/higasgt/go-snippets/pull/16 에서 공개하고 있다.
.
├─ docker-compose.yml
├─ go.mod
├─ go.sum
├─ main.go
├─ 끈기
│ └── kvs
│ ├── client.go
│ └── client_test.go
└── testhelper
└── mock_redis_client.go
이번에는 redis-client 구현있어서 다음의 라이브러리를 사용하고 있다.
https://github.com/go-redis/redis
redis를 다루기 구현
자세한 설명은 코드 내에 기재 하지만, 토큰 값을 가져 오도록 하는 구현을 예를 가정한다.
persistence/kvs/client.go
package kvs import ( "strconv" "time" "github.com/go-redis/redis" "github.com/pkg/errors" ) // 값 취득 때 없던 경우는 redis.Nil 이 error 타입으로 반환된다. // 에러 핸들링을 위해 internal한 package 에 정의한다 const Nil = redis.Nil // New create instance of redis.Client func New(dsn string) (*redis.Client, error) { client := redis.NewClient(&redis.Options{ Addr: dsn, Password: "", DB: 0, }) if err := client.Ping().Err(); err != nil { return nil, errors.Wrapf(err, "failed to ping redis server") } return client, nil } // 이번은 토큰 베이스에서 값을 얻도록 하는 예를 구현한다 const ( tokenKey = "TOKEN_" tokenDuration = time.Second * 3600 ) // SetToken set token and value func SetToken(cli *redis.Client, token string, userID int) error { if err := cli.Set(tokenKey+token, userID, tokenDuration).Err(); err != nil { return err } return nil } // SetToken set token and value func GetIDByToken(cli *redis.Client, token string) (int, error) { v, err := cli.Get(tokenKey+token).Result() if err != nil { return 0, errors.Wrapf(err, "failed to get id from redis by token") } id, err := strconv.Atoi(v) if err != nil { return 0, errors.Wrapf(err, "failed to convert string to int") } return id, nil } |
유닛 테스트 하기
위의 코드를 단위 테스트 하는 것은 크게 두 가지 방법이 있을 거라고 생각한다.
*redis.Client를 wrap 하여 interface를 작성하고 mock 교체하도록 한다
유닛 테스트용 redis server를 준비한다
옵션 1을 선택하는 경우의 예로서 go-redis/redis의 issue에 소개되어 있다.
https://github.com/go-redis/redis/issues/332
이번에는 선택 2를 하고 있다. 이것은 다음의 라이브러리를 사용한다.
https://github.com/alicebob/miniredis
miniredis는 단위 테스트 redis mock 서버를 만들수 있는 라이브러리이다. 이것을 사용하여 테스트를 만들어간다.
테스트 redis 서버
테스트용 redis 서버를 준비하는 테스트 헬퍼를 만든다. 만
일을 위해 설명이지만, 테스트 도우미를 만들기 위해, *testing.T 전달에, t.Helper() 라고 쓴다.
테스트 도우미로 만드는 것으로,이를 사용하여 테스트 오류 부분을 쉽게 이해할 수 있다.
testhelper/mock_redis_client.go
package testhelper import ( "testing" "github.com/alicebob/miniredis" "github.com/go-redis/redis" ) func NewMockRedis(t *testing.T) *redis.Client { t.Helper() // redis 서버를 만든다 s, err := miniredis.Run() if err != nil { t.Fatalf("unexpected error while createing test redis server '%#v'", err) } // *redis.Client를 준비 client := redis.NewClient(&redis.Options{ Addr: s.Addr(), Password: "", DB: 0, }) return client } |
테스트를 하기
위에서 만든 테스트 도우미를 사용하여 테스트를 쓴다. mock 서버에 대한 *redis.Client을 준비하고 이것을 테스트 할 함수에 전달하면 끝이다.
persistence/kvs/client_test.go
package kvs_test import ( "testing" "time" "github.com/higasgt/go-snippets/redis-cli/persistence/kvs" "github.com/higasgt/go-snippets/redis-cli/testhelper" ) func TestSetToken(t *testing.T) { client := testhelper.NewMockRedis(t) if err := kvs.SetToken(client, "test", 1); err != nil { t.Fatalf("unexpected error while SetToken '%#v'", err) } actual, err := client.Get("TOKEN_test").Result() if err != nil { t.Fatalf("unexpected error while get value '%#v'", err) } if expected := "1"; expected != actual { t.Errorf("expected value '%s', actual value '%s'", expected, actual) } } func TestGetIDByToken(t *testing.T) { client := testhelper.NewMockRedis(t) if err := client.Set("TOKEN_test", 1, time.Second*1000).Err(); err != nil { t.Fatalf("unexpected error while set test data '%#v'", err) } actual, err := kvs.GetIDByToken(client, "test") if err != nil { t.Fatalf("unexpected error while GetIDByToken '%#v'", err) } if expected := 1; expected != actual { t.Errorf("expected value '%#v', actual value '%#v'", expected, actual) } } |
이제 Redis를 다루고 있는 코드의 테스트를 쓸 수 있다.
결론
이번에는 redis를 다루는 코드 부분 구현을 하고, 이것에 대한 테스트를 작성하는 방법을 소개했다.