1

I'm writing some test for a Go module. A lot of it is checking that functions return the correct values. This is a simple example for what I'm currently doing:

package foo

import (
    "reflect"
    "testing"
)

func Foo() int {
    return 3
}

func TestFoo(t *testing.T) {
    expected := 4
    actual := Foo()

    if !reflect.DeepEqual(actual, expected) {
        t.Errorf("actual=%v, expected=%v", actual, expected)
    }
}

A single test might contain a many such equality checks. Duplicating these 6 lines for each check makes the tests hard to read and error-prone to write (from what I've experienced the past few days). So I thought I'd make an assertEquals() function that wraps the whole logic similar to what other testing frameworks provide (e.g. in JUnit's org.junit.Assert):

func TestFoo(t *testing.T) {
    assertEqual(t, 4, Foo())
}

func assertEqual(t *testing.T, actual interface{}, expected interface{}) {
    if !reflect.DeepEqual(actual, expected) {
        t.Errorf("Assertion failed: %v != %v", actual, expected)
    }
}

The problem now is that Errorf() will obviously not show the line where assertEqual() is called but the call to Errorf() inside of assertEqual:

=== RUN   TestFoo
    foo_test.go:28: Assertion failed: 4 != 3
--- FAIL: TestFoo (0.00s)

Is there a way to fix that, e.g. by showing the whole stack trace instead of only the location of the call to Errorf()?

Or is there a better way to avoid repeating those lines of code to check the return value of a function?

Feuermurmel
  • 9,490
  • 10
  • 60
  • 90
  • 1
    Use testing.T.Helper() . – Volker Feb 25 '22 at 13:25
  • Volker: Thanks for the pointers! Sadly, in my experience, table-based tests also reduced readability because the additional data flow that needs to be considered when reading the test (member usage on test instance -> position of member in anonymous type -> column in test data -> find right row based on other hints in the output etc.) – Feuermurmel Feb 25 '22 at 13:36

1 Answers1

3

You can use t.Helper():

Helper marks the calling function as a test helper function. When printing file and line information, that function will be skipped. Helper may be called simultaneously from multiple goroutines.

So your helper function becomes:

func assertEqual(t *testing.T, actual interface{}, expected interface{}) {
    t.Helper()
    if !reflect.DeepEqual(actual, expected) {
        t.Errorf("Assertion failed: %v != %v", actual, expected)
    }
}

Output:

=== RUN   TestFoo
    prog.go:13: Assertion failed: 4 != 3
--- FAIL: TestFoo (0.00s)
FAIL

where prog.go:13 in this playground is the line in the main test target that calls assertEqual instead of t.Errorf within it.

This will only change the file line in the testing output. If you actually want the full stack trace, you can use runtime.Caller as mentioned in these two threads.

blackgreen
  • 34,072
  • 23
  • 111
  • 129