adds scenarios to test nested steps
Этот коммит содержится в:
родитель
ba80ebcd33
коммит
703b40de76
7 изменённых файлов: 236 добавлений и 52 удалений
|
@ -8,7 +8,7 @@ Savybė: užkrauti savybes
|
||||||
Scenarijus: savybių užkrovimas iš aplanko
|
Scenarijus: savybių užkrovimas iš aplanko
|
||||||
Duota savybių aplankas "features"
|
Duota savybių aplankas "features"
|
||||||
Kai aš išskaitau savybes
|
Kai aš išskaitau savybes
|
||||||
Tada aš turėčiau turėti 9 savybių failus:
|
Tada aš turėčiau turėti 10 savybių failus:
|
||||||
"""
|
"""
|
||||||
features/background.feature
|
features/background.feature
|
||||||
features/events.feature
|
features/events.feature
|
||||||
|
@ -16,6 +16,7 @@ Savybė: užkrauti savybes
|
||||||
features/formatter/events.feature
|
features/formatter/events.feature
|
||||||
features/lang.feature
|
features/lang.feature
|
||||||
features/load.feature
|
features/load.feature
|
||||||
|
features/multistep.feature
|
||||||
features/outline.feature
|
features/outline.feature
|
||||||
features/run.feature
|
features/run.feature
|
||||||
features/snippets.feature
|
features/snippets.feature
|
||||||
|
|
|
@ -6,7 +6,7 @@ Feature: load features
|
||||||
Scenario: load features within path
|
Scenario: load features within path
|
||||||
Given a feature path "features"
|
Given a feature path "features"
|
||||||
When I parse features
|
When I parse features
|
||||||
Then I should have 9 feature files:
|
Then I should have 10 feature files:
|
||||||
"""
|
"""
|
||||||
features/background.feature
|
features/background.feature
|
||||||
features/events.feature
|
features/events.feature
|
||||||
|
@ -14,6 +14,7 @@ Feature: load features
|
||||||
features/formatter/events.feature
|
features/formatter/events.feature
|
||||||
features/lang.feature
|
features/lang.feature
|
||||||
features/load.feature
|
features/load.feature
|
||||||
|
features/multistep.feature
|
||||||
features/outline.feature
|
features/outline.feature
|
||||||
features/run.feature
|
features/run.feature
|
||||||
features/snippets.feature
|
features/snippets.feature
|
||||||
|
|
140
features/multistep.feature
Обычный файл
140
features/multistep.feature
Обычный файл
|
@ -0,0 +1,140 @@
|
||||||
|
Feature: run features with nested steps
|
||||||
|
In order to test multisteps
|
||||||
|
As a test suite
|
||||||
|
I need to be able to execute multisteps
|
||||||
|
|
||||||
|
Scenario: should run passing multistep successfully
|
||||||
|
Given a feature "normal.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: normal feature
|
||||||
|
|
||||||
|
Scenario: run passing multistep
|
||||||
|
Given passing step
|
||||||
|
Then passing multistep
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have passed
|
||||||
|
And the following steps should be passed:
|
||||||
|
"""
|
||||||
|
passing step
|
||||||
|
passing multistep
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: should fail multistep
|
||||||
|
Given a feature "failed.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: failed feature
|
||||||
|
|
||||||
|
Scenario: run failing multistep
|
||||||
|
Given passing step
|
||||||
|
When failing multistep
|
||||||
|
Then I should have 1 scenario registered
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have failed
|
||||||
|
And the following step should be failed:
|
||||||
|
"""
|
||||||
|
failing multistep
|
||||||
|
"""
|
||||||
|
And the following steps should be skipped:
|
||||||
|
"""
|
||||||
|
I should have 1 scenario registered
|
||||||
|
"""
|
||||||
|
And the following steps should be passed:
|
||||||
|
"""
|
||||||
|
passing step
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: should fail nested multistep
|
||||||
|
Given a feature "failed.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: failed feature
|
||||||
|
|
||||||
|
Scenario: run failing nested multistep
|
||||||
|
Given failing nested multistep
|
||||||
|
When passing step
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have failed
|
||||||
|
And the following step should be failed:
|
||||||
|
"""
|
||||||
|
failing nested multistep
|
||||||
|
"""
|
||||||
|
And the following steps should be skipped:
|
||||||
|
"""
|
||||||
|
passing step
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: should skip steps after undefined multistep
|
||||||
|
Given a feature "undefined.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: run undefined multistep
|
||||||
|
|
||||||
|
Scenario: run undefined multistep
|
||||||
|
Given passing step
|
||||||
|
When undefined multistep
|
||||||
|
Then passing multistep
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have passed
|
||||||
|
And the following step should be passed:
|
||||||
|
"""
|
||||||
|
passing step
|
||||||
|
"""
|
||||||
|
And the following step should be undefined:
|
||||||
|
"""
|
||||||
|
undefined multistep
|
||||||
|
"""
|
||||||
|
And the following step should be skipped:
|
||||||
|
"""
|
||||||
|
passing multistep
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: should match undefined steps in a row
|
||||||
|
Given a feature "undefined.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: undefined feature
|
||||||
|
|
||||||
|
Scenario: parse a scenario
|
||||||
|
Given undefined step
|
||||||
|
When undefined multistep
|
||||||
|
Then I should have 1 scenario registered
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have passed
|
||||||
|
And the following steps should be undefined:
|
||||||
|
"""
|
||||||
|
undefined step
|
||||||
|
undefined multistep
|
||||||
|
"""
|
||||||
|
And the following step should be skipped:
|
||||||
|
"""
|
||||||
|
I should have 1 scenario registered
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: should mark undefined steps after pending
|
||||||
|
Given a feature "pending.feature" file:
|
||||||
|
"""
|
||||||
|
Feature: pending feature
|
||||||
|
|
||||||
|
Scenario: parse a scenario
|
||||||
|
Given pending step
|
||||||
|
When undefined step
|
||||||
|
Then undefined multistep
|
||||||
|
And I should have 1 scenario registered
|
||||||
|
"""
|
||||||
|
When I run feature suite
|
||||||
|
Then the suite should have passed
|
||||||
|
And the following steps should be undefined:
|
||||||
|
"""
|
||||||
|
undefined step
|
||||||
|
undefined multistep
|
||||||
|
"""
|
||||||
|
And the following step should be pending:
|
||||||
|
"""
|
||||||
|
pending step
|
||||||
|
"""
|
||||||
|
And the following step should be skipped:
|
||||||
|
"""
|
||||||
|
I should have 1 scenario registered
|
||||||
|
"""
|
6
run.go
6
run.go
|
@ -160,13 +160,11 @@ func Run(suite string, contextInitializer func(suite *Suite)) int {
|
||||||
func supportsConcurrency(format string) bool {
|
func supportsConcurrency(format string) bool {
|
||||||
switch format {
|
switch format {
|
||||||
case "events":
|
case "events":
|
||||||
return false
|
|
||||||
case "junit":
|
case "junit":
|
||||||
return false
|
|
||||||
case "pretty":
|
case "pretty":
|
||||||
return false
|
|
||||||
case "cucumber":
|
case "cucumber":
|
||||||
return false
|
default:
|
||||||
|
return true // supports concurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
return true // all custom formatters are treated as supporting concurrency
|
return true // all custom formatters are treated as supporting concurrency
|
||||||
|
|
17
stepdef.go
17
stepdef.go
|
@ -14,6 +14,22 @@ import (
|
||||||
|
|
||||||
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
||||||
|
|
||||||
|
// Steps allows to nest steps
|
||||||
|
// instead of returning an error in step func
|
||||||
|
// it is possible to return combined steps:
|
||||||
|
//
|
||||||
|
// func multistep(name string) godog.Steps {
|
||||||
|
// return godog.Steps{
|
||||||
|
// fmt.Sprintf(`an user named "%s"`, name),
|
||||||
|
// fmt.Sprintf(`user "%s" is authenticated`, name),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// These steps will be matched and executed in
|
||||||
|
// sequential order. The first one which fails
|
||||||
|
// will result in main step failure.
|
||||||
|
type Steps []string
|
||||||
|
|
||||||
// StepDef is a registered step definition
|
// StepDef is a registered step definition
|
||||||
// contains a StepHandler and regexp which
|
// contains a StepHandler and regexp which
|
||||||
// is used to match a step. Args which
|
// is used to match a step. Args which
|
||||||
|
@ -27,6 +43,7 @@ type StepDef struct {
|
||||||
hv reflect.Value
|
hv reflect.Value
|
||||||
Expr *regexp.Regexp
|
Expr *regexp.Regexp
|
||||||
Handler interface{}
|
Handler interface{}
|
||||||
|
nested bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *StepDef) definitionID() string {
|
func (sd *StepDef) definitionID() string {
|
||||||
|
|
104
suite.go
104
suite.go
|
@ -100,6 +100,12 @@ func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||||
panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut()))
|
panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def := &StepDef{
|
||||||
|
Handler: stepFunc,
|
||||||
|
Expr: regex,
|
||||||
|
hv: v,
|
||||||
|
}
|
||||||
|
|
||||||
typ = typ.Out(0)
|
typ = typ.Out(0)
|
||||||
switch typ.Kind() {
|
switch typ.Kind() {
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
|
@ -107,6 +113,7 @@ func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||||
panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind()))
|
panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind()))
|
||||||
}
|
}
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
|
def.nested = true
|
||||||
if typ.Elem().Kind() != reflect.String {
|
if typ.Elem().Kind() != reflect.String {
|
||||||
panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind()))
|
panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind()))
|
||||||
}
|
}
|
||||||
|
@ -114,11 +121,7 @@ func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||||
panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind()))
|
panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
s.steps = append(s.steps, &StepDef{
|
s.steps = append(s.steps, def)
|
||||||
Handler: stepFunc,
|
|
||||||
Expr: regex,
|
|
||||||
hv: v,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeSuite registers a function or method
|
// BeforeSuite registers a function or method
|
||||||
|
@ -197,27 +200,20 @@ func (s *Suite) run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
||||||
for _, h := range s.steps {
|
def := s.matchStepText(step.Text)
|
||||||
if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 {
|
if def != nil && step.Argument != nil {
|
||||||
var args []interface{}
|
def.args = append(def.args, step.Argument)
|
||||||
for _, m := range m[1:] {
|
|
||||||
args = append(args, m)
|
|
||||||
}
|
|
||||||
if step.Argument != nil {
|
|
||||||
args = append(args, step.Argument)
|
|
||||||
}
|
|
||||||
h.args = args
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// @TODO can handle ambiguous
|
return def
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
match := s.matchStep(step)
|
match := s.matchStep(step)
|
||||||
s.fmt.Defined(step, match)
|
s.fmt.Defined(step, match)
|
||||||
if match == nil {
|
|
||||||
|
// @TODO custom undefined err here to pass step text for snippet
|
||||||
|
// @TODO user multistep definitions may panic
|
||||||
|
if s.maybeUndefined(match) {
|
||||||
s.fmt.Undefined(step)
|
s.fmt.Undefined(step)
|
||||||
return ErrUndefined
|
return ErrUndefined
|
||||||
}
|
}
|
||||||
|
@ -227,11 +223,6 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// run before step handlers
|
|
||||||
for _, f := range s.beforeStepHandlers {
|
|
||||||
f(step)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
err = &traceError{
|
err = &traceError{
|
||||||
|
@ -254,10 +245,32 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// run before step handlers
|
||||||
|
for _, f := range s.beforeStepHandlers {
|
||||||
|
f(step)
|
||||||
|
}
|
||||||
|
|
||||||
err = s.maybeSubSteps(match.run())
|
err = s.maybeSubSteps(match.run())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Suite) maybeUndefined(step *StepDef) bool {
|
||||||
|
if nil == step {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !step.nested {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, text := range step.run().(Steps) {
|
||||||
|
if s.maybeUndefined(s.matchStepText(text)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Suite) maybeSubSteps(result interface{}) error {
|
func (s *Suite) maybeSubSteps(result interface{}) error {
|
||||||
if nil == result {
|
if nil == result {
|
||||||
return nil
|
return nil
|
||||||
|
@ -267,38 +280,37 @@ func (s *Suite) maybeSubSteps(result interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
steps, ok := result.([]string)
|
steps, ok := result.(Steps)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unexpected error, should have been []string: %T - %+v", result, result)
|
return fmt.Errorf("unexpected error, should have been []string: %T - %+v", result, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, step := range steps {
|
for _, text := range steps {
|
||||||
var def *StepDef
|
if def := s.matchStepText(text); def == nil {
|
||||||
for _, h := range s.steps {
|
|
||||||
if m := h.Expr.FindStringSubmatch(step); len(m) > 0 {
|
|
||||||
var args []interface{}
|
|
||||||
for _, m := range m[1:] {
|
|
||||||
args = append(args, m)
|
|
||||||
}
|
|
||||||
h.args = args
|
|
||||||
def = h
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if def == nil {
|
|
||||||
return ErrUndefined
|
return ErrUndefined
|
||||||
}
|
} else if err := s.maybeSubSteps(def.run()); err != nil {
|
||||||
|
|
||||||
// @TODO: step hooks only take gherkin.Step
|
|
||||||
// @TODO: cannot call formatter to register step execution either
|
|
||||||
if err := s.maybeSubSteps(def.run()); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Suite) matchStepText(text string) *StepDef {
|
||||||
|
for _, h := range s.steps {
|
||||||
|
if m := h.Expr.FindStringSubmatch(text); len(m) > 0 {
|
||||||
|
var args []interface{}
|
||||||
|
for _, m := range m[1:] {
|
||||||
|
args = append(args, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO copy step def
|
||||||
|
h.args = args
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Suite) runSteps(steps []*gherkin.Step, prevErr error) (err error) {
|
func (s *Suite) runSteps(steps []*gherkin.Step, prevErr error) (err error) {
|
||||||
err = prevErr
|
err = prevErr
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
|
|
|
@ -89,6 +89,21 @@ func SuiteContext(s *Suite) {
|
||||||
// Introduced to test formatter/cucumber.feature
|
// Introduced to test formatter/cucumber.feature
|
||||||
s.Step(`^the rendered json will be as follows:$`, c.theRenderJSONWillBe)
|
s.Step(`^the rendered json will be as follows:$`, c.theRenderJSONWillBe)
|
||||||
|
|
||||||
|
s.Step(`^failing multistep$`, func() Steps {
|
||||||
|
return Steps{"passing step", "failing step"}
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Step(`^undefined multistep$`, func() Steps {
|
||||||
|
return Steps{"passing step", "undefined step", "passing step"}
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Step(`^passing multistep$`, func() Steps {
|
||||||
|
return Steps{"passing step", "passing step", "passing step"}
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Step(`^failing nested multistep$`, func() Steps {
|
||||||
|
return Steps{"passing step", "passing multistep", "failing multistep"}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type firedEvent struct {
|
type firedEvent struct {
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче