Этот коммит содержится в:
gedi 2015-06-17 13:54:31 +03:00
родитель 2d3d04e0e6
коммит 774d3d1837
8 изменённых файлов: 151 добавлений и 36 удалений

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

@ -9,9 +9,9 @@ import (
// the regexp submatch to handle the step // the regexp submatch to handle the step
type Arg string type Arg string
// Float converts an argument to float64 // Float64 converts an argument to float64
// or panics if unable to convert it // or panics if unable to convert it
func (a Arg) Float() float64 { func (a Arg) Float64() float64 {
v, err := strconv.ParseFloat(string(a), 64) v, err := strconv.ParseFloat(string(a), 64)
if err == nil { if err == nil {
return v return v
@ -19,17 +19,72 @@ func (a Arg) Float() float64 {
panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, a, err)) panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, a, err))
} }
// Int converts an argument to int64 // Float32 converts an argument to float32
// or panics if unable to convert it // or panics if unable to convert it
func (a Arg) Int() int64 { func (a Arg) Float32() float32 {
v, err := strconv.ParseFloat(string(a), 32)
if err == nil {
return float32(v)
}
panic(fmt.Sprintf(`cannot convert "%s" to float32: %s`, a, err))
}
// Int converts an argument to int
// or panics if unable to convert it
func (a Arg) Int() int {
v, err := strconv.ParseInt(string(a), 10, 0) v, err := strconv.ParseInt(string(a), 10, 0)
if err == nil {
return int(v)
}
panic(fmt.Sprintf(`cannot convert "%s" to int: %s`, a, err))
}
// Int64 converts an argument to int64
// or panics if unable to convert it
func (a Arg) Int64() int64 {
v, err := strconv.ParseInt(string(a), 10, 64)
if err == nil { if err == nil {
return v return v
} }
panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, a, err)) panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, a, err))
} }
// Int32 converts an argument to int32
// or panics if unable to convert it
func (a Arg) Int32() int32 {
v, err := strconv.ParseInt(string(a), 10, 32)
if err == nil {
return int32(v)
}
panic(fmt.Sprintf(`cannot convert "%s" to int32: %s`, a, err))
}
// Int16 converts an argument to int16
// or panics if unable to convert it
func (a Arg) Int16() int16 {
v, err := strconv.ParseInt(string(a), 10, 16)
if err == nil {
return int16(v)
}
panic(fmt.Sprintf(`cannot convert "%s" to int16: %s`, a, err))
}
// Int8 converts an argument to int8
// or panics if unable to convert it
func (a Arg) Int8() int8 {
v, err := strconv.ParseInt(string(a), 10, 8)
if err == nil {
return int8(v)
}
panic(fmt.Sprintf(`cannot convert "%s" to int8: %s`, a, err))
}
// String converts an argument to string // String converts an argument to string
func (a Arg) String() string { func (a Arg) String() string {
return string(a) return string(a)
} }
// Bytes converts an argument string to bytes
func (a Arg) Bytes() []byte {
return []byte(a)
}

1
features/hooks.feature Обычный файл
Просмотреть файл

@ -0,0 +1 @@
Feature: suite hooks

25
features/load_features.feature Обычный файл
Просмотреть файл

@ -0,0 +1,25 @@
Feature: load features
In order to run features
As a test suite
I need to be able to load features
Scenario: load features within path
Given a feature path "features"
When I parse features
Then I should have 2 feature files
Scenario: load a specific feature file
Given a feature path "features/load_features.feature"
When I parse features
Then I should have 1 feature file
Scenario: load a feature file with a specified scenario
Given a feature path "features/load_features.feature:6"
When I parse features
Then I should have 1 scenario registered
Scenario: load a number of feature files
Given a feature path "features/load_features.feature"
And a feature path "features/hooks.feature"
When I parse features
Then I should have 2 feature files

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

@ -1,9 +0,0 @@
Feature: godog bdd suite
In order to test application behavior
As a suite
I need to be able to register and run features
Scenario: parses all features in path
Given a feature path "features"
When I parse features
Then I should have 5 feature file

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

@ -103,11 +103,11 @@ func (f *pretty) Node(node interface{}) {
f.doneBackground = false f.doneBackground = false
f.background = nil f.background = nil
f.features = append(f.features, t) f.features = append(f.features, t)
fmt.Println(bcl("Feature: ", white) + t.Title + "\n") fmt.Println(bcl("Feature: ", white) + t.Title)
fmt.Println(t.Description) fmt.Println(t.Description)
case *gherkin.Background: case *gherkin.Background:
f.background = t f.background = t
fmt.Println(bcl("Background:", white) + "\n") fmt.Println("\n" + bcl("Background:", white))
case *gherkin.Scenario: case *gherkin.Scenario:
f.commentPos = len(t.Token.Text) f.commentPos = len(t.Token.Text)
for _, step := range t.Steps { for _, step := range t.Steps {
@ -117,7 +117,7 @@ func (f *pretty) Node(node interface{}) {
} }
text := strings.Repeat(" ", t.Token.Indent) + bcl("Scenario: ", white) + t.Title text := strings.Repeat(" ", t.Token.Indent) + bcl("Scenario: ", white) + t.Title
text += strings.Repeat(" ", f.commentPos-len(t.Token.Text)+1) + f.line(t.Token) text += strings.Repeat(" ", f.commentPos-len(t.Token.Text)+1) + f.line(t.Token)
fmt.Println(text + "\n") fmt.Println("\n" + text)
} }
} }

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

