如何对使用gcloud存储的代码进行单元测试?

huangapple go评论79阅读模式
英文:

How to unit test code consuming gcloud storage?

问题

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

package main

import (
	"context"
	"time"

	"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."

	// 创建客户端
	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) {

	// 设置你的 Google Cloud Platform 项目 ID
	projectID := "Some project id"

	// 创建一个 Bucket 实例
	bucket := client.Bucket(bucketName)

	// 创建新的存储桶
	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 {
		// 尝试创建存储桶
		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)
}

bucket.Create()bucket.Attrs() 都是 HTTP 调用,Bucket()Object()NewReader() 返回结构体(所以在我的理解中,对于这种情况,没有实现接口的意义)。

注意:storage.NewClient() 也是一个 HTTP 调用,但是我在测试中使用了 monkey patching 方法,通过提供自定义实现来避免外部调用。

var (
	NewClient = storage.NewClient
)
英文:

I want to write unit test for the below code

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

bucket.Create() and bucket.Attrs() are http calls, also Bucket(), Object() and NewReader() returning structs(So in my sense there is no meaning of implement interface for this use case)

Note: storage.NewClient() is also http call but i am avoiding external call using monkey pathching approch in my test by providing custom implementaion.

var (
NewClient = storage.NewClient
)

答案1

得分: 0

代码如此简洁,以至于很难弄清楚如何测试它。

我猜Flimzy从标题中理解了这一点。

由于标题“如何为gcloud存储编写单元测试?”存在基本误解。

嗯,我们不应该这样做。他们已经做了。一个更好的标题应该是“如何对使用gcloud存储的代码进行单元测试?”我在这里并不是要挑剔,而是解释我在尝试解决这个问题时理解到的内容。

所以无论如何,这整个过程都让我写了更少的简洁代码,这样我就可以测试我编写的代码,测试驱动存储的代码行是否按照我们的期望工作。

这整个过程非常复杂,离奇得让我不认为它会回答你的问题。

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

注意,我不是很满意将*storage.BucketHandle作为返回参数,太具体了,但我没有用到它(我把它放在这里是因为它在那里,否则就会悬空),所以很难围绕它设计出一些东西。

注意2,我的代码可能无法完全编译。我有一个依赖问题,我现在不想解决它,它阻止我看到所有的错误(它在过程中停止得太早了)。

英文:

The code is so thin that is is hard to figure how to test that.

I guess Flimzy grasped that reading at the title.

There is a fundamental misunderstanding because of/in the title How to write unit test for gcloud storage?.

Well we should not. They did it. A better title would be How to unit test code consuming gcloud storage? I am not trying to be picky here, but explains what i understood trying to solve that question.

So anyways, this whole thing lead me to write less thin code, so that i can test that the code i write, the lines driving the storage, did what we expect it does.

This whole thing is so convoluted and out of tin air that I dont think it will answer your question.

but anyways, if that helps thinking about this difficulty that is already a win.

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

note that i am not happy having *storage.BucketHandle as a return parameter, too specific, but i had no use of it (i put it here because it was there, otherwise hanging), so it was hard to design something around that.

note², it might happen my code is not fully compilable. I am having a dependency problem that i don t want to fix now and it prevent me from seeing all errors (it stops too early in the process)

huangapple
  • 本文由 发表于 2021年8月10日 18:36:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/68725235.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定