Add option to run scenarios as *testing.T subtests (#419)

Этот коммит содержится в:
Viacheslav Poturaev 2021-08-11 17:19:05 +02:00 коммит произвёл GitHub
родитель c6c2a0885b
коммит 61730298a5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 171 добавлений и 6 удалений

Просмотреть файл

@ -377,7 +377,54 @@ See implementation examples:
### Running Godog with go test ### 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. The following example binds **godog** flags with specified prefix `godog` in order to prevent flag collisions.

45
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")
}
}

Просмотреть файл

@ -3,6 +3,7 @@ package flags
import ( import (
"context" "context"
"io" "io"
"testing"
) )
// Options are suite run options // Options are suite run options
@ -60,4 +61,7 @@ type Options struct {
// DefaultContext is used as initial context instead of context.Background(). // DefaultContext is used as initial context instead of context.Background().
DefaultContext context.Context DefaultContext context.Context
// TestingT runs scenarios as subtests.
TestingT *testing.T
} }

Просмотреть файл

@ -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. 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 Non Backward Compatible Changes
------------------------------- -------------------------------

4
run.go
Просмотреть файл

@ -12,6 +12,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"testing"
"github.com/cucumber/messages-go/v16" "github.com/cucumber/messages-go/v16"
@ -38,6 +39,7 @@ type runner struct {
stopOnFailure, strict bool stopOnFailure, strict bool
defaultContext context.Context defaultContext context.Context
testingT *testing.T
features []*models.Feature features []*models.Feature
@ -106,6 +108,7 @@ func (r *runner) concurrent(rate int) (failed bool) {
strict: r.strict, strict: r.strict,
storage: r.storage, storage: r.storage,
defaultContext: r.defaultContext, defaultContext: r.defaultContext,
testingT: r.testingT,
} }
if r.scenarioInitializer != nil { if r.scenarioInitializer != nil {
@ -236,6 +239,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
runner.stopOnFailure = opt.StopOnFailure runner.stopOnFailure = opt.StopOnFailure
runner.strict = opt.Strict runner.strict = opt.Strict
runner.defaultContext = opt.DefaultContext runner.defaultContext = opt.DefaultContext
runner.testingT = opt.TestingT
// store chosen seed in environment, so it could be seen in formatter summary report // store chosen seed in environment, so it could be seen in formatter summary report
os.Setenv("GODOG_SEED", strconv.FormatInt(runner.randomSeed, 10)) os.Setenv("GODOG_SEED", strconv.FormatInt(runner.randomSeed, 10))

Просмотреть файл

@ -432,6 +432,39 @@ func Test_AllFeaturesRun(t *testing.T) {
assert.Equal(t, expected, actualOutput) 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) { func Test_FormatterConcurrencyRun(t *testing.T) {
formatters := []string{ formatters := []string{
"progress", "progress",
@ -484,17 +517,30 @@ func testRun(
randomSeed int64, randomSeed int64,
featurePaths []string, featurePaths []string,
) (int, string) { ) (int, string) {
output := new(bytes.Buffer) t.Helper()
opts := Options{ opts := Options{
Format: format, Format: format,
NoColors: true,
Paths: featurePaths, Paths: featurePaths,
Concurrency: concurrency, Concurrency: concurrency,
Randomize: randomSeed, 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{ status := TestSuite{
Name: "succeed", Name: "succeed",
ScenarioInitializer: scenarioInitializer, ScenarioInitializer: scenarioInitializer,

Просмотреть файл

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"testing"
"github.com/cucumber/messages-go/v16" "github.com/cucumber/messages-go/v16"
@ -54,6 +55,7 @@ type suite struct {
strict bool strict bool
defaultContext context.Context defaultContext context.Context
testingT *testing.T
// suite event handlers // suite event handlers
beforeScenarioHandlers []BeforeScenarioHook beforeScenarioHandlers []BeforeScenarioHook
@ -442,7 +444,7 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
return ErrUndefined 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. // so that error from handler can be added to step.
pr := models.PickleResult{PickleID: pickle.Id, StartedAt: utils.TimeNowFunc()} 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) s.fmt.Pickle(pickle)
// scenario // 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 // After scenario handlers are called in context of last evaluated step
// so that error from handler can be added to step. // so that error from handler can be added to step.