为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
眼眸繁星 2022-09-19 17:35:40
我想为下面的代码编写单元测试package mainimport (    "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)
查看完整描述

1 回答

?
catspeake

TA贡献1111条经验 获得超0个赞

代码非常薄,很难弄清楚如何对其进行测试。


我猜弗利姆齐在标题中理解了这一点。


由于/在标题中,存在一个根本性的误解。How to write unit test for gcloud storage?


好吧,我们不应该。他们做到了。一个更好的标题是,我不是想在这里挑剔,而是解释我试图解决这个问题的理解。How to unit test code consuming gcloud storage?


所以无论如何,这整件事导致我写更少的瘦代码,这样我就可以测试我写的代码,驱动存储的行,是否做了我们期望它做的事情。


这整件事是如此复杂,没有锡空气,我认为它不会回答你的问题。


但无论如何,如果这有助于思考这个困难,那已经是一个胜利。


package main


import (

    "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


注意²,可能会发生我的代码无法完全编译的情况。我有一个依赖性问题,我现在不想修复,它阻止我看到所有错误(它在这个过程中过早停止)


查看完整回答
反对 回复 2022-09-19
  • 1 回答
  • 0 关注
  • 86 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号