3

I want to print an informative message to console from a test, but not have the verbose test output.

I have tried printing from the test using:

  • fmt.Println("my message") // STDOUT
  • fmt.Fprintln(os.Stderr, "my message") // STDERR
  • t.Log("my message\n") // testing.T log

which all produce the same effect of showing in the console if go test -v is used, but not showing if just go test is used.

However, with go test -v, I also get all the verbose test output like:

=== RUN   My_Test
--- PASS: My_Test (0.07s)

go help testflag says:

-v
    Verbose output: log all tests as they are run. Also print all
    text from Log and Logf calls even if the test succeeds.

but what I want is to not log all tests as they are run, but still print all text from Log and Logf calls even if the test succeeds

Is there a way to print a visible message from within a test, but not see the RUN and PASS messages?

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    You can write to a file using fmt.Fprintf, then run tests with tail -f in background – Tiago Peczenyj May 10 '22 at 03:55
  • @TiagoPeczenyj I was afraid that might be the only way. I opted to `cat` the file after running `go test`. It feels like there should be an option to not use `-v` but still allow printing to stdout. Thanks for the suggestion. – Bohemian May 10 '22 at 18:10

3 Answers3

1

Although this doesn't print during the test, it prints straight after, which is better than not printing at all.

Define an os.File in your test file to write messages to:

var messagesFile *os.File

func messages() *os.File {
    if messagesFile == nil {
        messagesFile, _ = os.Create("tests.out")
    }
    return messagesFile
}

os.Create creates a new file if one doesn't exist, otherwise truncates the existing file.

Within your tests, write messages to that file:

messages().WriteString("my message")

Run your tests using go test, then cat the file. In my case, I use make:

test:
    go test .
    @cat tests.out

Output looks like:

ok  <path to tests>
my message
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • This doesn't write to the console while tests are run either, so this doesn't answer the question either. Btw, if you're fine using a file, you can also set the created file to `os.Stdout`, then you can keep using `fmt.PrintXX()` functions. – icza May 10 '22 at 18:23
  • @icza this does answer the question. I didn't say the messages had to appear while the tests are running; I just said they have to appear in the console (I didn't specify *when*). What do you mean by "set the created file to `os.Stdout`"? – Bohemian May 10 '22 at 18:29
  • `os.Stdout` is a variable of type `*os.File`, if you have a file, you can assign to it, and the `fmt.PrintXX()` functions will write directly to your file then. – icza May 10 '22 at 18:38
  • If you mean changing my code to `messagesFile = os.Stdout`, it doesn't work. Nor would I expect it to work, since it's just a round about way of printing to stdout, which we know doesn't work - that's the point of the question. – Bohemian May 10 '22 at 23:54
  • _"set the created file to `os.Stdout`"_ means `os.Stdout = messagesFile`. `fmt.Println()` writes to `os.Stdout`, and this way it would directly write to your file. – icza May 11 '22 at 05:43
0

The testing framework "hijacks" the standard output and error streams for obvious reasons. So no matter what, whether writing to those streams appears in the output is controlled by the testing framework, and it provides no means to "customize" it other than showing or hiding all using the -v flag.

What you may do is use the -json testing flag:

-json
    Log verbose output and test results in JSON. This presents the
    same information as the -v flag in a machine-readable format.

So you get all the output you would otherwise get with -v, but you have a separate JSON object for each line.

Having this test function:

func TestMy_Test(t *testing.T) {
    fmt.Println("[custom] my message from fmt.Println")
}

Output of go test -v .

=== RUN   TestMy_Test
[custom] my message from fmt.Println
--- PASS: TestMy_Test (0.00s)
PASS
ok      github.com/icza/play    0.002s

Output of go test -json .

