Go语言 使用接口进行模拟以进行测试

fnatzsnv  于 2023-04-18  发布在  Go
关注(0)|答案(2)|浏览(130)

我是刚接触Go语言的,而且我是从OOP语言学来的。现在,在Go语言中,接口和类的概念似乎完全不同。
我想知道在测试的情况下mocking是如何工作的。我的困惑是是否可以使用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",
        },
    })
}
9nvpjoqh

9nvpjoqh1#

首先,我建议你使用https://github.com/vektra/mockery来自动生成基于接口的mock结构体。实现一个像你这样的mock结构体是可以的,但我认为如果你真的不需要一个非常特殊的行为,那只会浪费你的时间和精力。
其次,我们不需要像在代码中那样测试模拟结构体。

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

因此,当我们使用mock结构体时,假设struct***a***是struct***B***的依赖项。例如:

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;
}

而你想测试struct***B***的函数DoSomething,当然你并不关心,也不想在这种情况下测试struct***a***的函数DoTask,那么你只需要在测试中提供一个struct***a***的mock给struct***b***即可,这个mock也可以帮助你在测试struct***b * 时避免处理任何与struct***a*相关的挣扎。.现在你的测试应该是这样的:

func (s *TestSuiteOfStructB) TestDoSomething_NoError() {
    //Suppose that mockedOfA  is a mock of struct a
    instanceOfB := b{a: mockedOfA}
    mockedOfA.On("DoTask").Return(true)
    actualResult := instanceOfB.DoSomething()
    s.Equal(true, actualResult)
}

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

ymdaylpp

ymdaylpp2#

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

您真正需要的只是一个接口和一些使用标准库并行运行所有测试的代码。
检查下面的真实的世界示例,而不是下一个“计算器测试示例”:

├── api
│   ├── storage.go
│   ├── server.go
│   └── server_test.go
└── main.go

api/storage.go

package api

import "database/sql"

type storage struct {
    // any database driver of your choice...
    pool *sql.DB
}

func NewStorage(p *sql.DB) *storage {
    return &storage{pool: p}
}

func (s *storage) Find() (string, error) {
    // use database driver to find from storage...
    return `{ "id": "123" }`, nil
}

func (s *storage) Add(v string) error {
    // use database driver to add to storage...
    return nil
}

api/server.go

package api

import (
    "fmt"
    "net/http"
)

type Storage interface {
    Find() (string, error)
    Add(string) error
}

type server struct {
    storage Storage
}

func NewServer(s Storage) *server {
    return &server{storage: s}
}

func (s *server) Find(w http.ResponseWriter, r *http.Request) {
    response, err := s.storage.Find()
    if err != nil {
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprint(w, `{ "message": "not found" }`)
        return
    }

    fmt.Fprint(w, response)
}

func (s *server) Add(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query()
    name := query.Get("name")

    if err := s.storage.Add(name); err != nil {
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprint(w, `{ "message": "not found" }`)
        return
    }

    fmt.Fprint(w, `{ "message": "success" }`)
}

api/server_test.go

package api

import (
    "errors"
    "net/http"
    "net/http/httptest"
    "testing"
)

type mock struct {
    find func() (string, error)
    add  func(string) error
}

func (m *mock) Find() (string, error) { return m.find() }
func (m *mock) Add(v string) error    { return m.add(v) }

func TestFindOk(t *testing.T) {
    t.Parallel()

    // Arrange
    expectedBody := `{ "message": "ok" }`
    expectedStatus := http.StatusOK
    m := &mock{find: func() (string, error) { return expectedBody, nil }}
    server := NewServer(m)
    recorder := httptest.NewRecorder()

    // Act
    server.Find(recorder, &http.Request{})

    // Assert
    if recorder.Code != expectedStatus {
        t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
    }

    if recorder.Body.String() != expectedBody {
        t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
    }
}

func TestFindNotFound(t *testing.T) {
    t.Parallel()

    // Arrange
    expectedBody := `{ "message": "not found" }`
    expectedStatus := http.StatusNotFound
    m := &mock{find: func() (string, error) { return expectedBody, errors.New("not found") }}
    server := NewServer(m)
    recorder := httptest.NewRecorder()

    // Act
    server.Find(recorder, &http.Request{})

    // Assert
    if recorder.Code != expectedStatus {
        t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
    }

    if recorder.Body.String() != expectedBody {
        t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
    }
}

func TestAddOk(t *testing.T) {
    t.Parallel()

    // Arrange
    expectedBody := `{ "message": "success" }`
    expectedStatus := http.StatusOK
    m := &mock{add: func(string) error { return nil }}
    server := NewServer(m)
    recorder := httptest.NewRecorder()

    // Act
    request, _ := http.NewRequest("GET", "/add?name=mike", nil)
    server.Add(recorder, request)

    // Assert
    if recorder.Code != expectedStatus {
        t.Errorf("want %d, got %d", expectedStatus, recorder.Code)
    }

    if recorder.Body.String() != expectedBody {
        t.Errorf("want %s, got %s", expectedBody, recorder.Body.String())
    }
}

运行测试

go clean -testcache
go test ./...

相关问题