doulan7166 2017-08-29 14:07
浏览 41
已采纳

模拟Go数据库SDK

I am attempting to create a wrapper for test emulating around the Go Flex SDK for Google Cloud Datastore. While I am currently successfully running the localhost emulator using

gcloud beta emulators datastore start --no-store-on-disk

in a separate terminal from my testing window, I would prefer to create a mock database emulator that runs as part of the test process itself (without execing the above) so that I can run multiple tests in parallel, each with its own database emulator.

I have run into a problem with the Google SDK not implementing my interface.

My wrapper contains this code:

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

Note that these interfaces do not emulate the complete SDK. I am only including functions that I actually call in my code. I'll add new ones as needed later.

When I try to use an instance of *datastore.Client as a Datastorer, I get the following error:

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)

because *datastore.Client requires a function that takes a func(*datastore.Transaction) error and my interface wants a func(Transactioner) error.

Is there any way to change this so that it compiles?

If I can get it working, I plan to create types that implement my Datastorer and Transactioner interfaces and use maps to mock the real database. As far as tranactions go, for testing I can use sync.Mutex if I need them, but since each test is a single thread and will get its own database object, I may not need to lock them.

  • 写回答

2条回答 默认 最新

  • duanlu0075 2017-08-29 14:54
    关注

    I've gotten it to compile by using this code:

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

    I changed DataStore to a struct containing the datastore.Client and added a new interface Querier that contains the functions that I am using from datastore.Query. I also updated GetAll to accept an interface{} instead of a *datastore.Query and then type-assert it to be a *datastore.Query. I cannot have it accept a Querier because then I cannot pass variables of type *datastore.Query because they do not satisfy the Querier interface (Filter returns a Querier instead of a *datastore.Query).

    All existing tests using the emulator running in a separate process are passing.

    UPDATE:

    I changed Datastore to

    Datastore datastore.Client
    

    and added a wrapper Query around datastore.Query:

    Query datastore.Query
    

    Now, the Datastorer interface contains

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

    and the GetAll function is defined as

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

    and Query.Filter is defined as

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

    In calling code, I use

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

    This compiles and all tests are passing.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 如何让企业微信机器人实现消息汇总整合
  • ¥50 关于#ui#的问题:做yolov8的ui界面出现的问题
  • ¥15 如何用Python爬取各高校教师公开的教育和工作经历
  • ¥15 TLE9879QXA40 电机驱动
  • ¥20 对于工程问题的非线性数学模型进行线性化
  • ¥15 Mirare PLUS 进行密钥认证?(详解)
  • ¥15 物体双站RCS和其组成阵列后的双站RCS关系验证
  • ¥20 想用ollama做一个自己的AI数据库
  • ¥15 关于qualoth编辑及缝合服装领子的问题解决方案探寻
  • ¥15 请问怎么才能复现这样的图呀