testy

package module
v0.0.0-...-5b15482 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2017 License: Apache-2.0 Imports: 9 Imported by: 0

README

GoDoc Build Status

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.

The Go FAQ suggests that table-driven testing is the way to avoid repetitive test code. If that works for you for all situations, fine.

Testy has a different solution.

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

Consider this simple example:

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)

Using error labels

To prefix error messages with some descriptive text, you can use the Label method like this:

package example

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

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

	for i := 1; i <= 5; i++ {
		is.Label("Checking", i).True(i == 3) // Line 13
	}
}

The output with labels looks like this, making it clear which tests failed:

_examples/example3_test.go|10| TestExample3: 4 tests failed
_examples/example3_test.go|13| Checking 1: Expression was not true
_examples/example3_test.go|13| Checking 2: Expression was not true
_examples/example3_test.go|13| Checking 4: Expression was not true
_examples/example3_test.go|13| Checking 5: Expression was not true

Combining Uplevel and Label in a new facade

Because Uplevel and Label just return new facades, you can chain them at the start of a helper function and modify all subsequent logging:

package example

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

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

	for i := -1; i <= 2; i++ {
		checkEvenPositive(is, i) // Line 13
	}
}

func checkEvenPositive(is *testy.T, n int) {
	// replace 'is' with a labeled, upleveled equivalent
	is = is.Uplevel(1).Label("Testing", n)

	if n < 1 {
		is.Error("was not positive")
	}
	if n%2 != 0 {
		is.Error("was not even")
	}
}

This lets you write test helpers that report errors where they are called (line 13 in this case), but with detailed errors you can tie back to the original input data:

_examples/example4_test.go|10| TestExample4: 4 tests failed
_examples/example4_test.go|13| Testing -1: was not positive
_examples/example4_test.go|13| Testing -1: was not even
_examples/example4_test.go|13| Testing 0: was not positive
_examples/example4_test.go|13| Testing 1: was not even

Copyright 2015 by David A. Golden. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Documentation

Overview

Package testy is an extensible facade around Go's core testing library.

Go's core testing package doesn't let you refactor repetitive tests without reporting errors from the wrong place in the code. 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).

It gives a few convenient helper functions for common cases and makes it easy to implement your own.

The following example shows how to set up testy and use test helpers. In particular, note the use of 'defer' and a closure to schedule testy to output the log at the end of the function's execution.

package example

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

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

	is.True(1+1 == 3)
	is.False(2 == 2)

	is.Equal(1, 2)
	is.Equal(1.0, 1)
	is.Equal("foo\tbar", "foo\tbaz")
	is.Equal(true, false)

	is.Unequal(42, 42)

	is.NotNil(is)
}

Each error will be reported at the calling line. Calls to 'Equal' and 'Unequal' will return diagnostic details. Here is how some of the output would look in Vim's quickfix window:

...
_examples/example_test.go|15| Values were not equal:
|| 			   Got: 1 (int)
|| 			Wanted: 2 (int)
_examples/example_test.go|16| Values were not equal:
|| 			   Got: 1 (float64)
|| 			Wanted: 1 (int)
_examples/example_test.go|17| Values were not equal:
|| 			   Got: "foo\tbar"
|| 			Wanted: "foo\tbaz"
...

You can use the 'Uplevel' and 'Label' methods to return new facades, which you can use to implement custom helpers in various ways:

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

	// Check for numbers equal to 3
	for i := 1; i <= 5; i++ {
		is.Label("Checking", i).True(i == 3) // Line 13
	}

	// Check for positive, even numbers
	for i := -1; i <= 2; i++ {
		checkEvenPositive(is, i)             // Line 18
	}
}

func checkEvenPositive(is *testy.T, n int) {
	// Report one level up with a custom label
	is = is.Uplevel(1).Label("Testing", n)

	if n < 1 {
		is.Error("Value was not positive")
	}
	if n%2 != 0 {
		is.Error("Value was not even")
	}
}

The example above would return errors to a quickfix window like this:

...
_examples/example_test.go|13| Checking 1: Expression was not true
_examples/example_test.go|13| Checking 2: Expression was not true
_examples/example_test.go|13| Checking 4: Expression was not true
_examples/example_test.go|13| Checking 5: Expression was not true
_examples/example_test.go|18| Testing -1: Value was not positive
_examples/example_test.go|18| Testing -1: Value was not even
_examples/example_test.go|18| Testing 0: Value was not positive
_examples/example_test.go|18| Testing 1: Value was not even
...

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type T

