如何为Go语言S3 JSON上传的会话异常与上传失败编写单元测试?
解决Go语言S3上传函数的单元测试覆盖率问题
问题背景
我编写了用于将JSON数据上传至AWS S3的Go函数SaveJSONToS3,代码如下:
func SaveJSONToS3(data map[string]interface{}) error { name := fmt.Sprintf("audit_event_-%v.json", time.Now().Format(time.RFC822)) // Create an S3 session sess, err := session.NewSession(&aws.Config{ Region: aws.String(utils.AWS_REGION), // AWS region }) if err != nil { log.Printf(utils.LOG_FAILED_S3_SESSION, err) } // Create an S3 client svc := s3.New(sess) // Convert the JSON data to a byte array jsonData, err := json.Marshal(data) if err != nil { log.Printf(utils.LOG_FAILED_MARSHAL_JSON, err) return fmt.Errorf("failed to marshal JSON data: %v", err) } // Set the S3 bucket and object key bucket := utils.AWS_S3_BUCKET_NAME key := name // Upload the JSON data to S3 _, err = svc.PutObject(&s3.PutObjectInput{ Body: aws.ReadSeekCloser(bytes.NewReader(jsonData)), Bucket: aws.String(bucket), Key: aws.String(key), }) if err != nil { return fmt.Errorf("failed to upload JSON to S3: %v", err) } return nil }
目前已完成JSON序列化失败场景的单元测试,但缺少S3会话创建失败和S3上传失败两个分支的测试覆盖。由于会话是在函数内部创建的,无法直接Mock并传入原函数,需要解决这个问题。
解决方案
核心思路是通过依赖注入重构函数,将S3客户端的创建逻辑与核心上传逻辑解耦,让测试时可以传入Mock客户端。
步骤1:重构原函数
将原函数拆分为对外公共函数和内部核心逻辑函数,同时修复原函数中会话创建失败未返回错误的潜在panic问题:
import ( "bytes" "fmt" "log" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) // SaveJSONToS3 对外暴露的函数,保持原有调用方式 func SaveJSONToS3(data map[string]interface{}) error { sess, err := session.NewSession(&aws.Config{ Region: aws.String(utils.AWS_REGION), }) if err != nil { log.Printf(utils.LOG_FAILED_S3_SESSION, err) return fmt.Errorf("failed to create S3 session: %v", err) } svc := s3.New(sess) return saveJSONToS3WithClient(data, svc) } // saveJSONToS3WithClient 核心上传逻辑,接受外部传入的S3客户端,方便测试 func saveJSONToS3WithClient(data map[string]interface{}, svc *s3.S3) error { name := fmt.Sprintf("audit_event_-%v.json", time.Now().Format(time.RFC822)) jsonData, err := json.Marshal(data) if err != nil { log.Printf(utils.LOG_FAILED_MARSHAL_JSON, err) return fmt.Errorf("failed to marshal JSON data: %v", err) } bucket := utils.AWS_S3_BUCKET_NAME key := name _, err = svc.PutObject(&s3.PutObjectInput{ Body: aws.ReadSeekCloser(bytes.NewReader(jsonData)), Bucket: aws.String(bucket), Key: aws.String(key), }) if err != nil { return fmt.Errorf("failed to upload JSON to S3: %v", err) } return nil }
步骤2:编写缺失的单元测试
使用AWS官方Mock库github.com/aws/aws-sdk-go/aws/mock和猴子补丁库github.com/bouk/monkey来实现测试:
测试S3会话创建失败场景
import ( "fmt" "testing" "github.com/aws/aws-sdk-go/aws/session" "github.com/bouk/monkey" ) func TestSaveJSONToS3_SessionCreationError(t *testing.T) { // 猴子补丁替换session.NewSession,模拟会话创建失败 patch := monkey.Patch(session.NewSession, func(cfg *aws.Config) (*session.Session, error) { return nil, fmt.Errorf("mock session creation error") }) defer patch.Unpatch() err := SaveJSONToS3(map[string]interface{}{"key": "value"}) expectedErr := "failed to create S3 session: mock session creation error" if err == nil || err.Error() != expectedErr { t.Errorf("expected error: %s, got: %v", expectedErr, err) } }
测试S3上传失败场景
import ( "fmt" "testing" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/mock" "github.com/aws/aws-sdk-go/service/s3" "github.com/bouk/monkey" ) func TestSaveJSONToS3_PutObjectError(t *testing.T) { // 固定时间,避免动态生成的key导致Mock匹配失败 fixedTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) timePatch := monkey.Patch(time.Now, func() time.Time { return fixedTime }) defer timePatch.Unpatch() expectedKey := fmt.Sprintf("audit_event_-%v.json", fixedTime.Format(time.RFC822)) mockSvc := mock.NewMockS3() // 设置Mock的PutObject方法返回错误 mockSvc.On("PutObject", &s3.PutObjectInput{ Bucket: aws.String(utils.AWS_S3_BUCKET_NAME), Key: aws.String(expectedKey), Body: aws.ReadSeekCloser(mock.Anything), }).Return(nil, fmt.Errorf("mock upload error")) // 调用核心逻辑函数 err := saveJSONToS3WithClient(map[string]interface{}{"key": "value"}, mockSvc) expectedErr := "failed to upload JSON to S3: mock upload error" if err == nil || err.Error() != expectedErr { t.Errorf("expected error: %s, got: %v", expectedErr, err) } // 验证Mock方法被正确调用 mockSvc.AssertExpectations(t) }
依赖安装
执行以下命令安装所需测试依赖:
go get github.com/aws/aws-sdk-go/aws/mock go get github.com/bouk/monkey
内容的提问来源于stack exchange,提问作者gullu
相关产品推荐
相关产品推荐

