使用接口进行模拟以进行测试

kos*_*sas 7 unit-testing go go-testing

我对 Go 还很陌生,而且我来自 OOP 语言。现在,接口和类的概念似乎完全不同。

我想知道在测试时模拟如何工作。我遇到的困惑是是否可以用作struct类以及下面的方法是否是您想要的方法?假设这DefaultArticlesRepository将用于真实数据和MockArticlesRepository模拟它。

type ArticlesRepository interface {
    GetArticleSections() []ArticleSectionResponse
}

type DefaultArticlesRepository struct{}
type MockArticlesRepository struct{}

func (repository DefaultArticlesRepository) GetArticleSections() []ArticleSectionResponse {
    return []ArticleSectionResponse{
        {
            Title: "Default response",
            Tag:   "Default Tag",
        },
    }
}

func (repository MockArticlesRepository) GetArticleSections() []ArticleSectionResponse {
    return []ArticleSectionResponse{
        {
            Title: "Mock response",
            Tag:   "Mock Tag",
        },
    }
}

func ArticleSectionsProvider(v ArticlesRepository) ArticlesRepository {
    return v
}

func TestFoo(t *testing.T) {
    realProvider := ArticleSectionsProvider(DefaultArticlesRepository{})
    mockProvider := ArticleSectionsProvider(MockArticlesRepository{})

    assert.Equal(t, realProvider.GetArticleSections(), []ArticleSectionResponse{
        {
            Title: "Default response",
            Tag:   "Default Tag",
        },
    })

    assert.Equal(t, mockProvider.GetArticleSections(), []ArticleSectionResponse{
        {
            Title: "Mock response",
            Tag:   "Mock Tag",
        },
    })
}
Run Code Online (Sandbox Code Playgroud)

小智 5

首先,我建议您使用https://github.com/vektra/mockery根据接口自动生成模拟结构。实现像你这样的模拟结构是可以的,但我认为如果你并不真正需要该结构的非常特殊的行为,那只会浪费你的时间和精力。

其次,我们不需要像在代码中那样测试模拟结构。

assert.Equal(t, mockProvider.GetArticleSections(), []ArticleSectionResponse{
    {
        Title: "Mock response",
        Tag:   "Mock Tag",
    },
})
Run Code Online (Sandbox Code Playgroud)

因此,当我们使用模拟结构时,假设 struct a是 struct b的依赖项。例如:

assert.Equal(t, mockProvider.GetArticleSections(), []ArticleSectionResponse{
    {
        Title: "Mock response",
        Tag:   "Mock Tag",
    },
})
Run Code Online (Sandbox Code Playgroud)

并且您想测试 struct b的函数 DoSomething 。当然,在这种情况下,您不关心也不想测试 struct a的函数 DoTask 。然后,您只需在测试中提供 struct a的模拟到 struct b即可。此模拟还可以帮助您避免在测试 struct b时处理与 struct a相关的任何问题。现在你的测试应该是这样的:

type A interface {
    DoTask() bool
} 

type a struct {}

func (sa *a) DoTask() bool {
    return true
}

type b struct {
    a A
}

func (sb *b) DoSomething() bool {
    //Do some logic
    sb.a.DoTask();
    //Do some logic
    return true;
}
Run Code Online (Sandbox Code Playgroud)

最后,这只是一件小事,但没有看到您的ArticleSectionsProvider的明确责任。


mik*_*ajs 5

首先,不需要使用任何外部模拟库,例如:

\n\n

您真正需要的只是一个接口和一些使用标准库并行运行所有测试的代码。

\n

检查下面这个真实世界的示例,而不是下一个“计算器测试示例”:

\n
\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 api\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 storage.go\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 server.go\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 server_test.go\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 main.go\n
Run Code Online (Sandbox Code Playgroud)\n

api/storage.go

\n
\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 api\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 storage.go\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 server.go\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 server_test.go\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 main.go\n
Run Code Online (Sandbox Code Playgroud)\n

api/server.go

