我最近改用Golang选择了我选择的语言。 (在我以前的blog中,您可以阅读原因。)但是我也是测试驱动开发的忠实拥护者。使用Python,您有一个stubber,可以帮助您嘲笑AWS API。那么,您如何在Golang中这样做?我找到了两种方法。一种是通过依赖注射,一种是通过僵硬的。在此博客中,我将分享到目前为止的经验。
使用依赖注入
我的第一个实验是依赖注射。我使用以下代码来做到这一点:
package main
import (
"context"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"time"
"log"
"os"
)
type Request struct {}
type Response struct {}
type Lambda struct {
s3Client *s3.Client
}
func New() (*Lambda, error) {
cfg, err := config.LoadDefaultConfig(context.TODO())
m := new(Lambda)
m.SetS3Client(s3.NewFromConfig(cfg))
return m, err
}
func (x *Lambda) SetS3Client(client *s3.Client) {
x.s3Client = client
}
func (x *Lambda) Handler(ctx context.Context, request Request) (Response, error) {
data := []byte("Hello World")
_, err := x.s3Client.PutObject(x.ctx, &s3.PutObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("my-object-key"),
Body: bytes.NewReader(data),
})
return Response{}, err
}
您可以看到,我将SetS3Client
方法用作二元组。它使我可以从外部设置客户。进行单元测试时,这很有用。您可以在测试中使用它:
package main
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/s3"
"testing"
"time"
"log"
"os"
)
type mockS3Client struct {
s3.Client
Error error
}
func (m *mockS3Client) PutObject(input *s3.PutObjectInput) (*s3.PutObjectOutput, error) {
return &s3.PutObjectOutput{}, nil
}
func TestHandler(t *testing.T) {
lambda := New()
lambda.SetS3Client(&mockS3Client{})
var ctx = context.Background()
var event Request
t.Run("Invoke Handler", func(t *testing.T) {
response, err := lambda.Handler(ctx, event)
// Perform Assertions
})
}
我们注入一个模拟的对象,该对象充当用于执行API调用的客户端。通过这种方法,我现在可以编写一些测试。但是我意识到这种方法会造成另一个问题。例如,如果您有2个执行PutObject
调用的API调用该怎么办?在此示例中,我返回一个空的PutObjectOutput
。但是我想测试多个方案,那么您如何在模拟的对象中控制此行为?
使用顽固的
所以我做了更多的研究,发现了awsdocs/aws-doc-sdk-examples回购。该存储库使用了testtools
模块。因此,我开始了一个实验,以了解如何使用此模块。我按照以下内容进行了重构:
package main
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
type Request struct {}
type Response struct {}
type Lambda struct {
ctx context.Context
s3Client *s3.Client
}
func New(cfg aws.Config) *Lambda {
m := new(Lambda)
m.s3Client = s3.NewFromConfig(cfg)
return m
}
func (x *Lambda) Handler(ctx context.Context, request Request) (Response, error) {
data := []byte("Hello World")
_, err := x.s3Client.PutObject(x.ctx, &s3.PutObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("my-object-key"),
Body: bytes.NewReader(data),
})
return Response{}, err
}
我在New
方法中添加了一个cfg
参数,所以我还需要以我的主要方法传递。
package main
import (
"context"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/config"
"log"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Printf("error: %v", err)
return
}
lambda.Start(New(cfg).Handler)
}
测试本身看起来像这样:
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools"
"io"
"os"
"strings"
"testing"
)
func TestHandler(t *testing.T) {
var ctx = context.Background()
var event Request
t.Run("Upload a file to S3", func(t *testing.T) {
stubber := testtools.NewStubber()
lambda := New(*stubber.SdkConfig)
stubber.Add(testtools.Stub{
OperationName: "PutObject",
Input: &s3.PutObjectInput{
Bucket: aws.String("my-sample-bucket"),
Key: aws.String("my/object.json"),
Body: bytes.NewReader([]byte{}),
},
Output: &s3.PutObjectOutput{},
})
response, err := lambda.Handler(ctx, event)
testtools.ExitTest(stubber, t)
// Perform Assertions
})
}
您可以看到,我们现在在测试本身中移动了模拟。这使您可以根据测试使AWS API反应。最大的优点是它封装在测试本身中。例如,如果要添加一个场景,则PutObject
调用失败,则添加以下内容:
t.Run("Fail on upload", func(t *testing.T) {
stubber := testtools.NewStubber()
lambda := New(*stubber.SdkConfig)
raiseErr := &testtools.StubError{Err: errors.New("ClientError")}
stubber.Add(testtools.Stub{
OperationName: "PutObject",
Input: &s3.PutObjectInput{
Bucket: aws.String("my-sample-bucket"),
Key: aws.String("my/object.json"),
Body: bytes.NewReader([]byte{}),
},
Error: raiseErr,
})
_, err := lambda.Handler(ctx, event)
testtools.VerifyError(err, raiseErr, t)
testtools.ExitTest(stubber, t)
})
Stubber的主要优点是您可以从开箱即用验证Input
。但是在某些情况下,您想忽略某些领域。例如,如果您在Key
中使用时间戳。或您要上传的对象的实际Body
。
您可以通过设置IgnoreFields
忽略这些字段。 (示例:IgnoreFields: []string{"Key", "Body"}
)
我喜欢使用Stubber的第二件事是VerifyError
方法。这将验证您是否在测试方案中提出的错误是否返回。
最后一个是ExitTest
方法。这将确保实际调用所有定义的存根。换句话说,如果您仍然有存根。您的测试会失败,因为仍然有一个未符合的存根。
结论
testtool
是我在python中使用的固执的一个很好的替代。它使您可以将场景数据封装在测试中。避免难以维护模拟对象。 testtool
从配置中起作用,因此您无需固执。导致更少的代码来测试您的实施。