@ -46,7 +46,7 @@ func (l *lexer) read() *Token {
if len(line) == 0 { if len(line) == 0 {
return &Token{ return &Token{
Type: NEW_LINE, Type: NEW_LINE,
Line: l.lines - 1, Line: l.lines,
} }
} }
// comment // comment
@ -55,7 +55,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: COMMENT, Type: COMMENT,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: comment, Value: comment,
Text: line, Text: line,
Comment: comment, Comment: comment,
@ -66,7 +66,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: PYSTRING, Type: PYSTRING,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Text: line, Text: line,
} }
} }
@ -74,7 +74,7 @@ func (l *lexer) read() *Token {
if m := matchers["step"].FindStringSubmatch(line); len(m) > 0 { if m := matchers["step"].FindStringSubmatch(line); len(m) > 0 {
tok := &Token{ tok := &Token{
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: strings.TrimSpace(m[3]), Value: strings.TrimSpace(m[3]),
Text: line, Text: line,
Comment: strings.Trim(m[4], " #"), Comment: strings.Trim(m[4], " #"),
@ -98,7 +98,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: SCENARIO, Type: SCENARIO,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: strings.TrimSpace(m[2]), Value: strings.TrimSpace(m[2]),
Text: line, Text: line,
Comment: strings.Trim(m[3], " #"), Comment: strings.Trim(m[3], " #"),
@ -109,7 +109,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: BACKGROUND, Type: BACKGROUND,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Text: line, Text: line,
Comment: strings.Trim(m[2], " #"), Comment: strings.Trim(m[2], " #"),
} }
@ -119,7 +119,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: FEATURE, Type: FEATURE,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: strings.TrimSpace(m[2]), Value: strings.TrimSpace(m[2]),
Text: line, Text: line,
Comment: strings.Trim(m[3], " #"), Comment: strings.Trim(m[3], " #"),
@ -130,7 +130,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: TAGS, Type: TAGS,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: strings.TrimSpace(m[2]), Value: strings.TrimSpace(m[2]),
Text: line, Text: line,
Comment: strings.Trim(m[3], " #"), Comment: strings.Trim(m[3], " #"),
@ -141,7 +141,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: TABLE_ROW, Type: TABLE_ROW,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: strings.TrimSpace(m[2]), Value: strings.TrimSpace(m[2]),
Text: line, Text: line,
Comment: strings.Trim(m[3], " #"), Comment: strings.Trim(m[3], " #"),
@ -152,7 +152,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: SCENARIO_OUTLINE, Type: SCENARIO_OUTLINE,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Value: strings.TrimSpace(m[2]), Value: strings.TrimSpace(m[2]),
Text: line, Text: line,
Comment: strings.Trim(m[3], " #"), Comment: strings.Trim(m[3], " #"),
@ -163,7 +163,7 @@ func (l *lexer) read() *Token {
return &Token{ return &Token{
Type: EXAMPLES, Type: EXAMPLES,
Indent: len(m[1]), Indent: len(m[1]),
Line: l.lines - 1, Line: l.lines,
Text: line, Text: line,
Comment: strings.Trim(m[2], " #"), Comment: strings.Trim(m[2], " #"),
} }
@ -172,7 +172,7 @@ func (l *lexer) read() *Token {
text := strings.TrimLeftFunc(line, unicode.IsSpace) text := strings.TrimLeftFunc(line, unicode.IsSpace)
return &Token{ return &Token{
Type: TEXT, Type: TEXT,
Line: l.lines - 1, Line: l.lines,
Value: text, Value: text,
Indent: len(line) - len(text), Indent: len(line) - len(text),
Text: line, Text: line,

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

@ -8,6 +8,16 @@ import (
"github.com/DATA-DOG/godog/gherkin" "github.com/DATA-DOG/godog/gherkin"
) )
type BeforeScenarioHandler interface {
BeforeScenario(scenario *gherkin.Scenario)
}
type BeforeScenarioHandlerFunc func(scenario *gherkin.Scenario)
func (f BeforeScenarioHandlerFunc) BeforeScenario(scenario *gherkin.Scenario) {
f(scenario)
}
// Objects implementing the StepHandler interface can be // Objects implementing the StepHandler interface can be
// registered as step definitions in godog // registered as step definitions in godog
// //
@ -44,13 +54,15 @@ type stepMatchHandler struct {
// Suite is an interface which allows various contexts // Suite is an interface which allows various contexts
// to register step definitions and event handlers // to register step definitions and event handlers
type Suite interface { type Suite interface {
Step(exp *regexp.Regexp, h StepHandler) Step(expr *regexp.Regexp, h StepHandler)
BeforeScenario(h BeforeScenarioHandler)
} }
type suite struct { type suite struct {
steps []*stepMatchHandler beforeScenarioHandlers []BeforeScenarioHandler
features []*gherkin.Feature stepHandlers []*stepMatchHandler
fmt Formatter features []*gherkin.Feature
fmt Formatter
} }
// New initializes a suite which supports the Suite // New initializes a suite which supports the Suite
@ -71,12 +83,16 @@ func New() *suite {
// If none of the StepHandlers are matched, then a pending // If none of the StepHandlers are matched, then a pending
// step error will be raised. // step error will be raised.
func (s *suite) Step(expr *regexp.Regexp, h StepHandler) { func (s *suite) Step(expr *regexp.Regexp, h StepHandler) {
s.steps = append(s.steps, &stepMatchHandler{ s.stepHandlers = append(s.stepHandlers, &stepMatchHandler{
handler: h, handler: h,
expr: expr, expr: expr,
}) })
} }
func (s *suite) BeforeScenario(h BeforeScenarioHandler) {
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, h)
}
// Run - runs a godog feature suite // Run - runs a godog feature suite
func (s *suite) Run() { func (s *suite) Run() {
var err error var err error
@ -98,7 +114,7 @@ func (s *suite) Run() {
func (s *suite) runStep(step *gherkin.Step) (err error) { func (s *suite) runStep(step *gherkin.Step) (err error) {
var match *stepMatchHandler var match *stepMatchHandler
var args []Arg var args []Arg
for _, h := range s.steps { for _, h := range s.stepHandlers {
if m := h.expr.FindStringSubmatch(step.Text); len(m) > 0 { if m := h.expr.FindStringSubmatch(step.Text); len(m) > 0 {
match = h match = h
for _, a := range m[1:] { for _, a := range m[1:] {
@ -151,6 +167,10 @@ func (s *suite) runFeature(f *gherkin.Feature) {
s.fmt.Node(f) s.fmt.Node(f)
var failed bool var failed bool
for _, scenario := range f.Scenarios { for _, scenario := range f.Scenarios {
// run before scenario handlers
for _, h := range s.beforeScenarioHandlers {
h.BeforeScenario(scenario)
}
// background // background
if f.Background != nil && !failed { if f.Background != nil && !failed {
s.fmt.Node(f.Background) s.fmt.Node(f.Background)

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

@ -3,14 +3,21 @@ package godog
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"github.com/DATA-DOG/godog/gherkin"
) )
type suiteFeature struct { type suiteFeature struct {
suite suite
} }
func (s *suiteFeature) BeforeScenario(scenario *gherkin.Scenario) {
// reset feature paths
cfg.paths = []string{}
}
func (s *suiteFeature) featurePath(args ...Arg) error { func (s *suiteFeature) featurePath(args ...Arg) error {
cfg.paths = []string{args[0].String()} cfg.paths = append(cfg.paths, args[0].String())
return nil return nil
} }
@ -20,17 +27,30 @@ func (s *suiteFeature) parseFeatures(args ...Arg) (err error) {
} }
func (s *suiteFeature) numParsed(args ...Arg) (err error) { func (s *suiteFeature) numParsed(args ...Arg) (err error) {
if len(s.features) != int(args[0].Int()) { if len(s.features) != args[0].Int() {
err = fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.features)) err = fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.features))
} }
return return
} }
func (s *suiteFeature) numScenariosRegistered(args ...Arg) (err error) {
var num int
for _, ft := range s.features {
num += len(ft.Scenarios)
}
if num != args[0].Int() {
err = fmt.Errorf("expected %d scenarios to be registered, but got %d", args[0].Int(), num)
}
return
}
func SuiteContext(g Suite) { func SuiteContext(g Suite) {
s := &suiteFeature{ s := &suiteFeature{
suite: suite{}, suite: suite{},
} }
g.BeforeScenario(s)
g.Step( g.Step(
regexp.MustCompile(`^a feature path "([^"]*)"$`), regexp.MustCompile(`^a feature path "([^"]*)"$`),
StepHandlerFunc(s.featurePath)) StepHandlerFunc(s.featurePath))
@ -40,4 +60,7 @@ func SuiteContext(g Suite) {
g.Step( g.Step(
regexp.MustCompile(`^I should have ([\d]+) features? files?$`), regexp.MustCompile(`^I should have ([\d]+) features? files?$`),
StepHandlerFunc(s.numParsed)) StepHandlerFunc(s.numParsed))
g.Step(
regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`),
StepHandlerFunc(s.numScenariosRegistered))
} }