Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to deal with the "fmt" golang library package for CLI testing

Disclaimer: I wish you a merry XMas and I hope my question does not disturb you!

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

I did a lot of research on how to use interfaces in golang testing. But so far I was not able to wrap my head around this completely. At least I can not see how interfaces help me when I need to "mock" (apologies for using this word) golang std. library packages like "fmt".

I came up with two scenarios:

  1. use os/exec to test the command line interface
  2. wrap fmt package so I have control and am able to check the output strings

I do not like both scenarios:

  1. I experience going through the actual command line a convoluted and not-performant (see below). Might have portability issues, too.
  2. I believe this is the way to go but I fear that wrapping the fmt package might be a lot of work (at least wrapping the time package for testing turned out a non-trivial task (https://github.com/finklabs/ttime)).

Actual Question here: Is there another (better/simpler/idiomatic) way? Note: I want to do this in pure golang, I am not interested in the next testing framework.

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")
    }
}
like image 852
moin moin Avatar asked Dec 25 '15 11:12

moin moin


People also ask

What is import FMT in Go?

fmt stands for the Format package. This package allows to format basic strings, values, or anything and print them or collect user input from the console, or write into a file using a writer or even print customized fancy error messages. This package is all about formatting input and output.

How do I run a single test file in Golang?

Go provides the go test command for running the unit tests in a project. Running this command will ordinarily run the whole test suite, but you can limit it to only a specific file or test. If you run go test , it will run both the TestFactorial and TestSquare functions.


1 Answers

Chapter 11 of Kerningham's Book gives a good solution to this question. The trick is to change the calls to fmt.Printline() to calls to fmt.Fprint(out, ...) where out is initialised to os.Stdout

This can be overwritten in the test harness to new(bytes.Buffer) allowing the test to capture the output.

See https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo.go and https://github.com/adonovan/gopl.io/blob/master/ch11/echo/echo_test.go

edited by 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!")
    }
}
like image 69
Amnon Avatar answered Nov 14 '22 15:11

Amnon