2017-08-29 121 views
2

我正在尝试创建一个模拟Go Flex SDK for Google Cloud Datastore周围的测试的包装。虽然我目前成功运行从我的测试窗口,一个单独的终端使用嘲笑Go数据库SDK

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接口的类型,并使用地图来模拟真实的数据库。至于tranactions去测试我可以使用sync.Mutex如果我需要他们,但由于每个测试是一个单一的线程,并将获得自己的数据库对象,我可能不需要锁定它们。

+0

解决此问题的一种方法是在接口和实现之间有一个中介,基本上是一个实现您想要使用的接口的薄包装,然后将正确的类型传递给基础具体类型。例如。;它需要一个'Transactioner',将它转换为'* datastore.Transaction',然后将它传递给'* datastore.Client'。 – mkopriva

+0

这就是我正在意识到的。我已经取得了一些进展,但遇到了'datastore.Query'类型的新问题。如果我找到完整的解决方案,我会将其作为答案发布。 – Ralph

回答

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...) 
} 

我改变DataStorestruct包含datastore.Client并且加入了全新的界面Querier包含我从datastore.Query使用的功能。我还更新了GetAll以接受interface{}而不是*datastore.Query,然后将其断言为*datastore.Query。我不能让它接受Querier,因为然后我不能通过*datastore.Query类型的变量,因为它们不符合Querier接口(Filter返回Querier而不是*datastore.Query)。

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

UPDATE

我改变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)) 
} 

在调用代码,我使用

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

此编译和所有测试正在通过。

+0

可能会更好地使用'Querier'而不是'interface {}',然后有另一种类型,比方说'DSQuery',它实现'Querier'并环绕'* datastore.Query',然后你可以去掉类型断言并且很容易添加和使用Fake/Stub/Mock'Querier'实现。 – mkopriva

+0

我采纳了你的建议。查看更新的答案。 – Ralph

+0

最后一条建议:同样包装构造函数,以便在每次想要将'Querier'传递给func时,不必执行'(* Query)(q)'。 [简单示例](https://play.golang.org/p/Pql9bhJLUE) – mkopriva