Allow custom test name formats

When using suites, test names were just the raw suite method name, but that's often not
detailed enough to know what the problem is.  This commit adds the ability to pass a custom
name format using certain formatting marks (sort of similar to templating strings) to
include the name of the function that called Run or NamedRun, the name of the suite's type,
and/or the name of the method.
pull/115/head
Samuel Nelson 2015-01-10 14:59:21 -07:00
parent 8bb32e75f4
commit 4ecf7fc077
1 changed files with 76 additions and 7 deletions

View File

@ -6,12 +6,38 @@ import (
"os" "os"
"reflect" "reflect"
"regexp" "regexp"
"runtime"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var matchMethod = flag.String("m", "", "regular expression to select tests of the suite to run") const (
testPattern = "^Test"
defaultFormat = "{method}"
)
var (
matchMethod = flag.String("m", "", "Run only those test methods matching the regular expression.")
format = flag.String("nameformat", "",
"The format to use for test name output. Use {function} for the test function name, "+
"{suite} for the suite type name, and {method} for the test method name. Overrides "+
"formatting set by NamedRun.")
)
type testIdentifier struct {
function string
suite string
method string
}
func (t testIdentifier) Name(format string) string {
name := strings.Replace(format, "{function}", t.function, -1)
name = strings.Replace(name, "{suite}", t.suite, -1)
name = strings.Replace(name, "{method}", t.method, -1)
return name
}
// Suite is a basic testing suite with methods for storing and // Suite is a basic testing suite with methods for storing and
// retrieving the current *testing.T context. // retrieving the current *testing.T context.
@ -32,8 +58,44 @@ func (suite *Suite) SetT(t *testing.T) {
} }
// Run takes a testing suite and runs all of the tests attached // Run takes a testing suite and runs all of the tests attached
// to it. // to it. For legacy reasons, Run uses a default test name format
// of "{method}", which only prints out the test method name. See
// NamedRun for a version of Run that accepts other format strings
// for the test name in testing output.
func Run(t *testing.T, suite TestingSuite) { func Run(t *testing.T, suite TestingSuite) {
run("", t, suite)
}
// NamedRun performs a test run the same as Run, but with a different
// test name format. Pass a pattern to use as your test names for
// nameFormat. The following patterns will be replaced by details
// about the current test:
//
// * "{function}": The name of the function that called NamedRun.
// * "{suite}": The name of the testing suite's underlying type.
// * "{method}": The name of the test method.
//
// Be aware that the test flag "nameformat" will override nameFormat
// globally.
func NamedRun(nameFormat string, t *testing.T, suite TestingSuite) {
run(nameFormat, t, suite)
}
// run is used to ensure that Run and NamedRun (and any other suite
// running functions) are at the same calling stack depth.
func run(nameFormat string, t *testing.T, suite TestingSuite) {
if nameFormat == "" {
nameFormat = defaultFormat
}
if *format != "" {
nameFormat = *format
}
id := testIdentifier{}
if callPC, _, _, ok := runtime.Caller(2); ok {
id.function = runtime.FuncForPC(callPC).Name()
id.function = id.function[strings.LastIndex(id.function, ".")+1:]
}
suite.SetT(t) suite.SetT(t)
if setupAllSuite, ok := suite.(SetupAllSuite); ok { if setupAllSuite, ok := suite.(SetupAllSuite); ok {
@ -45,7 +107,13 @@ func Run(t *testing.T, suite TestingSuite) {
} }
}() }()
methodFinder := reflect.TypeOf(suite) suiteType := reflect.TypeOf(suite)
methodFinder := suiteType
if suiteType.Kind() == reflect.Ptr {
suiteType = suiteType.Elem()
}
id.suite = suiteType.Name()
tests := []testing.InternalTest{} tests := []testing.InternalTest{}
for index := 0; index < methodFinder.NumMethod(); index++ { for index := 0; index < methodFinder.NumMethod(); index++ {
method := methodFinder.Method(index) method := methodFinder.Method(index)
@ -55,8 +123,9 @@ func Run(t *testing.T, suite TestingSuite) {
os.Exit(1) os.Exit(1)
} }
if ok { if ok {
id.method = method.Name
test := testing.InternalTest{ test := testing.InternalTest{
Name: method.Name, Name: id.Name(nameFormat),
F: func(t *testing.T) { F: func(t *testing.T) {
parentT := suite.T() parentT := suite.T()
suite.SetT(t) suite.SetT(t)
@ -82,10 +151,10 @@ func Run(t *testing.T, suite TestingSuite) {
} }
} }
// Filtering method according to set regular expression // methodFilter filters method names similar to `go test -run regexp`. Note
// specified command-line argument -m // that methods must match testPattern prior to matching the passed in regexp.
func methodFilter(name string) (bool, error) { func methodFilter(name string) (bool, error) {
if ok, _ := regexp.MatchString("^Test", name); !ok { if ok, _ := regexp.MatchString(testPattern, name); !ok {
return false, nil return false, nil
} }
return regexp.MatchString(*matchMethod, name) return regexp.MatchString(*matchMethod, name)