{"Time":"2022-05-10T09:26:26.712800797+02:00","Action":"run","Package":"github.com/icza/play","Test":"TestMy_Test"}
{"Time":"2022-05-10T09:26:26.71293072+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"=== RUN   TestMy_Test\n"}
{"Time":"2022-05-10T09:26:26.712946548+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"[custom] my message from fmt.Println\n"}
{"Time":"2022-05-10T09:26:26.712954637+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"--- PASS: TestMy_Test (0.00s)\n"}
{"Time":"2022-05-10T09:26:26.712958774+02:00","Action":"pass","Package":"github.com/icza/play","Test":"TestMy_Test","Elapsed":0}
{"Time":"2022-05-10T09:26:26.712964812+02:00","Action":"output","Package":"github.com/icza/play","Output":"PASS\n"}
{"Time":"2022-05-10T09:26:26.713170439+02:00","Action":"output","Package":"github.com/icza/play","Output":"ok  \tgithub.com/icza/play\t0.002s\n"}
{"Time":"2022-05-10T09:26:26.713573313+02:00","Action":"pass","Package":"github.com/icza/play","Elapsed":0.003}

You can write a simple app that processes and filters these JSON objects. Or you can filter the output as you could filter any other output.

Output of go test -json . |grep '\[custom\]'

{"Time":"2022-05-10T09:28:24.197077681+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"[custom] my message from fmt.Println\n"}

If you also want the "pass" or "fail" messages, run go test -json . |grep '"pass"\|"fail"\|\[custom\]'

{"Time":"2022-05-10T09:29:26.069181336+02:00","Action":"output","Package":"github.com/icza/play","Test":"TestMy_Test","Output":"[custom] my message from fmt.Println\n"}
{"Time":"2022-05-10T09:29:26.069189228+02:00","Action":"pass","Package":"github.com/icza/play","Test":"TestMy_Test","Elapsed":0}
{"Time":"2022-05-10T09:29:26.069199239+02:00","Action":"pass","Package":"github.com/icza/play","Elapsed":0}
icza
  • 389,944
  • 63
  • 907
  • 827
  • You haven't actually answered the question; `-json` is not useful. If you read the question carefully you'll see I **don't** want verbose output, but as you yourself say `-json` behaves like `-v` producing verbose output – Bohemian May 10 '22 at 17:51
  • I found a solution: https://stackoverflow.com/a/72191109/256196 – Bohemian May 10 '22 at 18:06
  • @Bohemian Yes, using `-json` doesn't answer the question because the answer is that you can't (which is what I started my answer with). But using `-json` you can more easily filter the output than using `-v`. – icza May 10 '22 at 18:25
0

You can create you own Log function and use it for printing on screen

func Log(args ...interface{}) {
    fmt.Fprintln(os.Stdout, args...)
}

You can also make it to print log based on condition passed through flag

var p = flag.Bool("p", false, "Enable Local Logging")

func MyLog(args ...interface{}) {
  if *p {
    fmt.Fprintln(os.Stdout, args...)
  }
}

Example

package main

import (
    "fmt"
    "testing"
    "os"
    "flag"
)

var p = flag.Bool("p", false, "Enable Local Logging")

func Log(args ...interface{}) {
  if *p {
    fmt.Fprintln(os.Stdout, args...)
  }
}

func IntMin(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func TestIntMinBasic(t *testing.T) {
    ans := IntMin(2, -2)
    if ans != -2 {
        t.Errorf("IntMin(2, -2) = %d; want -2", ans)
    }
}

func TestIntMinTableDriven(t *testing.T) {
    var tests = []struct {
        a, b int
        want int
    }{
        {0, 1, 0},
        {1, 0, 0},
        {2, -2, -2},
        {0, -1, -1},
        {-1, 0, -1},
    }

    Log("Print to Screen")

    for _, tt := range tests {

        testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
        t.Run(testname, func(t *testing.T) {
            ans := IntMin(tt.a, tt.b)
            if ans != tt.want {
                t.Errorf("got %d, want %d", ans, tt.want)
            }
        })
    }
}

func BenchmarkIntMin(b *testing.B) {
    for i := 0; i < b.N; i++ {
        IntMin(1, 2)
    }
}

And to pass the flag you can use -args

-args Pass the remainder of the command line (everything after -args) to the test binary, uninterpreted and unchanged. Because this flag consumes the remainder of the command line, the package list (if present) must appear before this flag.

Cmd Example:

go test -args -p
Chandan
  • 11,465
  • 1
  • 6
  • 25