英文:
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论