Как добавить тестирование к методам уровня доступа к данным? - PullRequest
2 голосов
/ 05 августа 2020

У меня большие трудности с написанием тестов для моих резолверов graphql.

Предположим, у меня есть резолвер, который обрабатывает создание нового пользователя. Резолвер будет выглядеть примерно так:

func (r *mutationResolver) CreateUser(ctx context.Context, name string) (*model.User, error) {
    u, err := doa.AddUser(ctx, client, index, name)
    if err != nil {
        fmt.Println("Could not add User to db")
        return nil, err
    }
    fmt.Println("Insertion Successful")
    return u, nil
}

Мой слой doa имеет метод AddUser.

func AddUser(ctx context.Context, client *elastic.Client, index string, name string) (*model.User, error) {
    u := &model.User{
        UserID: uuid.New().String(),
        Name:   name,
    }
    s, err := utils.ParseToString(u)
    if err != nil {
        return nil, err
    }
    _, err = client.Index().
             Index(index).
             BodyString(s).
             Do(ctx)
    if err != nil {
        fmt.Println("Error Storing the User")
        return nil, err
    }
    return u, err
}

Я знаю, что использовал бы библиотеку тестирования, например gomock или testify, но я вообще не знаю, как структурировать код. Как мне написать интерфейсы, которые будет вызывать библиотека тестирования?

1 Ответ

0 голосов
/ 05 августа 2020

Для начала я бы вытащил создание модели. Тип пользователя, поскольку это объект высокого уровня, который определяет политику, согласно которой идентификаторы пользователей будут uuid. Вы не хотите, чтобы это было связано с нижним уровнем, где вы взаимодействуете с поиском elasti c. Я бы просто передал пользователя. Также вы можете проверить, созданы ли пользователи, как ожидалось. Наличие этого logi c в AddUser усложнит тестирование.

Далее я бы использовал интерфейс для передачи зависимости от elasti c .Client. Это позволит вам поиздеваться над ним и увидеть, что AddUser использует эту зависимость, как и ожидалось (т.е. добавляет пользователя).

Возможно, вы даже захотите создать новый тип, который будет содержать интерфейсы для ваших зависимостей низкого уровня и предоставляет AddUser как метод. Затем вы можете создать интерфейс, в котором есть AddUser, чтобы отделить клиентов от интерфейса от деталей низкого уровня.

Когда имитировать

Вот пример. Я думаю, что ссылка выше предлагает хороший совет по ограничению этого архитектурными границами. Вы видите, как это может быстро выйти из-под контроля.

package main

import (
    "context"
    "fmt"
    "github.com/olivere/elastic"
)

type User struct {}

type Repo interface {
    AddUser(User) error
}

type ElasticClient interface {
    Index() ElasticIndex
}

type ElasticIndex interface {
    Index(string) ElasticIndex
    BodyString(string) ElasticIndex
    Do(context.Context) (*elastic.IndexResponse, error)
}

type MockElasticClient struct {
    Log *[]string
    index MockElasticIndex
}

type MockElasticIndex struct {
    Log *[]string
}

func NewMockElasticClient() *MockElasticClient {
    var log []string
    return &MockElasticClient{
        Log: &log,
        index: MockElasticIndex{&log},
    }
}

func (c *MockElasticClient) Index() ElasticIndex {
    *c.Log = append(*c.Log, "Client.Index()")
    return &c.index
}

func (i *MockElasticIndex) Index(s string) ElasticIndex {
    *i.Log = append(*i.Log, fmt.Sprintf("Index.Index(%s)", s))
    return i
}

func (i *MockElasticIndex) BodyString(s string) ElasticIndex {
    *i.Log = append(*i.Log, fmt.Sprintf("Index.BodyString(%s)", s))
    return i
}

func (i *MockElasticIndex) Do(ctx context.Context) (*elastic.IndexResponse, error) {
    *i.Log = append(*i.Log, "Index.Do()")
    return nil, nil
}

// Use a wrapped client to deal with the fact that elastic
// returns their own types and not interfaces.
type WrappedElasticClient struct {
    wrapped *elastic.Client
}

type WrappedElasticIndexService struct {
    wrapped *elastic.IndexService
}

func (c WrappedElasticClient) Index() ElasticIndex {
    return WrappedElasticIndexService{c.wrapped.Index()}
}

func (i WrappedElasticIndexService) Index(s string) ElasticIndex {
    i.wrapped.Index(s)
    return i
}

func (i WrappedElasticIndexService) BodyString(s string) ElasticIndex {
    i.BodyString(s)
    return i
}

func (i WrappedElasticIndexService) Do(ctx context.Context) (*elastic.IndexResponse, error) {
    return i.wrapped.Do(ctx)
}

type ElasticRepo struct {
    ctx context.Context
    client ElasticClient
}

func (r ElasticRepo) AddUser(u User) error {
    s, err := utils.ParseToString(u)
    if err != nil {
        return err
    }

    _, err = r.client.Index().
        Index("some index").
        BodyString(s).
        Do(r.ctx)
    if err != nil {
        fmt.Println("Error Storing the User")
        return err
    }

    return err
}

func main() {
    m := NewMockElasticClient()
    r := ElasticRepo{
        ctx: nil,
        client: m,
        //client: WrappedElasticClient{&elastic.Client{}},
    }
    r.AddUser(User{})

    fmt.Println(m.Log)
}
...