From 4ecf7fc077a6566c86b078de663c2123b8f84a86 Mon Sep 17 00:00:00 2001 From: Samuel Nelson Date: Sat, 10 Jan 2015 14:59:21 -0700 Subject: [PATCH] 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. --- suite/suite.go | 83 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/suite/suite.go b/suite/suite.go index a0f0945..a527101 100644 --- a/suite/suite.go +++ b/suite/suite.go @@ -6,12 +6,38 @@ import ( "os" "reflect" "regexp" + "runtime" + "strings" "testing" "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 // 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 -// 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) { + 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) 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{} for index := 0; index < methodFinder.NumMethod(); index++ { method := methodFinder.Method(index) @@ -55,8 +123,9 @@ func Run(t *testing.T, suite TestingSuite) { os.Exit(1) } if ok { + id.method = method.Name test := testing.InternalTest{ - Name: method.Name, + Name: id.Name(nameFormat), F: func(t *testing.T) { parentT := suite.T() suite.SetT(t) @@ -82,10 +151,10 @@ func Run(t *testing.T, suite TestingSuite) { } } -// Filtering method according to set regular expression -// specified command-line argument -m +// methodFilter filters method names similar to `go test -run regexp`. Note +// that methods must match testPattern prior to matching the passed in regexp. func methodFilter(name string) (bool, error) { - if ok, _ := regexp.MatchString("^Test", name); !ok { + if ok, _ := regexp.MatchString(testPattern, name); !ok { return false, nil } return regexp.MatchString(*matchMethod, name)