\n
package api\n\nimport "database/sql"\n\ntype storage struct {\n    // any database driver of your choice...\n    pool *sql.DB\n}\n\nfunc NewStorage(p *sql.DB) *storage {\n    return &storage{pool: p}\n}\n\nfunc (s *storage) Find() (string, error) {\n    // use database driver to find from storage...\n    return `{ "id": "123" }`, nil\n}\n\nfunc (s *storage) Add(v string) error {\n    // use database driver to add to storage...\n    return nil\n}\n
Run Code Online (Sandbox Code Playgroud)\n

api/server_test.go

\n
package api\n\nimport (\n    "fmt"\n    "net/http"\n)\n\ntype Storage interface {\n    Find() (string, error)\n    Add(string) error\n}\n\ntype server struct {\n    storage Storage\n}\n\nfunc NewServer(s Storage) *server {\n    return &server{storage: s}\n}\n\nfunc (s *server) Find(w http.ResponseWriter, r *http.Request) {\n    response, err := s.storage.Find()\n    if err != nil {\n        w.WriteHeader(http.StatusNotFound)\n        fmt.Fprint(w, `{ "message": "not found" }`)\n        return\n    }\n\n    fmt.Fprint(w, response)\n}\n\nfunc (s *server) Add(w http.ResponseWriter, r *http.Request) {\n    query := r.URL.Query()\n    name := query.Get("name")\n\n    if err := s.storage.Add(name); err != nil {\n        w.WriteHeader(http.StatusNotFound)\n        fmt.Fprint(w, `{ "message": "not found" }`)\n        return\n    }\n\n    fmt.Fprint(w, `{ "message": "success" }`)\n}\n
Run Code Online (Sandbox Code Playgroud)\n

运行您的测试

\n
package api\n\nimport (\n    "errors"\n    "net/http"\n    "net/http/httptest"\n    "testing"\n)\n\ntype mock struct {\n    find func() (string, error)\n    add  func(string) error\n}\n\nfunc (m *mock) Find() (string, error) { return m.find() }\nfunc (m *mock) Add(v string) error    { return m.add(v) }\n\nfunc TestFindOk(t *testing.T) {\n    t.Parallel()\n\n    // Arrange\n    expectedBody := `{ "message": "ok" }`\n    expectedStatus := http.StatusOK\n    m := &mock{find: func() (string, error) { return expectedBody, nil }}\n    server := NewServer(m)\n    recorder := httptest.NewRecorder()\n\n    // Act\n    server.Find(recorder, &http.Request{})\n\n    // Assert\n    if recorder.Code != expectedStatus {\n        t.Errorf("want %d, got %d", expectedStatus, recorder.Code)\n    }\n\n    if recorder.Body.String() != expectedBody {\n        t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())\n    }\n}\n\nfunc TestFindNotFound(t *testing.T) {\n    t.Parallel()\n\n    // Arrange\n    expectedBody := `{ "message": "not found" }`\n    expectedStatus := http.StatusNotFound\n    m := &mock{find: func() (string, error) { return expectedBody, errors.New("not found") }}\n    server := NewServer(m)\n    recorder := httptest.NewRecorder()\n\n    // Act\n    server.Find(recorder, &http.Request{})\n\n    // Assert\n    if recorder.Code != expectedStatus {\n        t.Errorf("want %d, got %d", expectedStatus, recorder.Code)\n    }\n\n    if recorder.Body.String() != expectedBody {\n        t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())\n    }\n}\n\nfunc TestAddOk(t *testing.T) {\n    t.Parallel()\n\n    // Arrange\n    expectedBody := `{ "message": "success" }`\n    expectedStatus := http.StatusOK\n    m := &mock{add: func(string) error { return nil }}\n    server := NewServer(m)\n    recorder := httptest.NewRecorder()\n\n    // Act\n    request, _ := http.NewRequest("GET", "/add?name=mike", nil)\n    server.Add(recorder, request)\n\n    // Assert\n    if recorder.Code != expectedStatus {\n        t.Errorf("want %d, got %d", expectedStatus, recorder.Code)\n    }\n\n    if recorder.Body.String() != expectedBody {\n        t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n