Saya cukup baru di Go dan saya mengalami beberapa masalah dengan tes penulisan, khususnya mengejek respons fungsi paket.

Saya sedang menulis lib pembungkus untuk github.com/go-redis/redis. Saat ini hanya benar-benar memiliki kesalahan yang lebih baik untuk kegagalan, tetapi akan diperluas dengan pelacakan statsd lebih jauh, tapi saya ngelantur ...

Saya memiliki paket go berikut yang telah saya buat

package myredis

import (
    "time"

    "github.com/go-redis/redis"
    errors "github.com/pkg/errors"
)

var newRedisClient = redis.NewClient

// Options - My Redis Connection Options
type Options struct {
    *redis.Options
    DefaultLifetime time.Duration
}

// MyRedis - My Redis Type
type MyRedis struct {
    options Options
    client  *redis.Client
}

// Connect - Connects to the Redis Server. Returns an error on failure
func (r *MyRedis) Connect() error {

    r.client = newRedisClient(&redis.Options{
        Addr:     r.options.Addr,
        Password: r.options.Password,
        DB:       r.options.DB,
    })

    _, err := r.client.Ping().Result()
    if err != nil {
        return errors.Wrap(err, "myredis")
    }

    return nil
}

Masalah saya adalah saya ingin redis.NewClient mengembalikan tiruan. Ini adalah kode pengujian yang saya tulis, tetapi tidak berfungsi:

package myredis

import (
    "testing"

    "github.com/go-redis/redis"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

type redisStatusCmdMock struct {
    mock.Mock
}

func (m *redisStatusCmdMock) Result() (string, error) {
    args := m.Called()
    return args.Get(0).(string), args.Error(1)
}

type redisClientMock struct {
    mock.Mock
}

func (m *redisClientMock) Ping() redis.StatusCmd {
    args := m.Called()
    return args.Get(0).(redis.StatusCmd)
}

func TestConnect(t *testing.T) {
    assert := assert.New(t)

    old := newRedisClient
    defer func() { newRedisClient = old }()

    newRedisClient = func(options *redis.Options) *redis.Client {
        assert.Equal("127.0.0.1:1001", options.Addr)
        assert.Equal("password", options.Password)
        assert.Equal(1, options.DB)

        statusCmdMock := new(redisStatusCmdMock)
        statusCmdMock.On("Result").Return("success", nil)

        clientMock := new(redisClientMock)
        clientMock.On("Ping").Return(statusCmdMock)

        return clientMock
    }

    options := Options{}
    options.Addr = "127.0.0.1:1001"
    options.Password = "password"
    options.DB = 1

    r := MyRedis{options: options}

    result, err := r.Connect()

    assert.Equal("success", result)
    assert.Equal(nil, err)
}

Saya mendapatkan kesalahan berikut: cannot use clientMock (type *redisClientMock) as type *redis.Client in return argument. Saya pikir saya membaca bahwa saya perlu mengejek semua fungsi redis.Client agar dapat menggunakannya sebagai tiruan dalam kasus ini, tetapi apakah benar demikian? Sepertinya itu berlebihan dan saya harus bisa melakukan ini dengan cara tertentu. Bagaimana cara saya membuat tes ini berfungsi, atau apakah saya perlu merestrukturisasi kode saya sehingga lebih mudah untuk menulis tes?

0
SynackSA 19 September 2019, 20:50

1 menjawab

Jawaban Terbaik

redis.Client adalah tipe struct dan di Go tipe struct tidak bisa diolok-olok. Namun antarmuka di Go bisa dapat diolok-olok, jadi yang dapat Anda lakukan adalah mendefinisikan fungsi "redisclient baru" Anda sendiri yang alih-alih mengembalikan struct mengembalikan antarmuka. Dan karena antarmuka di Go dipenuhi secara implisit, Anda dapat mendefinisikan antarmuka Anda sedemikian rupa sehingga akan diimplementasikan oleh redis.Client di luar kotak.

type RedisClient interface {
    Ping() redis.StatusCmd
    // include any other methods that you need to use from redis
}

func NewRedisCliennt(options *redis.Options) RedisClient {
    return redis.NewClient(options)
}

var newRedisClient = NewRedisClient

Jika Anda juga ingin meniru nilai pengembalian dari Ping(), Anda perlu melakukan sedikit lebih banyak pekerjaan.

// First define an interface that will replace the concrete redis.StatusCmd.
type RedisStatusCmd interface {
    Result() (string, error)
    // include any other methods that you need to use from redis.StatusCmd
}

// Have the client interface return the new RedisStatusCmd interface
// instead of the concrete redis.StatusCmd type.
type RedisClient interface {
    Ping() RedisStatusCmd
    // include any other methods that you need to use from redis.Client
}

Sekarang *redis.Client tidak memenuhi antarmuka RedisClient lagi karena jenis pengembalian Ping() berbeda. Perhatikan bahwa tidak masalah bahwa jenis hasil redis.Client.Ping() memenuhi jenis antarmuka yang dikembalikan oleh RedisClient.Ping(), yang penting adalah tanda tangan metode berbeda dan oleh karena itu jenisnya berbeda.

Untuk memperbaikinya, Anda dapat menentukan pembungkus tipis yang menggunakan *redis.Client secara langsung dan juga memenuhi antarmuka RedisClient yang baru.

type redisclient struct {
    rc *redis.Client
}

func (c *redisclient) Ping() RedisStatusCmd {
    return c.rc.Ping()
}

func NewRedisCliennt(options *redis.Options) RedisClient {
    // here wrap the *redis.Client into *redisclient
    return &redisclient{redis.NewClient(options)}
}

var newRedisClient = NewRedisClient
2
mkopriva 20 September 2019, 06:29