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
|
||||
Duota savybių aplankas "features"
|
||||
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/events.feature
|
||||
|
@ -16,6 +16,7 @@ Savybė: užkrauti savybes
|
|||
features/formatter/events.feature
|
||||
features/lang.feature
|
||||
features/load.feature
|
||||
features/multistep.feature
|
||||
features/outline.feature
|
||||
features/run.feature
|
||||
features/snippets.feature
|
||||
|
|
|
@ -6,7 +6,7 @@ Feature: load features
|
|||
Scenario: load features within path
|
||||
Given a feature path "features"
|
||||
When I parse features
|
||||
Then I should have 9 feature files:
|
||||
Then I should have 10 feature files:
|
||||
"""
|
||||
features/background.feature
|
||||
features/events.feature
|
||||
|
@ -14,6 +14,7 @@ Feature: load features
|
|||
features/formatter/events.feature
|
||||
features/lang.feature
|
||||
features/load.feature
|
||||
features/multistep.feature
|
||||
features/outline.feature
|
||||
features/run.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 {
|
||||
switch format {
|
||||
case "events":
|
||||
return false
|
||||
case "junit":
|
||||
return false
|
||||
case "pretty":
|
||||
return false
|
||||
case "cucumber":
|
||||
return false
|
||||
default:
|
||||
return true // supports 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(`\(([^\)]+)\)`)
|
||||
|
||||
// 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
|
||||
// contains a StepHandler and regexp which
|
||||
// is used to match a step. Args which
|
||||
|
@ -27,6 +43,7 @@ type StepDef struct {
|
|||
hv reflect.Value
|
||||
Expr *regexp.Regexp
|
||||
Handler interface{}
|
||||
nested bool
|
||||
}
|
||||
|
||||
func (sd *StepDef) definitionID() string {
|
||||
|
|
96
suite.go
96
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()))
|
||||
}
|
||||
|
||||
def := &StepDef{
|
||||
Handler: stepFunc,
|
||||
Expr: regex,
|
||||
hv: v,
|
||||
}
|
||||
|
||||
typ = typ.Out(0)
|
||||
switch typ.Kind() {
|
||||
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()))
|
||||
}
|
||||
case reflect.Slice:
|
||||
def.nested = true
|
||||
if typ.Elem().Kind() != reflect.String {
|
||||
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()))
|
||||
}
|
||||
|
||||
s.steps = append(s.steps, &StepDef{
|
||||
Handler: stepFunc,
|
||||
Expr: regex,
|
||||
hv: v,
|
||||
})
|
||||
s.steps = append(s.steps, def)
|
||||
}
|
||||
|
||||
// BeforeSuite registers a function or method
|
||||
|
@ -197,27 +200,20 @@ func (s *Suite) run() {
|
|||
}
|
||||
|
||||
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
||||
for _, h := range s.steps {
|
||||
if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 {
|
||||
var args []interface{}
|
||||
for _, m := range m[1:] {
|
||||
args = append(args, m)
|
||||
def := s.matchStepText(step.Text)
|
||||
if def != nil && step.Argument != nil {
|
||||
def.args = append(def.args, step.Argument)
|
||||
}
|
||||
if step.Argument != nil {
|
||||
args = append(args, step.Argument)
|
||||
}
|
||||
h.args = args
|
||||
return h
|
||||
}
|
||||
}
|
||||
// @TODO can handle ambiguous
|
||||
return nil
|
||||
return def
|
||||
}
|
||||
|
||||
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||
match := s.matchStep(step)
|
||||
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)
|
||||
return ErrUndefined
|
||||
}
|
||||
|
@ -227,11 +223,6 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// run before step handlers
|
||||
for _, f := range s.beforeStepHandlers {
|
||||
f(step)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
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())
|
||||
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 {
|
||||
if nil == result {
|
||||
return nil
|
||||
|
@ -267,33 +280,32 @@ func (s *Suite) maybeSubSteps(result interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
steps, ok := result.([]string)
|
||||
steps, ok := result.(Steps)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected error, should have been []string: %T - %+v", result, result)
|
||||
}
|
||||
|
||||
for _, step := range steps {
|
||||
var def *StepDef
|
||||
for _, text := range steps {
|
||||
if def := s.matchStepText(text); def == nil {
|
||||
return ErrUndefined
|
||||
} else if err := s.maybeSubSteps(def.run()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Suite) matchStepText(text string) *StepDef {
|
||||
for _, h := range s.steps {
|
||||
if m := h.Expr.FindStringSubmatch(step); len(m) > 0 {
|
||||
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
|
||||
def = h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if def == nil {
|
||||
return ErrUndefined
|
||||
}
|
||||
|
||||
// @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 h
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -89,6 +89,21 @@ func SuiteContext(s *Suite) {
|
|||
// Introduced to test formatter/cucumber.feature
|
||||
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 {
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче