【问题标题】:Mocking a struct's method call in go tests在 go 测试中模拟结构的方法调用
【发布时间】:2020-12-11 06:11:49
【问题描述】:

我希望在测试中模拟结构的方法以提高代码覆盖率。 有一些关于这个的帖子,没有一个对我有用。我可能完全搞错了。

main/file1.go

type application struct {
    Name string
 }

func (app *application) find() error {
    // perform function logic
    return nil
}

func main() {
    app := &application{
        Name:   "Main Application",
    }

    err := app.find()
    if err != nil {
        fmt.Printf("Error in find call: %s\n", err)
    }
}

我需要能够对find() 进行模拟测试并返回error(我不想生成可能导致错误的测试用例,因为这不在我的控制范围内,我不确定如何通过传递可接受的参数来生成一个)。 我试图按照this 帖子的第二个答案,编译器不喜欢它。

main/file1_test.go

func Test_application_find(t *testing.T) {
    tests := []struct {
        name           string
        app            *application
        wantErr        string
    }{
        {
            name: "generate error",
            app: &application{
                Name: "Mock Application",
            },
            wantErr: true,
        },
    }
    for _, tt := range tests {
        mockCaller := tt.app.find // this works fine
        tt.app.find = func() error { // this assignment errors out
            return fmt.Errorf("Mock Error Message")
        }
        defer func() {
            tt.app.find = mockCaller // this assignment errors out
        }()

        t.Run(tt.name, func(t *testing.T) {
            if err := tt.app.find(); (err != nil) && (err.Error() != "Mock Error Message") {
                    t.Errorf("error = %s, wantErr %s", err.Error(), tt.wantErr)
            }
        } 
    }
}

我在这里做错了什么?请提出建议。

【问题讨论】:

    标签: go testing methods struct mocking


    【解决方案1】:

    有几种方法可以模拟一个方法。他们都有自己擅长的情况。

    作曲

    对于测试,您在原始结构之上创建一个新结构。

    struct testApp struct{
        application
    }
    

    现在teststruct 的行为方式与app 几乎相同,但您可以覆盖 find 函数:

    func (app *testApp) find() error {
        return errors.New("some error")
    }
    

    在测试中,您使用适当的app 初始化testApp

    app := testApp{
        application: &application{
            Name: "Mock Application",
        }
    }
    

    现在您可以在 app 变量上编写测试。

    请注意,这不适用于所有用例,因为组合不是继承。在原来的application 上调用函数将不会在testApp 中调用新的find 函数。

    界面

    您还可以模拟整个应用程序结构/测试所需的部分。如果您想测试另一个依赖于application 的结构,这很有意义。

    要测试的结构不应该直接使用application,而是它需要的功能的接口。 (这可能意味着重构你的代码来做到这一点。)

    然后使用模拟的find 函数创建一个模拟并将模拟传递给要测试的结构:

    type app interface{
        find() error
    }
    
    type mockApp struct{
        Name string
    }
    
    (s *mockApp) find() error {
        return errors.New("some error")
    }
    

    结构字段

    您还可以将find 函数设为结构字段。然后您可以覆盖它以进行测试。但是,这样做的缺点是必须更改代码。

    type application struct {
        Name string
        find func() error
    }
    

    编写一个创建者,以便能够以合理的方式初始化结构:

    func newApplication() *application {
        return &application{
            Name: "Main Application",
            find: func() error {
                // perform function logic
                return nil
            }
        }
    }
    

    现在您可以在测试中覆盖find 方法(实际上它现在是一个字段):

    app := newApplication()
    app.find = func() error {
        return errors.New("some error")
    }
    

    【讨论】:

    • 在我看来,interface 是最像 go 的方法。如果这意味着您正在模拟要测试的代码,那么请考虑将代码拆分为多个结构:要测试的代码 (application) 和要模拟的 finder 结构/接口。在我看来,find 更像是一种依赖。你也说过,你不能轻易影响它的结果。
    • 如果对您有帮助,请随时将其标记为正确答案。
    猜你喜欢
    • 2023-03-18
    • 1970-01-01
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 2014-05-29
    • 1970-01-01
    相关资源
    最近更新 更多