2015-12-25 2 views
7

Отказ от ответственности: Желаю вам веселого XMas, и я надеюсь, что мой вопрос не беспокоит вас!как бороться с пакетом библиотеки «fmt» golang для тестирования CLI

sample.go:

package main 

import(
    "fmt" 
    "os" 
) 


type sample struct { 
    value int64 
} 

func (s sample) useful() { 
    if s.value == 0 { 
     fmt.Println("Error: something is wrong!") 
     os.Exit(1) 
    } else { 
     fmt.Println("May the force be with you!") 
    } 
} 

func main() { 
    s := sample{42} 
    s.useful() 

    s.value = 0 
    s.useful() 
} 

// output: 
// May the force be with you! 
// Error: something is wrong! 
// exit status 1 

Я сделал много исследований о том, как использовать интерфейсы в тестировании golang. Но до сих пор я не мог полностью обернуть свою голову. По крайней мере, я не вижу, как интерфейсы помогают мне, когда мне нужно «издеваться» (извинения за использование этого слова) golang std. библиотечные пакеты, такие как «fmt».

я придумал два сценария:

  1. использование OS/Exec протестировать интерфейс командной строки
  2. обертка FMT пакет поэтому я должен контролировать и я в состоянии проверить выход строки

Я не люблю оба сценария:

  1. Мне кажется, что в реальной командной строке запутанная и не-исполнительная (см. Ниже). Могут возникнуть проблемы с переносимостью.
  2. Я считаю, что это путь, но я боюсь, что упаковка пакета fmt может быть большой работой (по крайней мере, упаковка пакета времени для тестирования оказалась нетривиальной задачей (https://github.com/finklabs/ttime)).

Актуальный вопрос здесь: Есть ли другой (лучший/более простой/идиоматический) способ? Примечание: Я хочу сделать это в чистом голанге, меня не интересует следующая платформа тестирования.

cli_test.go:

package main 

import(
    "os/exec" 
    "testing" 
) 


func TestCli(t *testing.T) { 
    out, err := exec.Command("go run sample.go").Output() 
    if err != nil { 
     t.Fatal(err) 
    } 
    if string(out) != "May the force be with you!\nError: this is broken and not useful!\nexit status 1" { 
     t.Fatal("There is something wrong with the CLI") 
    } 
} 

ответ

8

Глава 11 из книги Kerningham дает хорошее решение этого вопроса. Хитрости заключается в том, чтобы изменить вызовы fmt.Printline() к призывам fmt.Fprint (уходит, ...), где из инициализируются os.Stdout

Это может быть переписано в тестовом жгуте к новому (bytes.Buffer), что позволяет выполнить тест .

См https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo.go и https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo_test.go

отредактировал OP ... sample.go:

package main 


import(
    "fmt" 
    "os" 
    "io" 
) 


var out io.Writer = os.Stdout // modified during testing 
var exit func(code int) = os.Exit 

type sample struct { 
    value int64 
} 


func (s sample) useful() { 
    if s.value == 0 { 
     fmt.Fprint(out, "Error: something is wrong!\n") 
     exit(1) 
    } else { 
     fmt.Fprint(out, "May the force be with you!\n") 
    } 
} 


func main() { 
    s := sample{42} 
    s.useful() 

    s.value = 0 
    s.useful() 
} 

// output: 
// May the force be with you! 
// Error: this is broken and not useful! 
// exit status 1 

cli_test.go:

package main 

import(
    "bytes" 
    "testing" 
) 


func TestUsefulPositive(t *testing.T) { 
    bak := out 
    out = new(bytes.Buffer) 
    defer func() { out = bak }() 

    s := sample{42} 
    s.useful() 
    if out.(*bytes.Buffer).String() != "May the force be with you!\n" { 
     t.Fatal("There is something wrong with the CLI") 
    } 

} 


func TestUsefulNegative(t *testing.T) { 
    bak := out 
    out = new(bytes.Buffer) 
    defer func() { out = bak }() 
    code := 0 
    osexit := exit 
    exit = func(c int) { code = c } 
    defer func() { exit = osexit }() 

    s := sample{0} 
    s.useful() 
    if out.(*bytes.Buffer).String() != "Error: something is wrong!\n" { 
     t.Fatal("There is something wrong with the CLI") 
    } 
    if code != 1 { 
     t.Fatal("Wrong exit code!") 
    } 
} 
+0

ничего себе, я не знал, что Пайк написал книгу. Не могли бы вы указать название или ссылку на книгу. Мне нравятся ваши советы. Я собираюсь реорганизовать свой код и сообщить об этом. Спасибо огромное! – mark

+1

Язык программирования Go http://www.gopl.io/ Alan A. A. Donovan · Brian W. Kernighan Опубликовано 26 окт 2015 в мягкой обложке и 20 ноября в электронной книге Addison-Wesley; 380pp; ISBN: 978-0134190440 – Amnon

+0

Я реорганизовал код, следующий за вашим советом. Я надеюсь, что вы не возражаете, я добавил, что я подошел к вашему ответу, чтобы сделать его явным. Если я неправильно понял что-то, пожалуйста, измени его. Кстати, кажется, что проблема с os.Exit. – mark

2

Я пропускаю что-то здесь или ты говоря о testable examples?

В принципе, это работает следующим образом: В *_test.go файле, вы должны придерживаться конвенций Example[[T][_M]] где T является заполнителем для типа и M заполнителя для метода вы хотите отобразить проверяемой пример, как пример кода Godoc. Если функция только что называется Example(), код будет показан как пример пакета.

Ниже последней строке кода вашего примера, вы можете поместить комментарий как этот

// Output: 
// Foo 

Теперь go test будет убедиться, что тестируемый пример функции либо точно тушит все ниже // Output: (включая пробелы), или это приведет к сбою теста.

Вот конкретный пример для проверяемого примера

func ExampleMongoStore_Get() { 

    sessionId := "ExampleGetSession" 

    data, err := ms.Get(sessionId) 

    if err == sessionmw.ErrSessionNotFound { 

    fmt.Printf("Session '%s' not found\n", sessionId) 

    data = make(map[string]interface{}) 
    data["foo"] = "bar" 

    ms.Save(sessionId, data) 
    } 

    loaded, _ := ms.Get(sessionId) 
    fmt.Printf("Loaded value '%s' for key '%s' in session '%s'", 
    loaded["foo"], 
    "foo", sessionId) 
    // Output: 
    // Session 'ExampleGetSession' not found 
    // Loaded value 'bar' for key 'foo' in session 'ExampleGetSession' 
} 

Edit: Посмотри на the output of above example at godoc.org

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