Testy – an extensible facade around Go's core testing library

Testy is the first Go library I’ve written to share with the community. The name is a play on testing and the emotion I had working with the standard testing package. I offer it up to help others that might feel the same way. :grin:

(Kind) feedback would be much appreciated. An excerpt from the README page follows.

##Testy – An extensible testing facade

If Go’s standard testing package annoys you, you might like Testy.

There is a lot to like about Go’s testing package.

There are also two extremely annoying things about it:

  1. You can’t refactor repetitive tests without reporting errors from the wrong place in the code.
  2. The testing package is locked down tight, preventing trivial solutions to the prior problem.

Testy implements a facade around the testing package and hijacks its logging features.

This means:

  • You can report test errors at any level up the call stack.
  • You can label all errors in a scope to disambiguate repetitive tests.

The downside is an extra level of log message nesting (which your editor’s quickfix window should ignore, anyway).

Unlike many other Go testing libraries, it doesn’t offer an extensive testing framework. Unlike some assertion libraries, it doesn’t use stack traces or race-prone print statements.

Testy offers a simple, extensible solution focused on reporting errors simply and in the right place. But it does give a few convenient helper functions for the most common tests you’re likely to write.

Examples

Using a custom helper function

package example

import (
	"github.com/xdg/testy"
	"testing"
)

func TestExample1(t *testing.T) {
	is := testy.New(t)
	defer func() { t.Logf(is.Done()) }() // Line 10
	is.Error("First failure")            // Line 11
	checkTrue(is, 1+1 == 3)              // Line 12
}

func checkTrue(is *testy.T, cond bool) {
	if !cond {
		is.Uplevel(1).Error("Expression was not true")
	}
}

In the TestExample1 function, the is variable wraps the test variable, t. The defer closure schedules test logging output to be delivered to t via is.Done() when the test function exits.

When run in Vim, with vim-go, the quickfix window looks like this:

_examples/example1_test.go|10| TestExample1: 2 tests failed
_examples/example1_test.go|11| First failure
_examples/example1_test.go|12| Expression was not true

Note that the checkTrue error is reported from the call to checkTrue at line 12, not from inside the checkTrue function. The Uplevel method in checkTrue tells Testy to report the error one level up the call stack.

Using Testy helpers

The checkTrue pattern is so common that testing true and false are built-in to Testy as True and False. There are also Equal and Unequal helpers that use reflect.DeepEqual but provide diagnostic output on subsequent lines:

package example

import (
	"github.com/xdg/testy"
	"testing"
)

type pair struct {
	x float32
	y float32
}

func TestExample2(t *testing.T) {
	is := testy.New(t)
	defer func() { t.Logf(is.Done()) }()

	is.True(1+1 == 3)                          // Line 17
	is.False(2 == 2)                           // Line 18
	is.Equal(1, 2)                             // Line 19
	is.Equal(1.0, 1)                           // Line 20
	is.Equal("foo\tbar", "foo\tbaz")           // Line 21
	is.Equal(true, false)                      // Line 22
	is.Equal(&pair{1.0, 1.0}, &pair{1.1, 1.0}) // Line 23
	is.Unequal(42, 42)                         // Line 24
}

The diagnostic output quotes strings and indicates types where necessary to disambiguate. For example:

_examples/example2_test.go|15| TestExample2: 8 tests failed
_examples/example2_test.go|17| Expression was not true
_examples/example2_test.go|18| Expression was not false
_examples/example2_test.go|19| Values were not equal:
|| 			   Got: 1 (int)
|| 			Wanted: 2 (int)
_examples/example2_test.go|20| Values were not equal:
|| 			   Got: 1 (float64)
|| 			Wanted: 1 (int)
_examples/example2_test.go|21| Values were not equal:
|| 			   Got: "foo\tbar"
|| 			Wanted: "foo\tbaz"
_examples/example2_test.go|22| Values were not equal:
|| 			   Got: true
|| 			Wanted: false
_examples/example2_test.go|23| Values were not equal:
|| 			   Got: &{1 1} (*example.pair)
|| 			Wanted: &{1.1 1} (*example.pair)
_examples/example2_test.go|24| Values were not unequal:
|| 			   Got: 42 (int)

Please see the full README file for more examples…

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.