type T struct {
	// contains filtered or unexported fields
}

T is a facade around the testing.T type passed to Test functions. It intercepts log messages to attribute them to the correct level of the call stack.

func New

func New(t *testing.T) *T

New wraps a testy.T struct around a testing.T struct. The resulting struct can be used in the same way the testing.T struct would be, plus has additional methods specific to Testy. It calls NewCase with the calling function's name as the test case name.

func NewCase

func NewCase(t *testing.T, name string) *T

NewCase wraps a testy.T struct around a testing.T struct. The resulting struct can be used in the same way the testing.T struct would be, plus has additional methods specific to Testy. It takes a name argument that is used in the summary line during log output.

func (*T) Done

func (t *T) Done() string

Done returns any test log output formatted suitably for passing to a testing.T struct Logf method.

func (*T) Equal

func (t *T) Equal(got, want interface{})

Equal checks if its arguments are equal using reflect.DeepEqual. It is subject to all the usual limitations of that function. If the values are not equal, an error is logged and the 'got' and 'want' values are logged on subsequent lines for comparison.

func (*T) Error

func (t *T) Error(args ...interface{})

Error is equivalent to Log followed by Fail

func (*T) Errorf

func (t *T) Errorf(format string, args ...interface{})

Errorf is equivalent to Logf followed by Fail

func (*T) Fail

func (t *T) Fail()

Fail marks the test as having failed.

func (T) FailCount

func (t T) FailCount() int

FailCount returns the number of Fail, Error, Fatal or test helper failures recorded by the testy.T struct.

func (*T) FailNow

func (t *T) FailNow()

FailNow marks the test as having failed and stops execution. It is subject to the same restrictions as FailNow from the testing package.

func (*T) Failed

func (t *T) Failed() bool

Failed reports whether the test has been marked as having failed.

func (*T) False

func (t *T) False(cond bool)

False checks if its argument is false; if true, it logs an error.

func (*T) Fatal

func (t *T) Fatal(args ...interface{})

Fatal is equivalent to Log followed by FailNow

func (*T) Fatalf

func (t *T) Fatalf(format string, args ...interface{})

Fatalf is equivalent to Logf followed by FailNow

func (T) Label

func (t T) Label(s ...interface{}) *T

Label returns a testy.T struct that will prefix a label to all log messages. The label is constructed by concatenating arguments separated by a space (like fmt.Sprintln without the trailing space). A colon character and space will be added automatically

func (*T) Log

func (t *T) Log(args ...interface{})

Log joins its arguments by spaces like fmt.Sprintln and records the result for later delivery by the Done method.

func (*T) Logf

func (t *T) Logf(format string, args ...interface{})

Logf joins its arguments like fmt.Sprintf and records the result for later delivery by the Done method.

func (*T) Nil

func (t *T) Nil(got interface{})

Nil checks if its argument is nil (literal or nil slice, map, etc.); if non-nil, it logs an error.

func (*T) NotNil

func (t *T) NotNil(got interface{})

Nil checks if its argument is nil (literal or nil slice, map, etc.); if non-nil, it logs an error.

func (T) Output

func (t T) Output() []string

Output returns a copy of the slice of log messages recorded by the testy.T struct.

func (*T) Skip

func (t *T) Skip(args ...interface{})

Skip is equivalent to Log followed by SkipNow

func (*T) SkipNow

func (t *T) SkipNow()

SkipNow marks the test as skipped and halts execution. It is subject to the same restrictions as SkipNow from the testing package.

func (*T) Skipf

func (t *T) Skipf(format string, args ...interface{})

Skipf is equivalent to Logf followed by SkipNow

func (*T) Skipped

func (t *T) Skipped() bool

Skipped reports whether the test was skipped.

func (*T) True

func (t *T) True(cond bool)

True checks if its argument is true; if false, it logs an error.

func (*T) Unequal

func (t *T) Unequal(got, want interface{})

Unequal inverts the logic of Equal but is otherwise similar.

func (T) Uplevel

func (t T) Uplevel(depth int) *T

Uplevel returns a testy.T struct that will report log messages 'depth' frames higher up the call stack.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL