adds scenarios to test nested steps

Этот коммит содержится в:
gedi 2017-04-27 23:48:54 +03:00
родитель ba80ebcd33
коммит 703b40de76
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 56604CDCCC201556
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 Обычный файл
Просмотреть файл

@ -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
Просмотреть файл

@ -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

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

@ -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 {

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()))
}
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)
}
if step.Argument != nil {
args = append(args, step.Argument)
}
h.args = args
return h
}
def := s.matchStepText(step.Text)
if def != nil && step.Argument != nil {
def.args = append(def.args, step.Argument)
}
// @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,38 +280,37 @@ 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 _, 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 {
for _, text := range steps {
if def := s.matchStepText(text); 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 {
} 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(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) {
err = prevErr
for _, step := range steps {

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

@ -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 {