猿问

如何对消耗g云存储的代码进行单元测试?

我想为下面的代码编写单元测试


package main


import (

    "context"


    "google.golang.org/api/option"

    "cloud.google.com/go/storage"

)


var (

    NewClient = storage.NewClient

)


func InitializeClient(ctx context.Context) (*storage.Client, error) {

    credFilePath := "Storage credentials path."


    // Creates a client.

    client, err := NewClient(ctx, option.WithCredentialsFile(credFilePath))

    if err != nil {

        return nil, err

    }


    return client, nil

}


func createStorageBucket(ctx context.Context, client *storage.Client, bucketName string) (*storage.BucketHandle, error) {


    // Sets your Google Cloud Platform project ID.

    projectID := "Some project id"


    // Creates a Bucket instance.

    bucket := client.Bucket(bucketName)


    // Creates the new bucket.

    ctx, cancel := context.WithTimeout(ctx, time.Second*10)

    defer cancel()

    if err := bucket.Create(ctx, projectID, nil); err != nil {

        return nil, err

    }

    return bucket, nil

}


func bucketExists(ctx context.Context, client *storage.Client, bucketName string) error {

    bucket := client.Bucket(bucketName)

    if _, err := bucket.Attrs(ctx); err != nil {

        //try creating the bucket

        if _, err := createStorageBucket(ctx, client, bucketName); err != nil {

            return err

        }

    }

    return nil

}


func main() {

    ctx = context.Background()

    client, err := InitializeClient(ctx)

    bucketName := "Some bucket name"

    err = bucketExists(ctx, client, bucketName)

}

桶。创建() 和存储桶。Attrs() 是 http 调用,也是返回结构的存储桶()、对象() 和 NewReader() (所以在我看来,对于这个用例,实现接口没有任何意义)


注意:存储。NewClient() 也是 http 调用,但我通过提供自定义实现来避免在我的测试中使用猴子路径应用进行外部调用。


var (

    NewClient = storage.NewClient

)


眼眸繁星
浏览 87回答 1
1回答

catspeake

代码非常薄,很难弄清楚如何对其进行测试。我猜弗利姆齐在标题中理解了这一点。由于/在标题中,存在一个根本性的误解。How to write unit test for gcloud storage?好吧,我们不应该。他们做到了。一个更好的标题是,我不是想在这里挑剔,而是解释我试图解决这个问题的理解。How to unit test code consuming gcloud storage?所以无论如何,这整件事导致我写更少的瘦代码,这样我就可以测试我写的代码,驱动存储的行,是否做了我们期望它做的事情。这整件事是如此复杂,没有锡空气,我认为它不会回答你的问题。但无论如何,如果这有助于思考这个困难,那已经是一个胜利。package mainimport (    "context"    "flag"    "fmt"    "testing"    "time"    "cloud.google.com/go/storage"    "google.golang.org/api/option")type client struct {    c         *storage.Client    projectID string}func New() client {    return client{}}func (c *client) Initialize(ctx context.Context, projectID string) error {    credFilePath := "Storage credentials path."    x, err := NewClient(ctx, option.WithCredentialsFile(credFilePath))    if err == nil {        c.c = x        c.projectID = projectID    }    return err}func (c client) BucketExists(ctx context.Context, bucketName string) bool {    if c.c == nil {        return nil, fmt.Errorf("not initialized")    }    bucket := c.c.Bucket(bucketName)    err := bucket.Attrs(ctx)    return err == nil}func (c client) CreateBucket(ctx context.Context, bucketName string) (*storage.BucketHandle, error) {    if c.c == nil {        return nil, fmt.Errorf("not initialized")    }    bucket := c.c.Bucket(bucketName)    err := bucket.Create(ctx, c.projectID, nil)    if err != nil {        return nil, err    }    return bucket, err}func (c client) CreateBucketIfNone(ctx context.Context, bucketName string) (*storage.BucketHandle, error) {    if !c.BucketExists(bucketName) {        return c.CreateBucket(ctx, c.projectID, bucketName)    }    return c.c.Bucket(bucketName), nil}type clientStorageProvider interface { // give it a better name..    Initialize(ctx context.Context, projectID string) (err error)    CreateBucketIfNone(ctx context.Context, bucketName string) (*storage.BucketHandle, error)    CreateBucket(ctx context.Context, bucketName string) (*storage.BucketHandle, error)    BucketExists(ctx context.Context, bucketName string) bool}func main() {    flag.Parse()    cmd := flag.Arg(0)    projectID := flag.Arg(1)    bucketName := flag.Arg(2)    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)    defer cancel()    client := New()    if cmd == "create" {        createBucket(ctx, client, projectID, bucketName)    } else {        // ... more    }}// this is the part we are going to test.func createBucket(ctx context.Context, client clientStorageProvider, projectID, bucketName string) error {    err := client.Initialize(ctx, projectID)    if err != nil {        return err    }    return client.CreateBucketIfNone(bucketName)  // maybe we want to apply retry strategy here,  // and test that the retry was done;}type clientFaker struct {    initErr         error    createErr       error    createIfNoneErr error    bucketExistsErr error}func (c clientFaker) Initialize(ctx context.Context, projectID string) (err error) {    return c.initErr}func (c clientFaker) CreateBucketIfNone(ctx context.Context, bucketName string) (*storage.BucketHandle, error) {    return nil, c.createIfNoneErr}func (c clientFaker) CreateBucket(ctx context.Context, bucketName string) (*storage.BucketHandle, error) {    return nil, c.createErr}func (c clientFaker) BucketExists(ctx context.Context, bucketName string) bool {    return nil, c.bucketExistsErr}func TestCreateBucketWithFailedInit(t *testing.T) {    c := clientFaker{        initErr: fmt.Errorf("failed init"),    }    ctx := context.Background()    err := createBucket(ctx, c, "", "")    if err == nil {        t.Fatalf("should have failed to initialize the bucket")    }}// etc...请注意,我不高兴作为返回参数,太具体了,但我没有使用它(我把它放在这里,因为它在那里,否则挂着),所以很难围绕它设计一些东西。*storage.BucketHandle注意²,可能会发生我的代码无法完全编译的情况。我有一个依赖性问题,我现在不想修复,它阻止我看到所有错误(它在这个过程中过早停止)
随时随地看视频慕课网APP

相关分类

Go
我要回答