在开发应用程序时,使用 Redis 是一种常见的方式来处理和存储数据。Redis 提供了五种常用的数据类型:String、List、Set、Hash 和 Sorted Set。除了这些数据类型之外,有时我们还需要在 Redis 中执行复杂的命令序列,这时可以使用 Lua 脚本来实现。在本篇博客中,我们将探讨如何在 Go 语言中使用 go-redis/redismock 来测试 Redis 常用的五种数据类型以及执行 Lua 脚本。
1. 简介
首先,让我们了解一下 go-redis/redismock 是什么。它是一个用于 Mock Redis 数据库的库,可以模拟 Redis 客户端的行为,无需实际连接到真实的 Redis 数据库。这样,我们可以在单元测试中更快速和可控地测试与 Redis 交互的代码。
2. 安装和设置
在开始之前,请确保您已经安装了 Go 环境,并配置好了 Go 的工作空间。
首先,我们需要安装 go-redis/redismock 和 go-redis/redis/v8:
1 2
| go get github.com/go-redis/redismock/v8 go get github.com/go-redis/redis/v8
|
3. 示例:测试 Redis String、List、Set、Hash 和 Sorted Set
String 类型
首先,我们将测试 Redis 中的 String 类型。我们创建一个 string_ops.go 文件,并实现相应的 Go 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package main
import ( "context" "fmt" "github.com/go-redis/redis/v8" )
func SetString(client *redis.Client, key, value string) error { return client.Set(context.Background(), key, value, 0).Err() }
func GetString(client *redis.Client, key string) (string, error) { return client.Get(context.Background(), key).Result() }
|
在这个示例中,我们编写了 SetString 和 GetString 函数,用于设置和获取 Redis 中的 String 类型数据。
接下来,我们编写单元测试来测试这两个函数。创建一个 string_ops_test.go 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
package main
import ( "testing"
"github.com/go-redis/redismock/v8" )
func TestSetAndGet_String(t *testing.T) { client, mock := redismock.NewClientMock()
key := "mykey" value := "myvalue"
mock.ExpectSet(key, value).SetVal("OK") mock.ExpectGet(key).SetVal(value)
err := SetString(client, key, value) if err != nil { t.Errorf("SetString failed: %s", err) }
result, err := GetString(client, key) if err != nil { t.Errorf("GetString failed: %s", err) }
if result != value { t.Errorf("unexpected value, got: %s, want: %s", result, value) }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
|
List 类型
接下来,我们测试 Redis 中的 List 类型。我们创建一个 list_ops.go 文件,并实现相应的 Go 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package main
import ( "context" "fmt" "github.com/go-redis/redis/v8" )
func ListPush(client *redis.Client, key string, values ...string) (int64, error) { return client.LPush(context.Background(), key, values).Result() }
func ListPop(client *redis.Client, key string) (string, error) { return client.LPop(context.Background(), key).Result() }
|
在这个示例中,我们编写了 ListPush 和 ListPop 函数,用于在 Redis 中添加和弹出 List 类型数据。
接下来,我们编写单元测试来测试这两个函数。创建一个 list_ops_test.go 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
package main
import ( "testing"
"github.com/go-redis/redismock/v8" )
func TestListPushAndPop(t *testing.T) { client, mock := redismock.NewClientMock()
key := "mylist"
values := []string{"item1", "item2", "item3"} mock.ExpectLPush(key, values).SetVal(3)
mock.ExpectLPop(key).SetVal("item3")
count, err := ListPush(client, key, values...) if err != nil { t.Errorf("ListPush failed: %s", err) }
if count != 3 { t.Errorf("unexpected count, got: %d, want: %d", count, 3) }
item, err := ListPop(client, key) if err != nil { t.Errorf("ListPop failed: %s", err) }
if item != "item3" { t.Errorf("unexpected item, got: %s, want: %s", item, "item3") }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
|
Set 类型
接下来,我们测试 Redis 中的 Set 类型。我们创建一个 set_ops.go 文件,并实现相应的 Go 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package main
import ( "context" "fmt" "github.com/go-redis/redis/v8" )
func SetAdd(client *redis.Client, key string, members ...string) (int64, error) { return client.SAdd(context.Background(), key, members).Result() }
func SetMembers(client *redis.Client, key string) ([]string, error) { return client.SMembers(context.Background(), key).Result() }
|
在这个示例中,我们编写了 SetAdd 和 SetMembers 函数,用于在 Redis 中添加和获取 Set 类型数据。
接下来,我们编写单元测试来测试这两个函数。创建一个 set_ops_test.go 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
package main
import ( "reflect" "testing"
"github.com/go-redis/redismock/v8" )
func TestSetAddAndMembers(t *testing.T) { client, mock := redismock.NewClientMock()
key := "myset"
members := []string{"member1", "member2", "member3"} mock.ExpectSAdd(key, members).SetVal(3)
mock.ExpectSMembers(key).SetVal([]string{"member1", "member2", "member3"})
count, err := SetAdd(client, key, members...) if err != nil { t.Errorf("SetAdd failed: %s", err) }
if count != 3 { t.Errorf("unexpected count, got: %d, want: %d", count, 3) }
result, err := SetMembers(client, key) if err != nil { t.Errorf("SetMembers failed: %s", err) }
if !reflect.DeepEqual(result, members) { t.Errorf("unexpected members, got: %v, want: %v", result, members) }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
|
Hash 类型
接下来,我们测试 Redis 中的 Hash 类型。我们创建一个 hash_ops.go 文件,并实现相应的 Go 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package main
import ( "context" "fmt" "github.com/go-redis/redis/v8" )
func HashSet(client *redis.Client, key string, fieldValues map[string]interface{}) error { return client.HMSet(context.Background(), key, fieldValues).Err() }
func HashGet(client *redis.Client, key, field string) (string, error) { return client.HGet(context.Background(), key, field).Result() }
|
在这个示例中,我们编写了 HashSet 和 HashGet 函数,用于在 Redis 中设置和获取 Hash 类型数据。
接下来,我们编写单元测试来测试这两个函数。创建一个 hash_ops_test.go 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
package main
import ( "testing"
"github.com/go-redis/redismock/v8" )
func TestHashSetAndGet(t *testing.T) { client, mock := redismock.NewClientMock()
key := "myhash"
fieldValues := map[string]interface{}{ "field1": "value1", "field2": "value2", "field3": "value3", } mock.ExpectHMSet(key, fieldValues).SetVal("OK")
field := "field2" mock.ExpectHGet(key, field).SetVal("value2")
err := HashSet(client, key, fieldValues) if err != nil { t.Errorf("HashSet failed: %s", err) }
result, err := HashGet(client, key, field) if err != nil { t.Errorf("HashGet failed: %s", err) }
if result != "value2" { t.Errorf("unexpected value, got: %s, want: %s", result, "value2") }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
|
Sorted Set 类型
最后,我们测试 Redis 中的 Sorted Set 类型。我们创建一个 sorted_set_ops.go 文件,并实现相应的 Go 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package main
import ( "context" "fmt" "github.com/go-redis/redis/v8" )
func SortedSetAdd(client *redis.Client, key string, members ...*redis.Z) (int64, error) { return client.ZAdd(context.Background(), key, members...).Result() }
func SortedSetRange(client *redis.Client, key string, start, stop int64) ([]string, error) { return client.ZRange(context.Background(), key, start, stop).Result() }
|
在这个示例中,我们编写了 SortedSetAdd 和 SortedSetRange 函数,用于在 Redis 中添加和获取 Sorted Set 类型数据。
接下来,我们编写单元测试来测试这两个函数。创建一个 sorted_set_ops_test.go 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
package main
import ( "reflect" "testing"
"github.com/go-redis/redismock/v8" )
func TestSortedSetAddAndRange(t *testing.T) { client, mock := redismock.NewClientMock()
key := "mysortedset"
members := []*redis.Z{ &redis.Z{Score: 1, Member: "member1"}, &redis.Z{Score: 2, Member: "member2"}, &redis.Z{Score: 3, Member: "member3"}, } mock.ExpectZAdd(key, members...).SetVal(3)
mock.ExpectZRange(key, 0, -1).SetVal([]string{"member1", "member2", "member3"})
count, err := SortedSetAdd(client, key, members...) if err != nil { t.Errorf("SortedSetAdd failed: %s", err) }
if count != 3 { t.Errorf("unexpected count, got: %d, want: %d", count, 3) }
result, err := SortedSetRange(client, key, 0, -1) if err != nil { t.Errorf("SortedSetRange failed: %s", err) }
expected := []string{"member1", "member2", "member3"} if !reflect.DeepEqual(result, expected) { t.Errorf("unexpected members, got: %v, want: %v", result, expected) }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
|
4. 执行 Lua 脚本
现在,让我们测试在 Redis 中执行 Lua 脚本。我们将使用 counter.lua 脚本,并在其中实现计数器应用。
首先,创建一个名为 counter.lua 的 Lua 脚本:
1 2 3 4 5 6 7 8 9 10
|
local key = KEYS[1] local increment = tonumber(ARGV[1])
if increment > 0 then return redis.call('INCRBY', key, increment) else return redis.call('GET', key) end
|
在该脚本中,我们接收一个键和一个增量值作为参数。如果增量值大于 0,则在 Redis 中使用 INCRBY 命令增加计数;否则,返回该键的当前值。
接下来,我们创建一个名为 counter.go 的文件,并实现相应的 Go 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
package main
import ( "context" "github.com/go-redis/redis/v8" )
const counterLuaScript = ` local key = KEYS[1] local increment = tonumber(ARGV[1]) if increment > 0 then return redis.call('INCRBY', key, increment) else return redis.call('GET', key) end `
func IncreaseCounter(client *redis.Client, key string, increment int64) (int64, error) { result, err := client.Eval(context.Background(), counterLuaScript, []string{key}, increment).Result() if err != nil { return 0, err }
if intValue, ok := result.(int64); ok { return intValue, nil }
return 0, fmt.Errorf("unexpected result type") }
|
在这个示例中,我们编写了 IncreaseCounter 函数,它使用 Redis 客户端执行我们之前创建的 Lua 脚本,并返回结果。
接下来,我们编写单元测试来测试 IncreaseCounter 函数。创建一个 counter_test.go 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
package main
import ( "testing"
"github.com/go-redis/redismock/v8" )
func TestIncreaseCounter_PositiveIncrement(t *testing.T) { client, mock := redismock.NewClientMock()
key := "mykey" increment := int64(5)
mock.ExpectEval(counterLuaScript, []string{key}, increment).SetVal(increment)
result, err := IncreaseCounter(client, key, increment) if err != nil { t.Errorf("IncreaseCounter failed: %s", err) }
if result != increment { t.Errorf("unexpected result, got: %d, want: %d", result, increment) }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
func TestIncreaseCounter_NegativeIncrement(t *testing.T) { client, mock := redismock.NewClientMock()
key := "mykey" increment := int64(-5) currentValue := int64(10)
mock.ExpectEval(counterLuaScript, []string{key}, increment).SetVal(currentValue)
result, err := IncreaseCounter(client, key, increment) if err != nil { t.Errorf("IncreaseCounter failed: %s", err) }
if result != currentValue { t.Errorf("unexpected result, got: %d, want: %d", result, currentValue) }
if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled expectations: %s", err) } }
|
5. 结论
在本文中,我们学习了如何使用 go-redis/redismock 在 Go 语言中测试 Redis 常用的五种数据类型以及执行 Lua 脚本。我们测试了 Redis 中的 String、List、Set、Hash 和 Sorted Set 类型,并实现了一个简单的计数器应用来测试执行 Lua 脚本的功能。通过单元测试,我们可以确保这些 Redis 操作的正确性和稳定性。使用 go-redis/redismock,我们可以在不连接到真实 Redis 数据库的情况下快速和可控地测试与 Redis 交互的代码。这为我们的应用程序开发和维护带来了便利和信心。