模拟Go数据库SDK

时间:2017-08-29 14:07:41

标签: go mocking

我正在尝试为Go Flex SDK for Google Cloud Datastore周围的测试模拟创建一个包装器。我目前正在使用

成功运行localhost模拟器
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...)
}

请注意,这些界面不会模拟完整的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) error的函数,而我的界面需要一个func(Transactioner) error

有没有办法改变它以便编译?

如果我可以使它工作,我计划创建实现我的DatastorerTransactioner接口的类型,并使用map来模拟真实的数据库。对于转换,为了测试我可以使用sync.Mutex如果我需要它们,但由于每个测试都是一个线程并且将获得自己的数据库对象,我可能不需要锁定它们。

2 个答案:

答案 0 :(得分:2)

我已经使用此代码编译了它:

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更改为包含struct的{​​{1}},并添加了一个新界面datastore.Client,其中包含我在Querier中使用的功能。我还更新了datastore.Query以接受GetAll而不是interface{},然后将其声明为*datastore.Query。我不能接受*datastore.Query,因为我无法传递Querier类型的变量,因为它们不满足*datastore.Query界面(Querier会返回Filter而是一个Querier)。

使用在单独进程中运行的仿真器的所有现有测试都将通过。

<强>更新

我将*datastore.Query更改为

Datastore

并在Datastore datastore.Client 附近添加了一个包装Query

datastore.Query

现在,Query datastore.Query 界面包含

Datastorer

并且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))
}

这个编译并且所有测试都在通过。

答案 1 :(得分:0)

我知道很久以前就已经问过这个问题,但是如果万一仍然有人想知道如何模拟Google数据存储区客户端和事务,这是我如何使其工作的快照。

type Client interface {
    Get(ctx context.Context, key *datastore.Key, dst interface{}) (err error)
    NewTransaction(ctx context.Context, opts ...datastore.TransactionOption) (t Transaction, err error)
    Put(ctx context.Context, key *datastore.Key, src interface{}) (*datastore.Key, error)
}

type Transaction interface {
    Commit() (c *datastore.Commit, err error)
    Rollback() (err error)
    Get(key *datastore.Key, dst interface{}) (err error)
    Put(key *datastore.Key, src interface{}) (*datastore.PendingKey, error)
    Delete(key *datastore.Key) error
}

type Datastore struct {
    *datastore.Client
}

func (d *Datastore) NewTransaction(ctx context.Context, opts ...datastore.TransactionOption) (t Transaction, err error) {
    return d.Client.NewTransaction(ctx, opts...)
}


当然,如果您针对数据存储区使用其他方法,则取决于您实现这些方法。

在测试中,我现在可以模拟数据存储客户端和事务,如:

mockedClient := mock_gcloud.NewMockClient(ctrl)
mockedTransaction := mock_gcloud.NewMockTransaction(ctrl)
...
mockedClient.EXPECT().NewTransaction(context.Background()).Return(mockedTransaction, nil).Times(1)

等等。