go - 模拟 Go 数据库 SDK

标签 go mocking

我正在尝试创建一个包装器来围绕 Go Flex SDK for Google Cloud Datastore 进行测试模拟.虽然我目前正在使用

成功运行本地主机模拟器
gcloud beta emulators datastore start --no-store-on-disk

在与我的测试窗口不同的终端中,我更愿意创建一个模拟数据库模拟器,它作为测试过程本身的一部分运行(无需 exec 上面的内容),以便我可以运行多个并行测试,每个测试都有自己的数据库模拟器。

我遇到了 Google SDK 未实现我的界面的问题。

我的包装器包含这段代码:

package google

import (
    "context"

    "cloud.google.com/go/datastore"
)

type (
    // Datastore is a wrapper for the Google Cloud Datastore Client.
    Datastore datastore.Client

    // Datastorer represents things that can operate like a datastore.Client.
    Datastorer interface {
        Delete(context.Context, *datastore.Key) error
        Get(context.Context, *datastore.Key, interface{}) error
        GetAll(context.Context, *datastore.Query, interface{}) ([]*datastore.Key, error)
        Put(context.Context, *datastore.Key, interface{}) (*datastore.Key, error)
        PutMulti(context.Context, []*datastore.Key, interface{}) ([]*datastore.Key, error)
        RunInTransaction(context.Context, func(Transactioner) error, ...datastore.TransactionOption) (*datastore.Commit, error)
    }

    // Transactioner represents things that can operate like a datastore.Transaction.
    Transactioner interface {
        Commit() (*datastore.Commit, error)
        Delete(*datastore.Key) error
        DeleteMulti([]*datastore.Key) error
        Get(*datastore.Key, interface{}) error
        GetMulti([]*datastore.Key, interface{}) error
        Put(*datastore.Key, interface{}) (*datastore.PendingKey, error)
        PutMulti([]*datastore.Key, interface{}) ([]*datastore.PendingKey, error)
        Rollback() error
    }
)

// Delete deletes the entity for the given key.
func (d *Datastore) Delete(ctx context.Context, key *datastore.Key) error {
    return (*datastore.Client)(d).Delete(ctx, key)
}

// Get retrieves the entity for the given key.
func (d *Datastore) Get(ctx context.Context, key *datastore.Key, dst interface{}) error {
    return (*datastore.Client)(d).Get(ctx, key, dst)
}

// GetAll retrieves all entities for the given query.
func (d *Datastore) GetAll(ctx context.Context, q *datastore.Query, dst interface{}) ([]*datastore.Key, error) {
    return (*datastore.Client)(d).GetAll(ctx, q, dst)
}

// Put stores an entity for the given key.
func (d *Datastore) Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error) {
    return (*datastore.Client)(d).Put(ctx, key, src)
}

// PutMulti is a batch version of Put.
func (d *Datastore) PutMulti(ctx context.Context, keys []*datastore.Key, src interface{}) ([]*datastore.Key, error) {
    return (*datastore.Client)(d).PutMulti(ctx, keys, src)
}

// RunInTransaction runs the given function in a transaction.
func (d *Datastore) RunInTransaction(ctx context.Context, f func(tx Transactioner) error, opts ...datastore.TransactionOption) (*datastore.Commit, error) {
    return (*datastore.Client)(d).RunInTransaction(ctx, func(t *datastore.Transaction) error {
        return f(t)
    }, opts...)
}

请注意,这些接口(interface)不模拟完整的 SDK。我只包括我在代码中实际调用的函数。稍后我会根据需要添加新的。

当我尝试将 *datastore.Client 的实例用作 Datastorer 时,出现以下错误:

cannot use client (type *"cloud.google.com/go/datastore".Client) as type Datastorer in field value:
    *"cloud.google.com/go/datastore".Client does not implement Datastorer (wrong type for RunInTransaction method)
        have RunInTransaction(context.Context, func(*"cloud.google.com/go/datastore".Transaction) error, ..."cloud.google.com/go/datastore".TransactionOption) (*"cloud.google.com/go/datastore".Commit, error)
        want RunInTransaction(context.Context, func(Transactioner) error, ..."cloud.google.com/go/datastore".TransactionOption) (*"cloud.google.com/go/datastore".Commit, error)

因为*datastore.Client 需要一个接受func(*datastore.Transaction) 错误 的函数,而我的界面需要一个func(Transactioner) 错误.

有什么方法可以改变它以便编译吗?

如果我能让它工作,我计划创建实现我的 DatastorerTransactioner 接口(interface)的类型,并使用映射来模拟真实数据库。就事务而言,如果需要,我可以使用 sync.Mutex 进行测试,但由于每个测试都是一个线程,并且会获得自己的数据库对象,因此我可能不需要锁定它们。

最佳答案

我已经通过使用以下代码对其进行了编译:

package google

import (
    "context"

    "cloud.google.com/go/datastore"
)

