Skip to content

Commit 6173029

Browse files
authored
Add option to run scenarios as *testing.T subtests (#419)
1 parent c6c2a08 commit 6173029

File tree

7 files changed

+171
-6
lines changed

7 files changed

+171
-6
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,54 @@ See implementation examples:
377377

378378
### Running Godog with go test
379379

380-
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.
380+
You may integrate running **godog** in your **go test** command.
381+
382+
#### Subtests of *testing.T
383+
384+
You can run test suite using go [Subtests](https://pkg.go.dev/testing#hdr-Subtests_and_Sub_benchmarks).
385+
In this case it is not necessary to have **godog** command installed. See the following example.
386+
387+
```go
388+
package main_test
389+
390+
import (
391+
"testing"
392+
393+
"github.com/cucumber/godog"
394+
)
395+
396+
func TestFeatures(t *testing.T) {
397+
suite := godog.TestSuite{
398+
ScenarioInitializer: func(s *godog.ScenarioContext) {
399+
// Add step definitions here.
400+
},
401+
Options: &godog.Options{
402+
Format: "pretty",
403+
Paths: []string{"features"},
404+
TestingT: t, // Testing instance that will run subtests.
405+
},
406+
}
407+
408+
if suite.Run() != 0 {
409+
t.Fatal("non-zero status returned, failed to run feature tests")
410+
}
411+
}
412+
```
413+
414+
Then you can run suite.
415+
```
416+
go test -test.v -test.run ^TestFeatures$
417+
```
418+
419+
Or a particular scenario.
420+
```
421+
go test -test.v -test.run ^TestFeatures$/^my_scenario$
422+
```
423+
424+
#### TestMain
425+
426+
You can run test suite using go [TestMain](https://golang.org/pkg/testing/#hdr-Main) func available since **go 1.4**.
427+
In this case it is not necessary to have **godog** command installed. See the following examples.
381428

382429
The following example binds **godog** flags with specified prefix `godog` in order to prevent flag collisions.
383430

example_subtests_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package godog_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/cucumber/godog"
7+
)
8+
9+
func ExampleTestSuite_Run_subtests() {
10+
var t *testing.T // Comes from your test function, e.g. func TestFeatures(t *testing.T).
11+
12+
suite := godog.TestSuite{
13+
ScenarioInitializer: func(s *godog.ScenarioContext) {
14+
// Add step definitions here.
15+
},
16+
Options: &godog.Options{
17+
Format: "pretty",
18+
Paths: []string{"features"},
19+
TestingT: t, // Testing instance that will run subtests.
20+
},
21+
}
22+
23+
if suite.Run() != 0 {
24+
t.Fatal("non-zero status returned, failed to run feature tests")
25+
}
26+
}
27+
28+
func TestFeatures(t *testing.T) {
29+
suite := godog.TestSuite{
30+
ScenarioInitializer: func(s *godog.ScenarioContext) {
31+
godog.InitializeScenario(s)
32+
33+
// Add step definitions here.
34+
},
35+
Options: &godog.Options{
36+
Format: "pretty",
37+
Paths: []string{"features"},
38+
TestingT: t, // Testing instance that will run subtests.
39+
},
40+
}
41+
42+
if suite.Run() != 0 {
43+
t.Fatal("non-zero status returned, failed to run feature tests")
44+
}
45+
}

internal/flags/options.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package flags
33
import (
44
"context"
55
"io"
6+
"testing"
67
)
78

89
// Options are suite run options
@@ -60,4 +61,7 @@ type Options struct {
6061

6162
// DefaultContext is used as initial context instead of context.Background().
6263
DefaultContext context.Context
64+
65+
// TestingT runs scenarios as subtests.
66+
TestingT *testing.T
6367
}

release-notes/v0.12.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ You can now use `string` instead of `*godog.DocString` in declaration.
112112

113113
With the introduction of go1.16, go1.16 is now officially supported.
114114

115+
### Running scenarios as subtests of *testing.T
116+
117+
You can now assign an instance of `*testing.T` to `godog.Options.TestingT` so that scenarios will be invoked with
118+
`t.Run` allowing granular control with standard Go tools.
119+
120+
[More info](https://github.com/cucumber/godog#running-godog-with-go-test).
121+
115122
Non Backward Compatible Changes
116123
-------------------------------
117124

run.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strconv"
1313
"strings"
1414
"sync"
15+
"testing"
1516

1617
"github.com/cucumber/messages-go/v16"
1718

@@ -38,6 +39,7 @@ type runner struct {
3839
stopOnFailure, strict bool
3940

4041
defaultContext context.Context
42+
testingT *testing.T
4143

4244
features []*models.Feature
4345

@@ -106,6 +108,7 @@ func (r *runner) concurrent(rate int) (failed bool) {
106108
strict: r.strict,
107109
storage: r.storage,
108110
defaultContext: r.defaultContext,
111+
testingT: r.testingT,
109112
}
110113

111114
if r.scenarioInitializer != nil {
@@ -236,6 +239,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
236239
runner.stopOnFailure = opt.StopOnFailure
237240
runner.strict = opt.Strict
238241
runner.defaultContext = opt.DefaultContext
242+
runner.testingT = opt.TestingT
239243

240244
// store chosen seed in environment, so it could be seen in formatter summary report
241245
os.Setenv("GODOG_SEED", strconv.FormatInt(runner.randomSeed, 10))

run_test.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,39 @@ func Test_AllFeaturesRun(t *testing.T) {
432432
assert.Equal(t, expected, actualOutput)
433433
}
434434

435+
func Test_AllFeaturesRunAsSubtests(t *testing.T) {
436+
const concurrency = 100
437+
const noRandomFlag = 0
438+
const format = "progress"
439+
440+
const expected = `...................................................................... 70
441+
...................................................................... 140
442+
...................................................................... 210
443+
...................................................................... 280
444+
........................................ 320
445+
446+
447+
83 scenarios (83 passed)
448+
320 steps (320 passed)
449+
0s
450+
`
451+
452+
actualStatus, actualOutput := testRunWithOptions(
453+
t,
454+
Options{
455+
Format: format,
456+
Concurrency: concurrency,
457+
Paths: []string{"features"},
458+
Randomize: noRandomFlag,
459+
TestingT: t,
460+
},
461+
InitializeScenario,
462+
)
463+
464+
assert.Equal(t, exitSuccess, actualStatus)
465+
assert.Equal(t, expected, actualOutput)
466+
}
467+
435468
func Test_FormatterConcurrencyRun(t *testing.T) {
436469
formatters := []string{
437470
"progress",
@@ -484,17 +517,30 @@ func testRun(
484517
randomSeed int64,
485518
featurePaths []string,
486519
) (int, string) {
487-
output := new(bytes.Buffer)
520+
t.Helper()
488521

489522
opts := Options{
490523
Format: format,
491-
NoColors: true,
492524
Paths: featurePaths,
493525
Concurrency: concurrency,
494526
Randomize: randomSeed,
495-
Output: output,
496527
}
497528

529+
return testRunWithOptions(t, opts, scenarioInitializer)
530+
}
531+
532+
func testRunWithOptions(
533+
t *testing.T,
534+
opts Options,
535+
scenarioInitializer func(*ScenarioContext),
536+
) (int, string) {
537+
t.Helper()
538+
539+
output := new(bytes.Buffer)
540+
541+
opts.Output = output
542+
opts.NoColors = true
543+
498544
status := TestSuite{
499545
Name: "succeed",
500546
ScenarioInitializer: scenarioInitializer,

suite.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"reflect"
77
"strings"
8+
"testing"
89

910
"github.com/cucumber/messages-go/v16"
1011

@@ -54,6 +55,7 @@ type suite struct {
5455
strict bool
5556

5657
defaultContext context.Context
58+
testingT *testing.T
5759

5860
// suite event handlers
5961
beforeScenarioHandlers []BeforeScenarioHook
@@ -442,7 +444,7 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
442444
return ErrUndefined
443445
}
444446

445-
// Before scenario hooks are aclled in context of first evaluated step
447+
// Before scenario hooks are called in context of first evaluated step
446448
// so that error from handler can be added to step.
447449

448450
pr := models.PickleResult{PickleID: pickle.Id, StartedAt: utils.TimeNowFunc()}
@@ -451,7 +453,17 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
451453
s.fmt.Pickle(pickle)
452454

453455
// scenario
454-
ctx, err = s.runSteps(ctx, pickle, pickle.Steps)
456+
if s.testingT != nil {
457+
// Running scenario as a subtest.
458+
s.testingT.Run(pickle.Name, func(t *testing.T) {
459+
ctx, err = s.runSteps(ctx, pickle, pickle.Steps)
460+
if err != nil {
461+
t.Error(err)
462+
}
463+
})
464+
} else {
465+
ctx, err = s.runSteps(ctx, pickle, pickle.Steps)
466+
}
455467

456468
// After scenario handlers are called in context of last evaluated step
457469
// so that error from handler can be added to step.

0 commit comments

Comments
 (0)