【问题标题】:Understanding interfaces and mocks in go理解 Go 中的接口和模拟
【发布时间】:2020-08-20 09:46:34
【问题描述】:

我正在尝试为 AWS 服务 (ECR) 构建抽象。代码如下:

type ECR struct {
    Client ecriface.ECRAPI
    Ctx    context.Context
}

// ECRCreate establishes aws session and creates a repo with provided input
func (e *ECR) ECRCreate(ecrInput *ecr.CreateRepositoryInput) {

    result, err := e.Client.CreateRepositoryWithContext(e.Ctx, ecrInput)
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            switch aerr.Code() {
            case ecr.ErrCodeServerException:
                log.Errorln(ecr.ErrCodeServerException, aerr.Error())
            case ecr.ErrCodeInvalidParameterException:
                log.Errorln(ecr.ErrCodeInvalidParameterException, aerr.Error())
            case ecr.ErrCodeInvalidTagParameterException:
                log.Errorln(ecr.ErrCodeInvalidTagParameterException, aerr.Error())
            case ecr.ErrCodeTooManyTagsException:
                log.Errorln(ecr.ErrCodeTooManyTagsException, aerr.Error())
            case ecr.ErrCodeRepositoryAlreadyExistsException:
                log.Errorln(ecr.ErrCodeRepositoryAlreadyExistsException, aerr.Error())
            case ecr.ErrCodeLimitExceededException:
                log.Errorln(ecr.ErrCodeLimitExceededException, aerr.Error())
            case ecr.ErrCodeKmsException:
                log.Errorln(ecr.ErrCodeKmsException, aerr.Error())
            default:
                log.Errorln(aerr.Error())
            }
        } else {
            // Print the error, cast err to awserr.Error to get the Code and
            // Message from an error.
            log.Errorln(err.Error())
        }
        return
    }
    log.Infof("Result: %v", result)
}

模拟 aws sdk 创建存储库调用:

 type mockECRClient struct {
    ecriface.ECRAPI
}

func (m *mockECRClient) CreateRepositoryWithContext(ctx aws.Context, input *ecr.CreateRepositoryInput, opts ...request.Option) (*ecr.CreateRepositoryOutput, error) {
    createdAt := time.Now()
    encryptionType := "AES256"
    //awsMockAccount := "974589621236"
    encryptConfig := ecr.EncryptionConfiguration{EncryptionType: &encryptionType}
    imageScanConfig := input.ImageScanningConfiguration

    mockRepo := ecr.Repository{
        CreatedAt:                  &createdAt,
        EncryptionConfiguration:    &encryptConfig,
        ImageScanningConfiguration: imageScanConfig,
    }

    mockRepoOuput := ecr.CreateRepositoryOutput{Repository: &mockRepo}

    return &mockRepoOuput, nil

}

func TestECR_ECRCreate(t *testing.T) {
    ctx := context.TODO()
    mockSvc := &mockECRClient{}
    scan := true
    name := "Test1"
    inputTest1 := ecr.CreateRepositoryInput{
        RepositoryName:             &name,
        ImageScanningConfiguration: &ecr.ImageScanningConfiguration{ScanOnPush: &scan},
    }

    ecrTest := ECR{
        mockSvc,
        ctx,
    }

    ecrTest.ECRCreate(&inputTest1)
}

这很有效。但是,我对这里的界面和组合的使用有点困惑。 ECRAPI 由 ecriface 包定义,我实现了接口的签名之一,并且仍然能够使用模拟客户端 mockSvc。 问题:

  • 这是如何工作的?这是模拟接口的惯用方式吗?
  • ECRAPI接口的其他方法呢?那些是如何照顾的?

那么我的理解是否正确,我们可以定义具有任意数量的方法签名的接口,将该接口嵌入到结构中,然后将结构传递到预期接口的任何位置。但这意味着,我跳过实现我的接口的其他签名?

我想我在这里遗漏了一些重要的概念,请指教!

【问题讨论】:

    标签: go mocking


    【解决方案1】:

    简短的回答是:这是有效的,因为测试的函数只调用您明确定义的方法。如果它调用其他任何东西,它就会失败。

    事情是这样发生的:

    mockECRClient 结构嵌入了接口,因此它具有该接口的所有方法。但是,要调用这些方法中的任何一个,您必须将该接口设置为实现:

    x:=mockECRClient{}
    x.ECRAPI=<real implementation of ECRAPI>
    

    在为ECRAPI 定义Func 的x.Func() 调用实际上将调用x.ECRAPI.Func()。由于您没有设置 ECRAPI,所以上面的x.ECRAPI 为 nil,并且您调用的任何使用接收器的方法都会出现恐慌。

    但是,您为mockECRClient 定义了一个方法:CreateRepositoryWithContext。当你调用x.CreateRepositoryWithContext时,该方法属于x而不是x.ECRAPI,接收方为x,所以可以。

    【讨论】:

    • 感谢您的解释。 mockECR 客户端如何满足 ecrapi 接口,因为这是我传递给客户端字段的 ECR 结构
    • 因为是嵌入式接口,所以拥有ECRAPI的所有方法。
    • 谢谢@Burak!感谢耐心
    猜你喜欢
    • 1970-01-01
    • 2017-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多