测试处理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
}

进行单元测试

我认为对于上述代码的单元测试,有两种主要方法可供选择。

    1. 创建一个包装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先生!

广告
将在 10 秒后关闭
bannerAds