Как проверить функцию Go, которая запускает команду? - PullRequest
0 голосов
/ 26 сентября 2019

Следуя примеру на https://golang.org/pkg/os/exec/#Cmd.StdoutPipe, предположим, у меня есть функция getPerson(), определенная следующим образом:

package stdoutexample

import (
    "encoding/json"
    "os/exec"
)

// Person represents a person
type Person struct {
    Name string
    Age  int
}

func getPerson() (Person, error) {
    person := Person{}
    cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        return person, err
    }
    if err := cmd.Start(); err != nil {
        return person, err
    }
    if err := json.NewDecoder(stdout).Decode(&person); err != nil {
        return person, err
    }
    if err := cmd.Wait(); err != nil {
        return person, err
    }
    return person, nil
}

В моем «реальном» приложении выполнение команды может иметь разные выходы, яЯ хотел бы написать контрольные примеры для каждого из этих сценариев.Тем не менее, я не уверен, как это сделать.

Пока все, что у меня есть, это тестовый пример для одного случая:

package stdoutexample

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestGetPerson(t *testing.T) {
    person, err := getPerson()
    require.NoError(t, err)
    assert.Equal(t, person.Name, "Bob")
    assert.Equal(t, person.Age, 32)
}

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

Ответы [ 3 ]

1 голос
/ 26 сентября 2019

добавление к https://stackoverflow.com/a/58107208/9353289,

Вместо того, чтобы писать отдельные функции Test для каждого теста, я предлагаю вместо этого использовать подход Table Driven Test.Вот пример,

func Test_getPerson(t *testing.T) {
    tests := []struct {
        name          string
        commandOutput []byte
        want          Person
        wantErr       bool
    }{
        {
            name:          "Get Bob",
            commandOutput: []byte(`{"Name": "Bob", "Age": 32}`),
            want: Person{
                Name: "Bob",
                Age:  32,
            },
            wantErr: false,
        },

        {
            name:          "Get Alice",
            commandOutput: []byte(`{"Name": "Alice", "Age": 25}`),
            want: Person{
                Name: "Alice",
                Age:  25,
            },
            wantErr: false,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := getPerson(tt.commandOutput)
            require.NoError(t, err)
            assert.Equal(t, tt.want.Name, got.Name)
            assert.Equal(t, tt.want.Age, got.Age)

        })
    }
}

Простое добавление тестовых случаев в срез, запустит все тестовые случаи.

0 голосов
/ 26 сентября 2019

ваш проект реализации активно отклоняет тестирование мелкого зерна, потому что он не допускает никаких инъекций.

Однако, учитывая пример, кроме использования TestTable, улучшать особо нечего.

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

Чтобы смоделировать свою реализацию, вы используете возможности interface.Чтобы остановить выполнение, вы создаете макет, который выводит материал, который вы хотите проверить.

package main

import (
    "encoding/json"
    "fmt"
    "os/exec"
)

type Person struct{}

type PersonProvider struct {
    Cmd outer
}

func (p PersonProvider) Get() (Person, error) {
    person := Person{}
    b, err := p.Cmd.Out()
    if err != nil {
        return person, err
    }
    err = json.Unmarshal(b, &person)
    return person, err
}

type outer interface{ Out() ([]byte, error) }

type echo struct {
    input string
}

func (e echo) Out() ([]byte, error) {
    cmd := exec.Command("echo", "-n", e.input)
    return cmd.Output()
}

type mockEcho struct {
    output []byte
    err    error
}

func (m mockEcho) Out() ([]byte, error) {
    return m.output, m.err
}

func main() {

    fmt.Println(PersonProvider{Cmd: echo{input: `{"Name": "Bob", "Age": 32}`}}.Get())
    fmt.Println(PersonProvider{Cmd: mockEcho{output: nil, err: fmt.Errorf("invalid json")}}.Get())

}
0 голосов
/ 26 сентября 2019

Я добавил модульные тесты, разделив функцию на две части: одну, которая считывает вывод в срез байтов, и другую, которая анализирует этот вывод в Person:

package stdoutexample

import (
    "bytes"
    "encoding/json"
    "os/exec"
)

// Person represents a person
type Person struct {
    Name string
    Age  int
}

func getCommandOutput() ([]byte, error) {
    cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
    return cmd.Output()
}

func getPerson(commandOutput []byte) (Person, error) {
    person := Person{}
    if err := json.NewDecoder(bytes.NewReader(commandOutput)).Decode(&person); err != nil {
        return person, err
    }
    return person, nil
}

Следующий тестпроходные случаи:

package stdoutexample

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestGetPerson(t *testing.T) {
    commandOutput, err := getCommandOutput()
    require.NoError(t, err)
    person, err := getPerson(commandOutput)
    require.NoError(t, err)
    assert.Equal(t, person.Name, "Bob")
    assert.Equal(t, person.Age, 32)
}

func TestGetPersonBob(t *testing.T) {
    commandOutput := []byte(`{"Name": "Bob", "Age": 32}`)
    person, err := getPerson(commandOutput)
    require.NoError(t, err)
    assert.Equal(t, person.Name, "Bob")
    assert.Equal(t, person.Age, 32)
}

func TestGetPersonAlice(t *testing.T) {
    commandOutput := []byte(`{"Name": "Alice", "Age": 25}`)
    person, err := getPerson(commandOutput)
    require.NoError(t, err)
    assert.Equal(t, person.Name, "Alice")
    assert.Equal(t, person.Age, 25)
}

, где тестовые примеры Bob и Alice имитируют различный вывод, который может быть сгенерирован командой.

...