From 61730298a5326205b4dc752c389efbb0d8a7f437 Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Wed, 11 Aug 2021 17:19:05 +0200 Subject: [PATCH] Add option to run scenarios as *testing.T subtests (#419) --- README.md | 49 +++++++++++++++++++++++++++++++++++- example_subtests_test.go | 45 +++++++++++++++++++++++++++++++++ internal/flags/options.go | 4 +++ release-notes/v0.12.0.md | 7 ++++++ run.go | 4 +++ run_test.go | 52 ++++++++++++++++++++++++++++++++++++--- suite.go | 16 ++++++++++-- 7 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 example_subtests_test.go diff --git a/README.md b/README.md index 3aa722a..cdebefe 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,54 @@ See implementation examples: ### Running Godog with go test -You may integrate running **godog** in your **go test** command. You can run it using go [TestMain](https://golang.org/pkg/testing/#hdr-Main) func available since **go 1.4**. In this case it is not necessary to have **godog** command installed. See the following examples. +You may integrate running **godog** in your **go test** command. + +#### Subtests of *testing.T + +You can run test suite using go [Subtests](https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks). +In this case it is not necessary to have **godog** command installed. See the following example. + +```go +package main_test + +import ( + "testing" + + "github.com/cucumber/godog" +) + +func TestFeatures(t *testing.T) { + suite := godog.TestSuite{ + ScenarioInitializer: func(s *godog.ScenarioContext) { + // Add step definitions here. + }, + Options: &godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + TestingT: t, // Testing instance that will run subtests. + }, + } + + if suite.Run() != 0 { + t.Fatal("non-zero status returned, failed to run feature tests") + } +} +``` + +Then you can run suite. +``` +go test -test.v -test.run ^TestFeatures$ +``` + +Or a particular scenario. +``` +go test -test.v -test.run ^TestFeatures$/^my_scenario$ +``` + +#### TestMain + +You can run test suite using go [TestMain](https://golang.org/pkg/testing/#hdr-Main) func available since **go 1.4**. +In this case it is not necessary to have **godog** command installed. See the following examples. The following example binds **godog** flags with specified prefix `godog` in order to prevent flag collisions. diff --git a/example_subtests_test.go b/example_subtests_test.go new file mode 100644 index 0000000..55de2ac --- /dev/null +++ b/example_subtests_test.go @@ -0,0 +1,45 @@ +package godog_test + +import ( + "testing" + + "github.com/cucumber/godog" +) + +func ExampleTestSuite_Run_subtests() { + var t *testing.T // Comes from your test function, e.g. func TestFeatures(t *testing.T). + + suite := godog.TestSuite{ + ScenarioInitializer: func(s *godog.ScenarioContext) { + // Add step definitions here. + }, + Options: &godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + TestingT: t, // Testing instance that will run subtests. + }, + } + + if suite.Run() != 0 { + t.Fatal("non-zero status returned, failed to run feature tests") + } +} + +func TestFeatures(t *testing.T) { + suite := godog.TestSuite{ + ScenarioInitializer: func(s *godog.ScenarioContext) { + godog.InitializeScenario(s) + + // Add step definitions here. + }, + Options: &godog.Options{ + Format: "pretty", + Paths: []string{"features"}, + TestingT: t, // Testing instance that will run subtests. + }, + } + + if suite.Run() != 0 { + t.Fatal("non-zero status returned, failed to run feature tests") + } +} diff --git a/internal/flags/options.go b/internal/flags/options.go index 7392bdc..69e39d4 100644 --- a/internal/flags/options.go +++ b/internal/flags/options.go @@ -3,6 +3,7 @@ package flags import ( "context" "io" + "testing" ) // Options are suite run options @@ -60,4 +61,7 @@ type Options struct { // DefaultContext is used as initial context instead of context.Background(). DefaultContext context.Context + + // TestingT runs scenarios as subtests. + TestingT *testing.T } diff --git a/release-notes/v0.12.0.md b/release-notes/v0.12.0.md index 6321ef2..3e52693 100644 --- a/release-notes/v0.12.0.md +++ b/release-notes/v0.12.0.md @@ -112,6 +112,13 @@ You can now use `string` instead of `*godog.DocString` in declaration. With the introduction of go1.16, go1.16 is now officially supported. +### Running scenarios as subtests of *testing.T + +You can now assign an instance of `*testing.T` to `godog.Options.TestingT` so that scenarios will be invoked with +`t.Run` allowing granular control with standard Go tools. + +[More info](https://github.com/cucumber/godog#running-godog-with-go-test). + Non Backward Compatible Changes ------------------------------- diff --git a/run.go b/run.go index b3e0408..7d2999f 100644 --- a/run.go +++ b/run.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "sync" + "testing" "github.com/cucumber/messages-go/v16" @@ -38,6 +39,7 @@ type runner struct { stopOnFailure, strict bool defaultContext context.Context + testingT *testing.T features []*models.Feature @@ -106,6 +108,7 @@ func (r *runner) concurrent(rate int) (failed bool) { strict: r.strict, storage: r.storage, defaultContext: r.defaultContext, + testingT: r.testingT, } if r.scenarioInitializer != nil { @@ -236,6 +239,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int { runner.stopOnFailure = opt.StopOnFailure runner.strict = opt.Strict runner.defaultContext = opt.DefaultContext + runner.testingT = opt.TestingT // store chosen seed in environment, so it could be seen in formatter summary report os.Setenv("GODOG_SEED", strconv.FormatInt(runner.randomSeed, 10)) diff --git a/run_test.go b/run_test.go index 577ea96..9d3a8c1 100644 --- a/run_test.go +++ b/run_test.go @@ -432,6 +432,39 @@ func Test_AllFeaturesRun(t *testing.T) { assert.Equal(t, expected, actualOutput) } +func Test_AllFeaturesRunAsSubtests(t *testing.T) { + const concurrency = 100 + const noRandomFlag = 0 + const format = "progress" + + const expected = `...................................................................... 70 +...................................................................... 140 +...................................................................... 210 +...................................................................... 280 +........................................ 320 + + +83 scenarios (83 passed) +320 steps (320 passed) +0s +` + + actualStatus, actualOutput := testRunWithOptions( + t, + Options{ + Format: format, + Concurrency: concurrency, + Paths: []string{"features"}, + Randomize: noRandomFlag, + TestingT: t, + }, + InitializeScenario, + ) + + assert.Equal(t, exitSuccess, actualStatus) + assert.Equal(t, expected, actualOutput) +} + func Test_FormatterConcurrencyRun(t *testing.T) { formatters := []string{ "progress", @@ -484,17 +517,30 @@ func testRun( randomSeed int64, featurePaths []string, ) (int, string) { - output := new(bytes.Buffer) + t.Helper() opts := Options{ Format: format, - NoColors: true, Paths: featurePaths, Concurrency: concurrency, Randomize: randomSeed, - Output: output, } + return testRunWithOptions(t, opts, scenarioInitializer) +} + +func testRunWithOptions( + t *testing.T, + opts Options, + scenarioInitializer func(*ScenarioContext), +) (int, string) { + t.Helper() + + output := new(bytes.Buffer) + + opts.Output = output + opts.NoColors = true + status := TestSuite{ Name: "succeed", ScenarioInitializer: scenarioInitializer, diff --git a/suite.go b/suite.go index 4d6403c..1b8fca2 100644 --- a/suite.go +++ b/suite.go @@ -5,6 +5,7 @@ import ( "fmt" "reflect" "strings" + "testing" "github.com/cucumber/messages-go/v16" @@ -54,6 +55,7 @@ type suite struct { strict bool defaultContext context.Context + testingT *testing.T // suite event handlers beforeScenarioHandlers []BeforeScenarioHook @@ -442,7 +444,7 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) { return ErrUndefined } - // Before scenario hooks are aclled in context of first evaluated step + // Before scenario hooks are called in context of first evaluated step // so that error from handler can be added to step. pr := models.PickleResult{PickleID: pickle.Id, StartedAt: utils.TimeNowFunc()} @@ -451,7 +453,17 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) { s.fmt.Pickle(pickle) // scenario - ctx, err = s.runSteps(ctx, pickle, pickle.Steps) + if s.testingT != nil { + // Running scenario as a subtest. + s.testingT.Run(pickle.Name, func(t *testing.T) { + ctx, err = s.runSteps(ctx, pickle, pickle.Steps) + if err != nil { + t.Error(err) + } + }) + } else { + ctx, err = s.runSteps(ctx, pickle, pickle.Steps) + } // After scenario handlers are called in context of last evaluated step // so that error from handler can be added to step.