출처: 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를 다루는 코드 부분 구현을 하고, 이것에 대한 테스트를 작성하는 방법을 소개했다.