测试处理redis的golang代码
首先
這篇文章是Go3 AdventCalendar的第一天的文章。
关于像处理Redis和MySQL这样的技术实现,我们经常看到使用接口抽象化的代码单元测试技巧,但是关于测试技术实现本身的方法,我觉得并没有看到太多的技巧。所以本次我希望可以介绍一下在这种情况下的测试方法。
顺便一提,此次介绍的代码已经在https://github.com/hgsgtk/go-snippets/pull/16 上公开了。
.
├── docker-compose.yml
├── go.mod
├── go.sum
├── main.go
├── persistence
│ └── kvs
│ ├── client.go
│ └── client_test.go
└── testhelper
└── mock_redis_client.go
这次在实现redis客户端时,我们使用了以下的库。
编写处理Redis的实现
我們將在代碼中提供詳細說明,不過以獲取值的實現為例進行討論。
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并创建一个接口,以便可以替换为模拟版本。
- 准备一个用于单元测试的redis服务器。
作为选择选项1的例子,可以在 go-redis/redis 的问题中找到详情。
https://github.com/go-redis/redis/issues/332
本次我們將使用選項2進行操作。為此,我們將使用以下的程式庫:
https://github.com/alicebob/miniredis
miniredis是一个用于创建用于单元测试的redis模拟服务器的库。我们将使用它来编写测试。
测试用的Redis服务器
我們將創建一個用於準備測試用Redis服務器的測試輔助工具。只是為了確定,創建輔助工具需要傳入*testing.T,並且在上面寫上t.Helper()。通過創建測試輔助工具,可以更容易地找出使用它進行測試時的錯誤位置。
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
}
进行测试
使用之前创建的测试助手,编写测试。准备一个*redis.Client到模拟服务器,并将其传递给待测试的函数即可完成。
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相关的代码部分,并介绍了如何编写相关测试。
明天是@takochuu先生!