Added the new TestSuiteInitializer and ScenarioInitializer
Этот коммит содержится в:
родитель
7568b291e4
коммит
e6227a2e0f
14 изменённых файлов: 470 добавлений и 115 удалений
44
README.md
44
README.md
|
@ -160,7 +160,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
messages "github.com/cucumber/messages-go/v10"
|
||||
)
|
||||
|
||||
func thereAreGodogs(available int) error {
|
||||
|
@ -183,12 +182,12 @@ func thereShouldBeRemaining(remaining int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
||||
s.Step(`^I eat (\d+)$`, iEat)
|
||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||
|
||||
s.BeforeScenario(func(*messages.Pickle) {
|
||||
s.BeforeScenario(func(*godog.Scenario) {
|
||||
Godogs = 0 // clean the state before every scenario
|
||||
})
|
||||
}
|
||||
|
@ -250,22 +249,24 @@ The following example binds **godog** flags with specified prefix `godog`
|
|||
in order to prevent flag collisions.
|
||||
|
||||
``` go
|
||||
var opt = godog.Options{
|
||||
var opts = godog.Options{
|
||||
Output: colors.Colored(os.Stdout),
|
||||
Format: "progress", // can define default values
|
||||
}
|
||||
|
||||
func init() {
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opt)
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opts)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
opt.Paths = flag.Args()
|
||||
opts.Paths = flag.Args()
|
||||
|
||||
status := godog.RunWithOptions("godogs", func(s *godog.Suite) {
|
||||
FeatureContext(s)
|
||||
}, opt)
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
ScenarioInitializer: ScenarioContext,
|
||||
Options: &opts,
|
||||
}.Run()
|
||||
|
||||
if st := m.Run(); st > status {
|
||||
status = st
|
||||
|
@ -287,13 +288,17 @@ configuring needed options.
|
|||
|
||||
``` go
|
||||
func TestMain(m *testing.M) {
|
||||
status := godog.RunWithOptions("godog", func(s *godog.Suite) {
|
||||
FeatureContext(s)
|
||||
}, godog.Options{
|
||||
opts := godog.Options{
|
||||
Format: "progress",
|
||||
Paths: []string{"features"},
|
||||
Randomize: time.Now().UTC().UnixNano(), // randomize scenario execution order
|
||||
})
|
||||
}
|
||||
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
ScenarioInitializer: ScenarioContext,
|
||||
Options: opts,
|
||||
}.Run()
|
||||
|
||||
if st := m.Run(); st > status {
|
||||
status = st
|
||||
|
@ -315,12 +320,17 @@ func TestMain(m *testing.M) {
|
|||
break
|
||||
}
|
||||
}
|
||||
status := godog.RunWithOptions("godog", func(s *godog.Suite) {
|
||||
godog.SuiteContext(s)
|
||||
}, godog.Options{
|
||||
|
||||
opts := godog.Options{
|
||||
Format: format,
|
||||
Paths: []string{"features"},
|
||||
})
|
||||
}
|
||||
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
ScenarioInitializer: ScenarioContext,
|
||||
Options: opts,
|
||||
}.Run()
|
||||
|
||||
if st := m.Run(); st > status {
|
||||
status = st
|
||||
|
|
|
@ -56,7 +56,6 @@ need to store state within steps (a response), we should introduce a structure w
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/cucumber/gherkin-go/v11"
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
|
@ -71,11 +70,11 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
|||
return godog.ErrPending
|
||||
}
|
||||
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) error {
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
api := &apiFeature{}
|
||||
s.Step(`^I send "([^"]*)" request to "([^"]*)"$`, api.iSendrequestTo)
|
||||
s.Step(`^the response code should be (\d+)$`, api.theResponseCodeShouldBe)
|
||||
|
@ -98,7 +97,6 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/cucumber/gherkin-go/v11"
|
||||
"github.com/cucumber/godog"
|
||||
)
|
||||
|
||||
|
@ -142,7 +140,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) error {
|
||||
var expected, actual []byte
|
||||
var data interface{}
|
||||
if err = json.Unmarshal([]byte(body.Content), &data); err != nil {
|
||||
|
@ -158,7 +156,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgumen
|
|||
return
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
api := &apiFeature{}
|
||||
|
||||
s.BeforeScenario(api.resetResponse)
|
||||
|
|
|
@ -8,14 +8,13 @@ import (
|
|||
"reflect"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/messages-go/v10"
|
||||
)
|
||||
|
||||
type apiFeature struct {
|
||||
resp *httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (a *apiFeature) resetResponse(*messages.Pickle) {
|
||||
func (a *apiFeature) resetResponse(*godog.Scenario) {
|
||||
a.resp = httptest.NewRecorder()
|
||||
}
|
||||
|
||||
|
@ -51,7 +50,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) (err error) {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) (err error) {
|
||||
var expected, actual interface{}
|
||||
|
||||
// re-encode expected response
|
||||
|
@ -71,7 +70,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgumen
|
|||
return nil
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
api := &apiFeature{}
|
||||
|
||||
s.BeforeScenario(api.resetResponse)
|
||||
|
|
|
@ -6,10 +6,10 @@ Feature: eat godogs
|
|||
|
||||
Scenario: Eat 5 out of 12
|
||||
Given there are 12 godogs
|
||||
When I eat 4
|
||||
When I eat 5
|
||||
Then there should be 7 remaining
|
||||
|
||||
Scenario: Eat 12 out of 12
|
||||
Given there are 12 godogs
|
||||
When I eat 11
|
||||
When I eat 12
|
||||
Then there should be none remaining
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* file: $GOPATH/src/assert-godogs/godogs_test.go */
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -9,23 +8,24 @@ import (
|
|||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/colors"
|
||||
messages "github.com/cucumber/messages-go/v10"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var opt = godog.Options{Output: colors.Colored(os.Stdout)}
|
||||
var opts = godog.Options{Output: colors.Colored(os.Stdout)}
|
||||
|
||||
func init() {
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opt)
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opts)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
opt.Paths = flag.Args()
|
||||
opts.Paths = flag.Args()
|
||||
|
||||
status := godog.RunWithOptions("godogs", func(s *godog.Suite) {
|
||||
FeatureContext(s)
|
||||
}, opt)
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
ScenarioInitializer: ScenarioContext,
|
||||
Options: &opts,
|
||||
}.Run()
|
||||
|
||||
if st := m.Run(); st > status {
|
||||
status = st
|
||||
|
@ -65,13 +65,13 @@ func thereShouldBeNoneRemaining() error {
|
|||
)
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
||||
s.Step(`^I eat (\d+)$`, iEat)
|
||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||
s.Step(`^there should be none remaining$`, thereShouldBeNoneRemaining)
|
||||
|
||||
s.BeforeScenario(func(*messages.Pickle) {
|
||||
s.BeforeScenario(func(*godog.Scenario) {
|
||||
Godogs = 0 // clean the state before every scenario
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
txdb "github.com/DATA-DOG/go-txdb"
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/messages-go/v10"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -24,7 +23,7 @@ type apiFeature struct {
|
|||
resp *httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (a *apiFeature) resetResponse(*messages.Pickle) {
|
||||
func (a *apiFeature) resetResponse(*godog.Scenario) {
|
||||
a.resp = httptest.NewRecorder()
|
||||
if a.db != nil {
|
||||
a.db.Close()
|
||||
|
@ -71,7 +70,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) (err error) {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *godog.DocString) (err error) {
|
||||
var expected, actual interface{}
|
||||
|
||||
// re-encode expected response
|
||||
|
@ -91,7 +90,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgumen
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apiFeature) thereAreUsers(users *messages.PickleStepArgument_PickleTable) error {
|
||||
func (a *apiFeature) thereAreUsers(users *godog.Table) error {
|
||||
var fields []string
|
||||
var marks []string
|
||||
head := users.Rows[0].Cells
|
||||
|
@ -123,7 +122,7 @@ func (a *apiFeature) thereAreUsers(users *messages.PickleStepArgument_PickleTabl
|
|||
return nil
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
api := &apiFeature{}
|
||||
|
||||
s.BeforeScenario(api.resetResponse)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* file: $GOPATH/src/godogs/godogs_test.go */
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -9,22 +8,23 @@ import (
|
|||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/cucumber/godog/colors"
|
||||
messages "github.com/cucumber/messages-go/v10"
|
||||
)
|
||||
|
||||
var opt = godog.Options{Output: colors.Colored(os.Stdout)}
|
||||
var opts = godog.Options{Output: colors.Colored(os.Stdout)}
|
||||
|
||||
func init() {
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opt)
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opts)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
opt.Paths = flag.Args()
|
||||
opts.Paths = flag.Args()
|
||||
|
||||
status := godog.RunWithOptions("godogs", func(s *godog.Suite) {
|
||||
FeatureContext(s)
|
||||
}, opt)
|
||||
status := godog.TestSuite{
|
||||
Name: "godogs",
|
||||
ScenarioInitializer: ScenarioContext,
|
||||
Options: &opts,
|
||||
}.Run()
|
||||
|
||||
if st := m.Run(); st > status {
|
||||
status = st
|
||||
|
@ -52,12 +52,12 @@ func thereShouldBeRemaining(remaining int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func FeatureContext(s *godog.Suite) {
|
||||
func ScenarioContext(s *godog.ScenarioContext) {
|
||||
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
||||
s.Step(`^I eat (\d+)$`, iEat)
|
||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||
|
||||
s.BeforeScenario(func(*messages.Pickle) {
|
||||
s.BeforeScenario(func(*godog.Scenario) {
|
||||
Godogs = 0 // clean the state before every scenario
|
||||
})
|
||||
}
|
||||
|
|
6
ast.go
6
ast.go
|
@ -2,7 +2,7 @@ package godog
|
|||
|
||||
import "go/ast"
|
||||
|
||||
func astContexts(f *ast.File) []string {
|
||||
func astContexts(f *ast.File, selectName string) []string {
|
||||
var contexts []string
|
||||
for _, d := range f.Decls {
|
||||
switch fun := d.(type) {
|
||||
|
@ -12,13 +12,13 @@ func astContexts(f *ast.File) []string {
|
|||
case *ast.StarExpr:
|
||||
switch x := expr.X.(type) {
|
||||
case *ast.Ident:
|
||||
if x.Name == "Suite" {
|
||||
if x.Name == selectName {
|
||||
contexts = append(contexts, fun.Name.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
switch t := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
if t.Name == "godog" && x.Sel.Name == "Suite" {
|
||||
if t.Name == "godog" && x.Sel.Name == selectName {
|
||||
contexts = append(contexts, fun.Name.Name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func astContextParse(src string, t *testing.T) []string {
|
|||
t.Fatalf("unexpected error while parsing ast: %v", err)
|
||||
}
|
||||
|
||||
return astContexts(f)
|
||||
return astContexts(f, "Suite")
|
||||
}
|
||||
|
||||
func TestShouldGetSingleContextFromSource(t *testing.T) {
|
||||
|
|
115
builder.go
115
builder.go
|
@ -30,28 +30,51 @@ var (
|
|||
|
||||
import (
|
||||
"github.com/cucumber/godog"
|
||||
{{if .Contexts}}_test "{{.ImportPath}}"{{end}}
|
||||
{{if .XContexts}}_xtest "{{.ImportPath}}_test"{{end}}
|
||||
{{if .XContexts}}"testing/internal/testdeps"{{end}}
|
||||
{{if or .DeprecatedFeatureContexts .TestSuiteContexts .ScenarioContexts}}_test "{{.ImportPath}}"{{end}}
|
||||
{{if or .XDeprecatedFeatureContexts .XTestSuiteContexts .XScenarioContexts}}_xtest "{{.ImportPath}}_test"{{end}}
|
||||
{{if or .XDeprecatedFeatureContexts .XTestSuiteContexts .XScenarioContexts}}"testing/internal/testdeps"{{end}}
|
||||
"os"
|
||||
)
|
||||
|
||||
{{if .XContexts}}
|
||||
{{if or .XDeprecatedFeatureContexts .XTestSuiteContexts .XScenarioContexts}}
|
||||
func init() {
|
||||
testdeps.ImportPath = "{{.ImportPath}}"
|
||||
}
|
||||
{{end}}
|
||||
|
||||
func main() {
|
||||
{{if or .TestSuiteContexts .ScenarioContexts .XTestSuiteContexts .XScenarioContexts}}
|
||||
status := godog.TestSuite{
|
||||
Name: "{{ .Name }}",
|
||||
TestSuiteInitializer: func (ctx *godog.TestSuiteContext) {
|
||||
os.Setenv("GODOG_TESTED_PACKAGE", "{{.ImportPath}}")
|
||||
{{range .TestSuiteContexts}}
|
||||
_test.{{ . }}(ctx)
|
||||
{{end}}
|
||||
{{range .XTestSuiteContexts}}
|
||||
_xtest.{{ . }}(ctx)
|
||||
{{end}}
|
||||
},
|
||||
ScenarioInitializer: func (ctx *godog.ScenarioContext) {
|
||||
{{range .ScenarioContexts}}
|
||||
_test.{{ . }}(ctx)
|
||||
{{end}}
|
||||
{{range .XScenarioContexts}}
|
||||
_xtest.{{ . }}(ctx)
|
||||
{{end}}
|
||||
},
|
||||
}.Run()
|
||||
{{else}}
|
||||
status := godog.Run("{{ .Name }}", func (suite *godog.Suite) {
|
||||
os.Setenv("GODOG_TESTED_PACKAGE", "{{.ImportPath}}")
|
||||
{{range .Contexts}}
|
||||
{{range .DeprecatedFeatureContexts}}
|
||||
_test.{{ . }}(suite)
|
||||
{{end}}
|
||||
{{range .XContexts}}
|
||||
{{range .XDeprecatedFeatureContexts}}
|
||||
_xtest.{{ . }}(suite)
|
||||
{{end}}
|
||||
})
|
||||
{{end}}
|
||||
os.Exit(status)
|
||||
}`))
|
||||
|
||||
|
@ -308,20 +331,21 @@ func buildTempFile(pkg *build.Package) ([]byte, error) {
|
|||
// and produces a testmain source code.
|
||||
func buildTestMain(pkg *build.Package) ([]byte, error) {
|
||||
var (
|
||||
contexts []string
|
||||
xcontexts []string
|
||||
ctxs, xctxs contexts
|
||||
err error
|
||||
name, importPath string
|
||||
name = "main"
|
||||
importPath string
|
||||
)
|
||||
|
||||
if nil != pkg {
|
||||
contexts, err = processPackageTestFiles(pkg.TestGoFiles)
|
||||
if err != nil {
|
||||
if ctxs, err = processPackageTestFiles(pkg.TestGoFiles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
xcontexts, err = processPackageTestFiles(pkg.XTestGoFiles)
|
||||
if err != nil {
|
||||
|
||||
if xctxs, err = processPackageTestFiles(pkg.XTestGoFiles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
importPath = parseImport(pkg.ImportPath, pkg.Root)
|
||||
name = pkg.Name
|
||||
} else {
|
||||
|
@ -329,14 +353,22 @@ func buildTestMain(pkg *build.Package) ([]byte, error) {
|
|||
}
|
||||
data := struct {
|
||||
Name string
|
||||
Contexts []string
|
||||
XContexts []string
|
||||
ImportPath string
|
||||
DeprecatedFeatureContexts []string
|
||||
TestSuiteContexts []string
|
||||
ScenarioContexts []string
|
||||
XDeprecatedFeatureContexts []string
|
||||
XTestSuiteContexts []string
|
||||
XScenarioContexts []string
|
||||
}{
|
||||
Name: name,
|
||||
Contexts: contexts,
|
||||
XContexts: xcontexts,
|
||||
ImportPath: importPath,
|
||||
DeprecatedFeatureContexts: ctxs.deprecatedFeatureCtxs,
|
||||
TestSuiteContexts: ctxs.testSuiteCtxs,
|
||||
ScenarioContexts: ctxs.scenarioCtxs,
|
||||
XDeprecatedFeatureContexts: xctxs.deprecatedFeatureCtxs,
|
||||
XTestSuiteContexts: xctxs.testSuiteCtxs,
|
||||
XScenarioContexts: xctxs.scenarioCtxs,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
@ -380,11 +412,38 @@ func parseImport(rawPath, rootPath string) string {
|
|||
return mod.Path + filepath.ToSlash(strings.TrimPrefix(rawPath, normaliseLocalImportPath(mod.Dir)))
|
||||
}
|
||||
|
||||
type contexts struct {
|
||||
deprecatedFeatureCtxs []string
|
||||
testSuiteCtxs []string
|
||||
scenarioCtxs []string
|
||||
}
|
||||
|
||||
func (ctxs contexts) validate() error {
|
||||
var allCtxs []string
|
||||
allCtxs = append(allCtxs, ctxs.deprecatedFeatureCtxs...)
|
||||
allCtxs = append(allCtxs, ctxs.testSuiteCtxs...)
|
||||
allCtxs = append(allCtxs, ctxs.scenarioCtxs...)
|
||||
|
||||
var failed []string
|
||||
for _, ctx := range allCtxs {
|
||||
runes := []rune(ctx)
|
||||
if unicode.IsLower(runes[0]) {
|
||||
expected := append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...)
|
||||
failed = append(failed, fmt.Sprintf("%s - should be: %s", ctx, string(expected)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(failed) > 0 {
|
||||
return fmt.Errorf("godog contexts must be exported:\n\t%s", strings.Join(failed, "\n\t"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processPackageTestFiles runs through ast of each test
|
||||
// file pack and looks for godog suite contexts to register
|
||||
// on run
|
||||
func processPackageTestFiles(packs ...[]string) ([]string, error) {
|
||||
var ctxs []string
|
||||
func processPackageTestFiles(packs ...[]string) (ctxs contexts, _ error) {
|
||||
fset := token.NewFileSet()
|
||||
for _, pack := range packs {
|
||||
for _, testFile := range pack {
|
||||
|
@ -393,21 +452,13 @@ func processPackageTestFiles(packs ...[]string) ([]string, error) {
|
|||
return ctxs, err
|
||||
}
|
||||
|
||||
ctxs = append(ctxs, astContexts(node)...)
|
||||
ctxs.deprecatedFeatureCtxs = append(ctxs.deprecatedFeatureCtxs, astContexts(node, "Suite")...)
|
||||
ctxs.testSuiteCtxs = append(ctxs.testSuiteCtxs, astContexts(node, "TestSuiteContext")...)
|
||||
ctxs.scenarioCtxs = append(ctxs.scenarioCtxs, astContexts(node, "ScenarioContext")...)
|
||||
}
|
||||
}
|
||||
var failed []string
|
||||
for _, ctx := range ctxs {
|
||||
runes := []rune(ctx)
|
||||
if unicode.IsLower(runes[0]) {
|
||||
expected := append([]rune{unicode.ToUpper(runes[0])}, runes[1:]...)
|
||||
failed = append(failed, fmt.Sprintf("%s - should be: %s", ctx, string(expected)))
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
return ctxs, fmt.Errorf("godog contexts must be exported:\n\t%s", strings.Join(failed, "\n\t"))
|
||||
}
|
||||
return ctxs, nil
|
||||
|
||||
return ctxs, ctxs.validate()
|
||||
}
|
||||
|
||||
func findToolDir() string {
|
||||
|
|
123
run.go
123
run.go
|
@ -20,6 +20,8 @@ const (
|
|||
)
|
||||
|
||||
type initializer func(*Suite)
|
||||
type testSuiteInitializer func(*TestSuiteContext)
|
||||
type scenarioInitializer func(*ScenarioContext)
|
||||
|
||||
type runner struct {
|
||||
randomSeed int64
|
||||
|
@ -27,6 +29,8 @@ type runner struct {
|
|||
features []*feature
|
||||
fmt Formatter
|
||||
initializer initializer
|
||||
testSuiteInitializer testSuiteInitializer
|
||||
scenarioInitializer scenarioInitializer
|
||||
}
|
||||
|
||||
func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool) {
|
||||
|
@ -38,8 +42,17 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool
|
|||
useFmtCopy = true
|
||||
}
|
||||
|
||||
testSuiteContext := TestSuiteContext{}
|
||||
if r.testSuiteInitializer != nil {
|
||||
r.testSuiteInitializer(&testSuiteContext)
|
||||
}
|
||||
r.fmt.TestRunStarted()
|
||||
|
||||
// run before suite handlers
|
||||
for _, f := range testSuiteContext.beforeSuiteHandlers {
|
||||
f()
|
||||
}
|
||||
|
||||
queue := make(chan int, rate)
|
||||
for i, ft := range r.features {
|
||||
queue <- i // reserve space in queue
|
||||
|
@ -74,7 +87,14 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool
|
|||
suite.fmt = r.fmt
|
||||
}
|
||||
|
||||
if r.initializer != nil {
|
||||
r.initializer(suite)
|
||||
}
|
||||
|
||||
if r.scenarioInitializer != nil {
|
||||
suite.scenarioInitializer = r.scenarioInitializer
|
||||
}
|
||||
|
||||
suite.run()
|
||||
if suite.failed {
|
||||
copyLock.Lock()
|
||||
|
@ -105,6 +125,11 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool
|
|||
}
|
||||
close(queue)
|
||||
|
||||
// run after suite handlers
|
||||
for _, f := range testSuiteContext.afterSuiteHandlers {
|
||||
f()
|
||||
}
|
||||
|
||||
// print summary
|
||||
r.fmt.Summary()
|
||||
return
|
||||
|
@ -126,7 +151,22 @@ func (r *runner) concurrent(rate int, formatterFn func() Formatter) (failed bool
|
|||
//
|
||||
// If there are flag related errors they will
|
||||
// be directed to os.Stderr
|
||||
func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Options) int {
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios.
|
||||
// Use:
|
||||
// godog.TestSuite{
|
||||
// Name: name,
|
||||
// TestSuiteInitializer: testSuiteInitializer,
|
||||
// ScenarioInitializer: scenarioInitializer,
|
||||
// Options: &opts,
|
||||
// }.Run()
|
||||
// instead.
|
||||
func RunWithOptions(suite string, initializer func(*Suite), opt Options) int {
|
||||
return runWithOptions(suite, runner{initializer: initializer}, opt)
|
||||
}
|
||||
|
||||
func runWithOptions(suite string, runner runner, opt Options) int {
|
||||
var output io.Writer = os.Stdout
|
||||
if nil != opt.Output {
|
||||
output = opt.Output
|
||||
|
@ -140,7 +180,7 @@ func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Opt
|
|||
|
||||
if opt.ShowStepDefinitions {
|
||||
s := &Suite{}
|
||||
contextInitializer(s)
|
||||
runner.initializer(s)
|
||||
s.printStepDefinitions(output)
|
||||
return exitOptionError
|
||||
}
|
||||
|
@ -169,35 +209,30 @@ func RunWithOptions(suite string, contextInitializer func(suite *Suite), opt Opt
|
|||
))
|
||||
return exitOptionError
|
||||
}
|
||||
runner.fmt = formatter(suite, output)
|
||||
|
||||
features, err := parseFeatures(opt.Tags, opt.Paths)
|
||||
if err != nil {
|
||||
var err error
|
||||
if runner.features, err = parseFeatures(opt.Tags, opt.Paths); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return exitOptionError
|
||||
}
|
||||
|
||||
// user may have specified -1 option to create random seed
|
||||
randomize := opt.Randomize
|
||||
if randomize == -1 {
|
||||
randomize = makeRandomSeed()
|
||||
runner.randomSeed = opt.Randomize
|
||||
if runner.randomSeed == -1 {
|
||||
runner.randomSeed = makeRandomSeed()
|
||||
}
|
||||
|
||||
r := runner{
|
||||
fmt: formatter(suite, output),
|
||||
initializer: contextInitializer,
|
||||
features: features,
|
||||
randomSeed: randomize,
|
||||
stopOnFailure: opt.StopOnFailure,
|
||||
strict: opt.Strict,
|
||||
}
|
||||
runner.stopOnFailure = opt.StopOnFailure
|
||||
runner.strict = opt.Strict
|
||||
|
||||
// store chosen seed in environment, so it could be seen in formatter summary report
|
||||
os.Setenv("GODOG_SEED", strconv.FormatInt(r.randomSeed, 10))
|
||||
os.Setenv("GODOG_SEED", strconv.FormatInt(runner.randomSeed, 10))
|
||||
// determine tested package
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
os.Setenv("GODOG_TESTED_PACKAGE", runsFromPackage(filename))
|
||||
|
||||
failed := r.concurrent(opt.Concurrency, func() Formatter { return formatter(suite, output) })
|
||||
failed := runner.concurrent(opt.Concurrency, func() Formatter { return formatter(suite, output) })
|
||||
|
||||
// @TODO: should prevent from having these
|
||||
os.Setenv("GODOG_SEED", "")
|
||||
|
@ -240,9 +275,20 @@ func runsFromPackage(fp string) string {
|
|||
//
|
||||
// If there are flag related errors they will
|
||||
// be directed to os.Stderr
|
||||
func Run(suite string, contextInitializer func(suite *Suite)) int {
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios.
|
||||
// Use:
|
||||
// godog.TestSuite{
|
||||
// Name: name,
|
||||
// TestSuiteInitializer: testSuiteInitializer,
|
||||
// ScenarioInitializer: scenarioInitializer,
|
||||
// }.Run()
|
||||
// instead.
|
||||
func Run(suite string, initializer func(*Suite)) int {
|
||||
var opt Options
|
||||
opt.Output = colors.Colored(os.Stdout)
|
||||
|
||||
flagSet := FlagSet(&opt)
|
||||
if err := flagSet.Parse(os.Args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
|
@ -251,5 +297,44 @@ func Run(suite string, contextInitializer func(suite *Suite)) int {
|
|||
|
||||
opt.Paths = flagSet.Args()
|
||||
|
||||
return RunWithOptions(suite, contextInitializer, opt)
|
||||
return RunWithOptions(suite, initializer, opt)
|
||||
}
|
||||
|
||||
// TestSuite allows for configuration
|
||||
// of the Test Suite Execution
|
||||
type TestSuite struct {
|
||||
Name string
|
||||
TestSuiteInitializer func(*TestSuiteContext)
|
||||
ScenarioInitializer func(*ScenarioContext)
|
||||
Options *Options
|
||||
}
|
||||
|
||||
// Run will execute the test suite.
|
||||
//
|
||||
// If options are not set, it will reads
|
||||
// all configuration options from flags.
|
||||
//
|
||||
// The exit codes may vary from:
|
||||
// 0 - success
|
||||
// 1 - failed
|
||||
// 2 - command line usage error
|
||||
// 128 - or higher, os signal related error exit codes
|
||||
//
|
||||
// If there are flag related errors they will be directed to os.Stderr
|
||||
func (ts TestSuite) Run() int {
|
||||
if ts.Options == nil {
|
||||
ts.Options = &Options{}
|
||||
ts.Options.Output = colors.Colored(os.Stdout)
|
||||
|
||||
flagSet := FlagSet(ts.Options)
|
||||
if err := flagSet.Parse(os.Args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return exitOptionError
|
||||
}
|
||||
|
||||
ts.Options.Paths = flagSet.Args()
|
||||
}
|
||||
|
||||
r := runner{testSuiteInitializer: ts.TestSuiteInitializer, scenarioInitializer: ts.ScenarioInitializer}
|
||||
return runWithOptions(ts.Name, r, *ts.Options)
|
||||
}
|
||||
|
|
39
suite.go
39
suite.go
|
@ -165,6 +165,10 @@ var ErrPending = fmt.Errorf("step implementation is pending")
|
|||
// in order to have a trace information. Only step
|
||||
// executions are catching panic error since it may
|
||||
// be a context specific error.
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios.
|
||||
// This struct will therefore not be exported in the future.
|
||||
type Suite struct {
|
||||
steps []*StepDefinition
|
||||
features []*feature
|
||||
|
@ -175,6 +179,8 @@ type Suite struct {
|
|||
stopOnFailure bool
|
||||
strict bool
|
||||
|
||||
scenarioInitializer scenarioInitializer
|
||||
|
||||
// suite event handlers
|
||||
beforeSuiteHandlers []func()
|
||||
beforeFeatureHandlers []func(*messages.GherkinDocument)
|
||||
|
@ -201,6 +207,10 @@ type Suite struct {
|
|||
// If none of the *StepDefinition is matched, then
|
||||
// ErrUndefined error will be returned when
|
||||
// running steps.
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *ScenarioContext) Step instead.
|
||||
func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||
var regex *regexp.Regexp
|
||||
|
||||
|
@ -254,6 +264,10 @@ func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
|||
//
|
||||
// Use it to prepare the test suite for a spin.
|
||||
// Connect and prepare database for instance...
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *TestSuiteContext) BeforeSuite instead.
|
||||
func (s *Suite) BeforeSuite(fn func()) {
|
||||
s.beforeSuiteHandlers = append(s.beforeSuiteHandlers, fn)
|
||||
}
|
||||
|
@ -285,12 +299,20 @@ func (s *Suite) BeforeFeature(fn func(*messages.GherkinDocument)) {
|
|||
// It is a good practice to restore the default state
|
||||
// before every scenario so it would be isolated from
|
||||
// any kind of state.
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *ScenarioContext) BeforeScenario instead.
|
||||
func (s *Suite) BeforeScenario(fn func(*messages.Pickle)) {
|
||||
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn)
|
||||
}
|
||||
|
||||
// BeforeStep registers a function or method
|
||||
// to be run before every step.
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *ScenarioContext) BeforeStep instead.
|
||||
func (s *Suite) BeforeStep(fn func(*messages.Pickle_PickleStep)) {
|
||||
s.beforeStepHandlers = append(s.beforeStepHandlers, fn)
|
||||
}
|
||||
|
@ -304,12 +326,20 @@ func (s *Suite) BeforeStep(fn func(*messages.Pickle_PickleStep)) {
|
|||
//
|
||||
// In some cases, for example when running a headless
|
||||
// browser, to take a screenshot after failure.
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *ScenarioContext) AfterStep instead.
|
||||
func (s *Suite) AfterStep(fn func(*messages.Pickle_PickleStep, error)) {
|
||||
s.afterStepHandlers = append(s.afterStepHandlers, fn)
|
||||
}
|
||||
|
||||
// AfterScenario registers an function or method
|
||||
// to be run after every pickle.
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *ScenarioContext) AfterScenario instead.
|
||||
func (s *Suite) AfterScenario(fn func(*messages.Pickle, error)) {
|
||||
s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn)
|
||||
}
|
||||
|
@ -326,6 +356,10 @@ func (s *Suite) AfterFeature(fn func(*messages.GherkinDocument)) {
|
|||
|
||||
// AfterSuite registers a function or method
|
||||
// to be run once after suite runner
|
||||
//
|
||||
// Deprecated: The current Suite initializer will be removed and replaced by
|
||||
// two initializers, one for the Test Suite and one for the Scenarios. Use
|
||||
// func (ctx *TestSuiteContext) AfterSuite instead.
|
||||
func (s *Suite) AfterSuite(fn func()) {
|
||||
s.afterSuiteHandlers = append(s.afterSuiteHandlers, fn)
|
||||
}
|
||||
|
@ -552,6 +586,11 @@ func (s *Suite) runFeature(f *feature) {
|
|||
}()
|
||||
|
||||
for _, pickle := range f.pickles {
|
||||
if s.scenarioInitializer != nil {
|
||||
sc := ScenarioContext{suite: s}
|
||||
s.scenarioInitializer(&sc)
|
||||
}
|
||||
|
||||
err := s.runPickle(pickle)
|
||||
if s.shouldFail(err) {
|
||||
s.failed = true
|
||||
|
|
125
test_context.go
Обычный файл
125
test_context.go
Обычный файл
|
@ -0,0 +1,125 @@
|
|||
package godog
|
||||
|
||||
import "github.com/cucumber/messages-go/v10"
|
||||
|
||||
// Scenario represents the executed scenario
|
||||
type Scenario = messages.Pickle
|
||||
|
||||
// Step represents the executed step
|
||||
type Step = messages.Pickle_PickleStep
|
||||
|
||||
// DocString represents the DocString argument made to a step definition
|
||||
type DocString = messages.PickleStepArgument_PickleDocString
|
||||
|
||||
// Table represents the Table argument made to a step definition
|
||||
type Table = messages.PickleStepArgument_PickleTable
|
||||
|
||||
// TestSuiteContext allows various contexts
|
||||
// to register event handlers.
|
||||
//
|
||||
// When running a test suite, the instance of TestSuiteContext
|
||||
// is passed to all functions (contexts), which
|
||||
// have it as a first and only argument.
|
||||
//
|
||||
// Note that all event hooks does not catch panic errors
|
||||
// in order to have a trace information
|
||||
type TestSuiteContext struct {
|
||||
beforeSuiteHandlers []func()
|
||||
afterSuiteHandlers []func()
|
||||
}
|
||||
|
||||
// BeforeSuite registers a function or method
|
||||
// to be run once before suite runner.
|
||||
//
|
||||
// Use it to prepare the test suite for a spin.
|
||||
// Connect and prepare database for instance...
|
||||
func (ctx *TestSuiteContext) BeforeSuite(fn func()) {
|
||||
ctx.beforeSuiteHandlers = append(ctx.beforeSuiteHandlers, fn)
|
||||
}
|
||||
|
||||
// AfterSuite registers a function or method
|
||||
// to be run once after suite runner
|
||||
func (ctx *TestSuiteContext) AfterSuite(fn func()) {
|
||||
ctx.afterSuiteHandlers = append(ctx.afterSuiteHandlers, fn)
|
||||
}
|
||||
|
||||
// ScenarioContext allows various contexts
|
||||
// to register steps and event handlers.
|
||||
//
|
||||
// When running a scenario, the instance of ScenarioContext
|
||||
// is passed to all functions (contexts), which
|
||||
// have it as a first and only argument.
|
||||
//
|
||||
// Note that all event hooks does not catch panic errors
|
||||
// in order to have a trace information. Only step
|
||||
// executions are catching panic error since it may
|
||||
// be a context specific error.
|
||||
type ScenarioContext struct {
|
||||
suite *Suite
|
||||
}
|
||||
|
||||
// BeforeScenario registers a function or method
|
||||
// to be run before every scenario.
|
||||
//
|
||||
// It is a good practice to restore the default state
|
||||
// before every scenario so it would be isolated from
|
||||
// any kind of state.
|
||||
func (ctx *ScenarioContext) BeforeScenario(fn func(sc *Scenario)) {
|
||||
ctx.suite.BeforeScenario(fn)
|
||||
}
|
||||
|
||||
// AfterScenario registers an function or method
|
||||
// to be run after every scenario.
|
||||
func (ctx *ScenarioContext) AfterScenario(fn func(sc *Scenario, err error)) {
|
||||
ctx.suite.AfterScenario(fn)
|
||||
}
|
||||
|
||||
// BeforeStep registers a function or method
|
||||
// to be run before every step.
|
||||
func (ctx *ScenarioContext) BeforeStep(fn func(st *Step)) {
|
||||
ctx.suite.BeforeStep(fn)
|
||||
}
|
||||
|
||||
// AfterStep registers an function or method
|
||||
// to be run after every step.
|
||||
//
|
||||
// It may be convenient to return a different kind of error
|
||||
// in order to print more state details which may help
|
||||
// in case of step failure
|
||||
//
|
||||
// In some cases, for example when running a headless
|
||||
// browser, to take a screenshot after failure.
|
||||
func (ctx *ScenarioContext) AfterStep(fn func(st *Step, err error)) {
|
||||
ctx.suite.AfterStep(fn)
|
||||
}
|
||||
|
||||
// Step allows to register a *StepDefinition in the
|
||||
// Godog feature suite, the definition will be applied
|
||||
// to all steps matching the given Regexp expr.
|
||||
//
|
||||
// It will panic if expr is not a valid regular
|
||||
// expression or stepFunc is not a valid step
|
||||
// handler.
|
||||
//
|
||||
// The expression can be of type: *regexp.Regexp, string or []byte
|
||||
//
|
||||
// The stepFunc may accept one or several arguments of type:
|
||||
// - int, int8, int16, int32, int64
|
||||
// - float32, float64
|
||||
// - string
|
||||
// - []byte
|
||||
// - *godog.DocString
|
||||
// - *godog.Table
|
||||
//
|
||||
// The stepFunc need to return either an error or []string for multistep
|
||||
//
|
||||
// Note that if there are two definitions which may match
|
||||
// the same step, then only the first matched handler
|
||||
// will be applied.
|
||||
//
|
||||
// If none of the *StepDefinition is matched, then
|
||||
// ErrUndefined error will be returned when
|
||||
// running steps.
|
||||
func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) {
|
||||
ctx.suite.Step(expr, stepFunc)
|
||||
}
|
49
test_context_test.go
Обычный файл
49
test_context_test.go
Обычный файл
|
@ -0,0 +1,49 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cucumber/gherkin-go/v11"
|
||||
"github.com/cucumber/godog/colors"
|
||||
"github.com/cucumber/messages-go/v10"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_TestContext(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
|
||||
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||
require.NoError(t, err)
|
||||
|
||||
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
testSuiteInitializer: nil,
|
||||
scenarioInitializer: func(sc *ScenarioContext) {
|
||||
sc.Step(`^one$`, func() error { return nil })
|
||||
sc.Step(`^two$`, func() error { return nil })
|
||||
},
|
||||
}
|
||||
|
||||
failed := r.concurrent(1, func() Formatter { return progressFunc("progress", w) })
|
||||
require.False(t, failed)
|
||||
|
||||
expected := `.. 2
|
||||
|
||||
|
||||
1 scenarios (1 passed)
|
||||
2 steps (2 passed)
|
||||
0s
|
||||
`
|
||||
|
||||
actual := buf.String()
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче