Как протестировать сценарии os.exit в Go

Учитывая этот код

func doomed() {
  os.Exit(1)
}

Как правильно проверить, что вызов этой функции приведет к существованию с помощью go test? Это должно происходить в рамках набора тестов, другими словами вызов os.Exit() не может влиять на другие тесты и должен быть захвачен.

Ответ 1

Там презентация Эндрю Джерранда (одного из основных членов команды Go), где он показывает, как это сделать.

Для функции (в main.go)

package main

import (
    "fmt"
    "os"
)

func Crasher() {
    fmt.Println("Going down in flames!")
    os.Exit(1)
}

здесь, как вы его протестировали (через main_test.go):

package main

import (
    "os"
    "os/exec"
    "testing"
)

func TestCrasher(t *testing.T) {
    if os.Getenv("BE_CRASHER") == "1" {
        Crasher()
        return
    }
    cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")
    cmd.Env = append(os.Environ(), "BE_CRASHER=1")
    err := cmd.Run()
    if e, ok := err.(*exec.ExitError); ok && !e.Success() {
        return
    }
    t.Fatalf("process ran with err %v, want exit status 1", err)
}

Что делает код, снова вызывает go test в отдельном процессе через exec.Command, ограничивая выполнение тестом TestCrasher (через переключатель -test.run=TestCrasher). Он также передает флаг через переменную среды (BE_CRASHER=1), которую второй вызов проверяет и, если задан, вызывает системный тест, возвращающийся сразу после этого, чтобы предотвратить запуск в бесконечный цикл. Таким образом, мы возвращаемся обратно в наш исходный сайт вызова и теперь можем подтвердить действительный код выхода.

Источник: Слайд 23 презентации Эндрю. Второй слайд содержит ссылку на видеоролик . Он рассказывает о тестах подпроцесса на 47:09

Ответ 2

Я не думаю, что вы можете протестировать фактический os.Exit без моделирования тестирования извне (используя exec.Command) процесс.

Тем не менее, вы могли бы достичь своей цели , создав интерфейс или тип функции, а затем воспользуйся выполнением noop в своих тестах:

Перейти на площадку

package main

import "os"
import "fmt"

type exiter func (code int)

func main() {
    doExit(func(code int){})
    fmt.Println("got here")
    doExit(func(code int){ os.Exit(code)})
}

func doExit(exit exiter) {
    exit(1)
}

Ответ 3

Я делаю это с помощью bouk/monkey:

func TestDoomed(t *testing.T) {
  fakeExit := func(int) {
    panic("os.Exit called")      
  }
  patch := monkey.Patch(os.Exit, fakeExit)
  defer patch.Unpatch()
  assert.PanicsWithValue(t, "os.Exit called", doomed, "os.Exit was not called")
}

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

Ответ 4

Вы не можете, вам нужно будет использовать exec.Command и проверить возвращаемое значение.

Ответ 5

Код для тестирования:

package main
import "os"

var my_private_exit_function func(code int) = os.Exit

func main() {
    MyAbstractFunctionAndExit(1)
}

func MyAbstractFunctionAndExit(exit int) {
    my_private_exit_function(exit)
}

Код тестирования:

package main

import (
    "os"
    "testing"
)

func TestMyAbstractFunctionAndExit(t *testing.T) {
    var ok bool = false // The default value can be omitted :)

    // Prepare testing
    my_private_exit_function = func(c int) {
        ok = true
    }
    // Run function
    MyAbstractFunctionAndExit(1)
    // Check
    if ok == false {
        t.Errorf("Error in AbstractFunction()")
    }
    // Restore if need
    my_private_exit_function = os.Exit
}