2017-08-29 70 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