test feature file loader
Этот коммит содержится в:
родитель
2d3d04e0e6
коммит
774d3d1837
8 изменённых файлов: 151 добавлений и 36 удалений
63
arguments.go
63
arguments.go
|
@ -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
Обычный файл
1
features/hooks.feature
Обычный файл
|
@ -0,0 +1 @@
|
||||||
|
Feature: suite hooks
|
25
features/load_features.feature
Обычный файл
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,
|
||||||
|
|
32
suite.go
32
suite.go
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче