2015-06-06 2 views
10

Скажем, у меня был следующий код, который печатает некоторые сообщения журнала. Как я могу проверить, что правильные сообщения были зарегистрированы? Поскольку log.Fatal звонки os.Exit(1), тесты терпят неудачу.Как проверить функцию Go, содержащую log.Fatal()

package main 

import (
    "log" 
) 

func hello() { 
    log.Print("Hello!") 
} 

func goodbye() { 
    log.Fatal("Goodbye!") 
} 

func init() { 
    log.SetFlags(0) 
} 

func main() { 
    hello() 
    goodbye() 
} 

Вот гипотетические тесты:

package main 

import (
    "bytes" 
    "log" 
    "testing" 
) 


func TestHello(t *testing.T) { 
    var buf bytes.Buffer 
    log.SetOutput(&buf) 

    hello() 

    wantMsg := "Hello!\n" 
    msg := buf.String() 
    if msg != wantMsg { 
     t.Errorf("%#v, wanted %#v", msg, wantMsg) 
    } 
} 

func TestGoodby(t *testing.T) { 
    var buf bytes.Buffer 
    log.SetOutput(&buf) 

    goodbye() 

    wantMsg := "Goodbye!\n" 
    msg := buf.String() 
    if msg != wantMsg { 
     t.Errorf("%#v, wanted %#v", msg, wantMsg) 
    } 
} 

ответ

4

Это похоже на «How to test os.Exit() scenarios in Go»: вам нужно реализовать свой собственный регистратор, который по умолчанию редирект log.xxx(), но дает возможность, при тестировании, для замены функция как log.Fatalf() с вашим собственным (который не вызывает os.Exit(1))

Я сделал то же самое для тестирования os.Exit() звонков в exit/exit.go:

exiter = New(func(int) {}) 
exiter.Exit(3) 
So(exiter.Status(), ShouldEqual, 3) 

(здесь, моя функция «выход» является пустым один, который ничего не делает)

-2

Вы не можете и не должны. Это «вы должны» протестировать «каждую строку» - отношение странно, особенно для терминальных условий, и это то, что для log.Fatal. (или просто проверить его с внешней стороны.)

+5

«Can not» немного силен; но я соглашусь с «не должен». Если вы хотите протестировать условие сбоя (не основного) блока кода, тогда не используйте 'log.Fatal' для этого кода, но вместо этого возвращайте' error' (т.если отказ является опцией, имеет API, который представляет это). В подавляющем большинстве случаев «log.Fatal» следует использовать только в функциях 'main' или' init' (или, возможно, некоторые вещи, которые должны быть вызваны только непосредственно из них). –

+1

в сложной распределенной системе есть сценарии, где мой код не может выполнить свою задачу, например, база данных пошла вниз или отсутствует конфигурация. я предпочитаю Fatal здесь, сначала, чтобы избежать выполнения с неопределенным программным состоянием, а во-вторых, чтобы я получил предупреждение о мониторинге – Plato

2

У меня есть следующий код для проверки моей функции. в xxx.go:

var logFatalf = log.Fatalf 

if err != nil { 
    logFatalf("failed to init launcher, err:%v", err) 
} 

И в xxx_test.go:

// TestFatal is used to do tests which are supposed to be fatal 
func TestFatal(t *testing.T) { 
    origLogFatalf := logFatalf 

    // after this test, replace the original fatal function 
    defer func() { logFataf = origLogFatalf }() 

    errors := []string{} 
    logFatalf = func(format string, args ...interface{}) { 
     if len(args) > 0 { 
      errors = append(errors, fmt.Sprintf(format, args)) 
     } else { 
      errors = append(errors, format) 
     } 
    } 
    if len(errors) != 1 { 
     t.Errorf("excepted one error, actual %v", len(errors)) 
    } 
} 
0

Я хотел бы использовать в высшей степени удобный bouk/monkey пакет (здесь вместе с stretchr/testify).

func TestGoodby(t *testing.T) { 
    wantMsg := "Goodbye!" 

    fakeLogFatal := func(msg ...interface{}) { 
    assert.Equal(t, wantMsg, msg[0]) 
    panic("log.Fatal called") 
    } 
    patch := monkey.Patch(log.Fatal, fakeLogFatal) 
    defer patch.Unpatch() 
    assert.PanicsWithValue(t, "log.Fatal called", goodbye, "log.Fatal was not called") 
} 

Советую чтение caveats to using bouk/monkey, прежде чем этот маршрут.

2

Хотя можно протестировать код, содержащий log.Fatal, это не рекомендуется. В частности, вы не можете проверить этот код таким образом, который поддерживается флагом -cover на go test.

Вместо этого рекомендуется изменить код, чтобы вернуть ошибку вместо вызова журнала. Fatal. В последовательной функции вы можете добавить дополнительное возвращаемое значение, а в goroutine вы можете передать ошибку на канал типа chan error (или некоторый тип структуры, содержащий поле типа ошибки).

После того, как это изменение будет сделано, ваш код будет намного легче читать, гораздо легче протестировать, и он будет более переносимым (теперь вы можете использовать его в серверной программе в дополнение к инструментам командной строки).

Если у вас есть log.Println звонки, я также рекомендую передать пользовательский регистратор в качестве поля на ресивере. Таким образом, вы можете войти в пользовательский регистратор, который вы можете установить на stderr или stdout для сервера, и noop logger для тестов (чтобы вы не получали кучу ненужного вывода в своих тестах). Пакет log поддерживает пользовательские журналы, поэтому нет необходимости писать свой собственный или импортировать для него сторонний пакет.

Смежные вопросы