type (
    // Datastore is a wrapper for the Google Cloud Datastore Client.
    Datastore struct {
        *datastore.Client
    }

    // Datastorer represents things that can operate like a datastore.Client.
    Datastorer interface {
        Delete(context.Context, *datastore.Key) error
        Get(context.Context, *datastore.Key, interface{}) error
        GetAll(context.Context, interface{}, interface{}) ([]*datastore.Key, error)
        Put(context.Context, *datastore.Key, interface{}) (*datastore.Key, error)
        PutMulti(context.Context, []*datastore.Key, interface{}) ([]*datastore.Key, error)
        RunInTransaction(context.Context, func(Transactioner) error, ...datastore.TransactionOption) (*datastore.Commit, error)
    }

    // Querier represents things that can operate like a datastore.Query.
    Querier interface {
        Filter(string, interface{}) Querier
    }

    // Transactioner represents things that can operate like a datastore.Transaction.
    Transactioner interface {
        Commit() (*datastore.Commit, error)
        Delete(*datastore.Key) error
        DeleteMulti([]*datastore.Key) error
        Get(*datastore.Key, interface{}) error
        GetMulti([]*datastore.Key, interface{}) error
        Put(*datastore.Key, interface{}) (*datastore.PendingKey, error)
        PutMulti([]*datastore.Key, interface{}) ([]*datastore.PendingKey, error)
        Rollback() error
    }
)

// Delete deletes the entity for the given key.
func (d *Datastore) Delete(ctx context.Context, key *datastore.Key) error {
    return d.Client.Delete(ctx, key)
}

// Get retrieves the entity for the given key.
func (d *Datastore) Get(ctx context.Context, key *datastore.Key, dst interface{}) error {
    return d.Client.Get(ctx, key, dst)
}

// GetAll retrieves all entities for the given query.
func (d *Datastore) GetAll(ctx context.Context, q interface{}, dst interface{}) ([]*datastore.Key, error) {
    return d.Client.GetAll(ctx, q.(*datastore.Query), dst)
}

// Put stores an entity for the given key.
func (d *Datastore) Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error) {
    return d.Client.Put(ctx, key, src)
}

// PutMulti is a batch version of Put.
func (d *Datastore) PutMulti(ctx context.Context, keys []*datastore.Key, src interface{}) ([]*datastore.Key, error) {
    return d.Client.PutMulti(ctx, keys, src)
}

// RunInTransaction runs the given function in a transaction.
func (d *Datastore) RunInTransaction(ctx context.Context, f func(tx Transactioner) error, opts ...datastore.TransactionOption) (*datastore.Commit, error) {
    return d.Client.RunInTransaction(ctx, func(t *datastore.Transaction) error {
        return f(t)
    }, opts...)
}

我将 DataStore 更改为包含 datastore.Clientstruct 并添加了一个新接口(interface) Querier,其中包含我在 datastore.Query 中使用的函数。我还更新了 GetAll 以接受 interface{} 而不是 *datastore.Query,然后将其类型断言为 *数据存储。查询。我不能让它接受 Querier 因为那样我就不能传递 *datastore.Query 类型的变量,因为它们不满足 Querier 接口(interface)(Filter 返回一个 Querier 而不是 *datastore.Query)。

使用在单独进程中运行的模拟器的所有现有测试均通过。

更新:

我将 Datastore 更改为

Datastore datastore.Client

并在 datastore.Query 周围添加了一个包装器 Query:

Query datastore.Query

现在,Datastorer 接口(interface)包含

GetAll(context.Context, Querier, interface{}) ([]*datastore.Key, error)

GetAll 函数定义为

func (d *Datastore) GetAll(ctx context.Context, q Querier, dst interface{}) ([]*datastore.Key, error) {
    return (*datastore.Client)(d).GetAll(ctx, (*datastore.Query)(q.(*Query)), dst)
}

Query.Filter定义为

func (q *Query) Filter(filterStr string, value interface{}) Querier {
    return (*Query)((*datastore.Query)(q).Filter(filterStr, value))
}

在调用代码中,我使用

q := datastore.NewQuery(entity).Filter("Deleted =", false)
_, err := r.client.GetAll(ctx, (*Query)(q), data)

此编译并且所有测试都通过。

关于go - 模拟 Go 数据库 SDK,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45941260/

相关文章:

google-app-engine - GAE后台请求错误

go - monorepo 和自定义目录结构中的多个 Go 模块

unix - 在 Go 中获取文件 inode

ssl - golang JWT 无签名。 "crypto/rsa: verification error"

PHPUnit:如何保存传递给 stub 方法的参数

ruby-on-rails - 如何在 View 规范中 stub 当前用户的属性

go - 为什么 slice []struct 的行为与 []builtin 不同?

go - 在 Go 包函数中模拟函数

android - 是否有一种方法可以在使用 Espresso 测试的 Activity 中捕获调用的方法

asp.net-mvc - 在 MVC 3 中模拟 session