test table node and events fired
Этот коммит содержится в:
родитель
00fab00e22
коммит
49130c8b08
9 изменённых файлов: 140 добавлений и 70 удалений
34
features/events.feature
Обычный файл
34
features/events.feature
Обычный файл
|
@ -0,0 +1,34 @@
|
|||
Feature: suite events
|
||||
In order to run tasks before and after important events
|
||||
As a test suite
|
||||
I need to provide a way to hook into these events
|
||||
|
||||
Background:
|
||||
Given I'm listening to suite events
|
||||
|
||||
Scenario: triggers before scenario event
|
||||
Given a feature path "features/load_features.feature:6"
|
||||
When I run feature suite
|
||||
Then there was event triggered before scenario "load features within path"
|
||||
|
||||
Scenario: triggers appropriate events for a single scenario
|
||||
Given a feature path "features/load_features.feature:6"
|
||||
When I run feature suite
|
||||
Then these events had to be fired for a number of times:
|
||||
| BeforeSuite | 1 |
|
||||
| BeforeScenario | 1 |
|
||||
| BeforeStep | 3 |
|
||||
| AfterStep | 3 |
|
||||
| AfterScenario | 1 |
|
||||
| AfterSuite | 1 |
|
||||
|
||||
Scenario: triggers appropriate events whole feature
|
||||
Given a feature path "features/load_features.feature"
|
||||
When I run feature suite
|
||||
Then these events had to be fired for a number of times:
|
||||
| BeforeSuite | 1 |
|
||||
| BeforeScenario | 4 |
|
||||
| BeforeStep | 13 |
|
||||
| AfterStep | 13 |
|
||||
| AfterScenario | 4 |
|
||||
| AfterSuite | 1 |
|
|
@ -1,12 +0,0 @@
|
|||
Feature: suite hooks
|
||||
In order to run tasks before and after important events
|
||||
As a test suite
|
||||
I need to provide a way to hook into these events
|
||||
|
||||
Background:
|
||||
Given I'm listening to suite events
|
||||
|
||||
Scenario: triggers before scenario hook
|
||||
Given a feature path "features/load_features.feature:6"
|
||||
When I run feature suite
|
||||
Then there was event triggered before scenario "load features within path"
|
|
@ -8,7 +8,7 @@ Feature: load features
|
|||
When I parse features
|
||||
Then I should have 2 feature files:
|
||||
"""
|
||||
features/hooks.feature
|
||||
features/events.feature
|
||||
features/load_features.feature
|
||||
"""
|
||||
|
||||
|
@ -27,10 +27,10 @@ Feature: load features
|
|||
|
||||
Scenario: load a number of feature files
|
||||
Given a feature path "features/load_features.feature"
|
||||
And a feature path "features/hooks.feature"
|
||||
And a feature path "features/events.feature"
|
||||
When I parse features
|
||||
Then I should have 2 feature files:
|
||||
"""
|
||||
features/load_features.feature
|
||||
features/hooks.feature
|
||||
features/events.feature
|
||||
"""
|
||||
|
|
|
@ -23,6 +23,7 @@ type pretty struct {
|
|||
commentPos int
|
||||
doneBackground bool
|
||||
background *gherkin.Background
|
||||
scenario *gherkin.Scenario
|
||||
|
||||
// summary
|
||||
started time.Time
|
||||
|
@ -38,20 +39,6 @@ func (f *pretty) line(tok *gherkin.Token) string {
|
|||
return cl(fmt.Sprintf("# %s:%d", f.feature.Path, tok.Line), black)
|
||||
}
|
||||
|
||||
// checks whether it should not print a background step once again
|
||||
func (f *pretty) canPrintStep(step *gherkin.Step) bool {
|
||||
if f.background == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if step.Background == nil {
|
||||
f.doneBackground = true
|
||||
return true
|
||||
}
|
||||
|
||||
return !f.doneBackground
|
||||
}
|
||||
|
||||
// Node takes a gherkin node for formatting
|
||||
func (f *pretty) Node(node interface{}) {
|
||||
switch t := node.(type) {
|
||||
|
@ -61,25 +48,27 @@ func (f *pretty) Node(node interface{}) {
|
|||
fmt.Println("")
|
||||
}
|
||||
f.feature = t
|
||||
f.doneBackground = false
|
||||
f.scenario = nil
|
||||
f.background = nil
|
||||
f.features = append(f.features, t)
|
||||
fmt.Println(bcl("Feature: ", white) + t.Title)
|
||||
fmt.Println(t.Description)
|
||||
case *gherkin.Background:
|
||||
// determine comment position based on step length
|
||||
f.commentPos = len(t.Token.Text)
|
||||
for _, step := range t.Steps {
|
||||
if len(step.Token.Text) > f.commentPos {
|
||||
f.commentPos = len(step.Token.Text)
|
||||
}
|
||||
}
|
||||
// do not repeat background
|
||||
if !f.doneBackground {
|
||||
// do not repeat background for the same feature
|
||||
if f.background == nil && f.scenario == nil {
|
||||
f.background = t
|
||||
// determine comment position based on step length
|
||||
f.commentPos = len(t.Token.Text)
|
||||
for _, step := range t.Steps {
|
||||
if len(step.Token.Text) > f.commentPos {
|
||||
f.commentPos = len(step.Token.Text)
|
||||
}
|
||||
}
|
||||
// print background node
|
||||
fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white))
|
||||
}
|
||||
case *gherkin.Scenario:
|
||||
f.scenario = t
|
||||
// determine comment position based on step length
|
||||
f.commentPos = len(t.Token.Text)
|
||||
for _, step := range t.Steps {
|
||||
|
@ -180,7 +169,8 @@ func (f *pretty) printStep(stepAction interface{}) {
|
|||
fatal(fmt.Errorf("unexpected step type received: %T", typ))
|
||||
}
|
||||
|
||||
if !f.canPrintStep(step) {
|
||||
// do not print background more than once
|
||||
if f.scenario == nil && step.Background != f.background {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -225,11 +215,33 @@ func (f *pretty) printStep(stepAction interface{}) {
|
|||
fmt.Println(cl(step.PyString.Raw, c))
|
||||
fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c))
|
||||
}
|
||||
if step.Table != nil {
|
||||
f.printTable(step.Table, c)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(s(step.Token.Indent) + bcl(err, red))
|
||||
}
|
||||
}
|
||||
|
||||
// print table with aligned table cells
|
||||
func (f *pretty) printTable(t *gherkin.Table, c color) {
|
||||
var longest = make([]int, len(t.Rows[0]))
|
||||
var cols = make([]string, len(t.Rows[0]))
|
||||
for _, row := range t.Rows {
|
||||
for i, col := range row {
|
||||
if longest[i] < len(col) {
|
||||
longest[i] = len(col)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, row := range t.Rows {
|
||||
for i, col := range row {
|
||||
cols[i] = col + s(longest[i]-len(col))
|
||||
}
|
||||
fmt.Println(s(t.Token.Indent) + cl("| "+strings.Join(cols, " | ")+" |", c))
|
||||
}
|
||||
}
|
||||
|
||||
// Passed is called to represent a passed step
|
||||
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
|
||||
s := &passed{step: step, handler: match}
|
||||
|
|
|
@ -155,7 +155,7 @@ type Table struct {
|
|||
*Token
|
||||
OutlineScenario *Scenario
|
||||
Step *Step
|
||||
rows [][]string
|
||||
Rows [][]string
|
||||
}
|
||||
|
||||
var allSteps = []TokenType{
|
||||
|
@ -369,17 +369,17 @@ func (p *parser) parsePystring() (*PyString, error) {
|
|||
}
|
||||
|
||||
func (p *parser) parseTable() (*Table, error) {
|
||||
tbl := &Table{}
|
||||
tbl := &Table{Token: p.peek()}
|
||||
for row := p.peek(); row.Type == TABLE_ROW; row = p.peek() {
|
||||
var cols []string
|
||||
for _, r := range strings.Split(strings.Trim(row.Value, "|"), "|") {
|
||||
cols = append(cols, strings.TrimFunc(r, unicode.IsSpace))
|
||||
}
|
||||
// ensure the same colum number for each row
|
||||
if len(tbl.rows) > 0 && len(tbl.rows[0]) != len(cols) {
|
||||
if len(tbl.Rows) > 0 && len(tbl.Rows[0]) != len(cols) {
|
||||
return tbl, p.err("table row has not the same number of columns compared to previous row", row.Line)
|
||||
}
|
||||
tbl.rows = append(tbl.rows, cols)
|
||||
tbl.Rows = append(tbl.Rows, cols)
|
||||
p.next() // jump over the peeked token
|
||||
}
|
||||
return tbl, nil
|
||||
|
|
|
@ -25,13 +25,13 @@ func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) {
|
|||
if s.Examples == nil {
|
||||
t.Fatalf("outline scenario '%s' has no examples", s.Title)
|
||||
}
|
||||
if len(s.Examples.rows) <= num {
|
||||
if len(s.Examples.Rows) <= num {
|
||||
t.Fatalf("outline scenario '%s' table has no row: %d", s.Title, num)
|
||||
}
|
||||
if len(s.Examples.rows[num]) != len(cols) {
|
||||
if len(s.Examples.Rows[num]) != len(cols) {
|
||||
t.Fatalf("outline scenario '%s' table row length, does not match expected: %d", s.Title, len(cols))
|
||||
}
|
||||
for i, col := range s.Examples.rows[num] {
|
||||
for i, col := range s.Examples.Rows[num] {
|
||||
if col != cols[i] {
|
||||
t.Fatalf("outline scenario '%s' table row %d, column %d - value '%s', does not match expected: %s", s.Title, num, i, col, cols[i])
|
||||
}
|
||||
|
|
|
@ -69,13 +69,13 @@ func (s *Step) assertTableRow(t *testing.T, num int, cols ...string) {
|
|||
if s.Table == nil {
|
||||
t.Fatalf("step '%s %s' has no table", s.Type, s.Text)
|
||||
}
|
||||
if len(s.Table.rows) <= num {
|
||||
if len(s.Table.Rows) <= num {
|
||||
t.Fatalf("step '%s %s' table has no row: %d", s.Type, s.Text, num)
|
||||
}
|
||||
if len(s.Table.rows[num]) != len(cols) {
|
||||
if len(s.Table.Rows[num]) != len(cols) {
|
||||
t.Fatalf("step '%s %s' table row length, does not match expected: %d", s.Type, s.Text, len(cols))
|
||||
}
|
||||
for i, col := range s.Table.rows[num] {
|
||||
for i, col := range s.Table.Rows[num] {
|
||||
if col != cols[i] {
|
||||
t.Fatalf("step '%s %s' table row %d, column %d - value '%s', does not match expected: %s", s.Type, s.Text, num, i, col, cols[i])
|
||||
}
|
||||
|
|
5
suite.go
5
suite.go
|
@ -11,9 +11,10 @@ import (
|
|||
"github.com/DATA-DOG/godog/gherkin"
|
||||
)
|
||||
|
||||
// Status represents a step status
|
||||
// Status represents a step or scenario status
|
||||
type Status int
|
||||
|
||||
// step or scenario status constants
|
||||
const (
|
||||
invalid Status = iota
|
||||
Passed
|
||||
|
@ -303,7 +304,7 @@ func (s *suite) runFeature(f *gherkin.Feature) {
|
|||
s.skipSteps(scenario.Steps)
|
||||
case status == Undefined:
|
||||
s.skipSteps(scenario.Steps)
|
||||
case status == invalid:
|
||||
case status == Passed || status == invalid:
|
||||
status = s.runSteps(scenario.Steps)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,24 +14,39 @@ type firedEvent struct {
|
|||
}
|
||||
|
||||
type suiteFeature struct {
|
||||
suite
|
||||
events []*firedEvent
|
||||
testedSuite *suite
|
||||
events []*firedEvent
|
||||
}
|
||||
|
||||
func (s *suiteFeature) HandleBeforeScenario(scenario *gherkin.Scenario) {
|
||||
// reset whole suite with the state
|
||||
s.testedSuite = &suite{fmt: &testFormatter{}}
|
||||
// our tested suite will have the same context registered
|
||||
SuiteContext(s.testedSuite)
|
||||
// reset feature paths
|
||||
cfg.paths = []string{}
|
||||
// reset event stack
|
||||
// reset all fired events
|
||||
s.events = []*firedEvent{}
|
||||
// reset formatter, which collects all details
|
||||
s.fmt = &testFormatter{}
|
||||
}
|
||||
|
||||
func (s *suiteFeature) iAmListeningToSuiteEvents(args ...*Arg) error {
|
||||
s.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{
|
||||
scenario,
|
||||
}})
|
||||
s.testedSuite.BeforeSuite(BeforeSuiteHandlerFunc(func() {
|
||||
s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}})
|
||||
}))
|
||||
s.testedSuite.AfterSuite(AfterSuiteHandlerFunc(func() {
|
||||
s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}})
|
||||
}))
|
||||
s.testedSuite.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
|
||||
}))
|
||||
s.testedSuite.AfterScenario(AfterScenarioHandlerFunc(func(scenario *gherkin.Scenario, status Status) {
|
||||
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, status}})
|
||||
}))
|
||||
s.testedSuite.BeforeStep(BeforeStepHandlerFunc(func(step *gherkin.Step) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
|
||||
}))
|
||||
s.testedSuite.AfterStep(AfterStepHandlerFunc(func(step *gherkin.Step, status Status) {
|
||||
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, status}})
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
@ -42,17 +57,17 @@ func (s *suiteFeature) featurePath(args ...*Arg) error {
|
|||
}
|
||||
|
||||
func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) {
|
||||
s.features, err = cfg.features()
|
||||
s.testedSuite.features, err = cfg.features()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) error {
|
||||
if len(s.features) != args[0].Int() {
|
||||
return fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.features))
|
||||
if len(s.testedSuite.features) != args[0].Int() {
|
||||
return fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.testedSuite.features))
|
||||
}
|
||||
expected := args[1].PyString().Lines
|
||||
var actual []string
|
||||
for _, ft := range s.features {
|
||||
for _, ft := range s.testedSuite.features {
|
||||
actual = append(actual, ft.Path)
|
||||
}
|
||||
if len(expected) != len(actual) {
|
||||
|
@ -70,13 +85,13 @@ func (s *suiteFeature) iRunFeatureSuite(args ...*Arg) error {
|
|||
if err := s.parseFeatures(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.run()
|
||||
s.testedSuite.run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteFeature) numScenariosRegistered(args ...*Arg) (err error) {
|
||||
var num int
|
||||
for _, ft := range s.features {
|
||||
for _, ft := range s.testedSuite.features {
|
||||
num += len(ft.Scenarios)
|
||||
}
|
||||
if num != args[0].Int() {
|
||||
|
@ -120,11 +135,28 @@ func (s *suiteFeature) thereWasEventTriggeredBeforeScenario(args ...*Arg) error
|
|||
return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, args[0].String(), `"`+strings.Join(found, `", "`)+`"`)
|
||||
}
|
||||
|
||||
func SuiteContext(g Suite) {
|
||||
s := &suiteFeature{
|
||||
suite: suite{},
|
||||
func (s *suiteFeature) theseEventsHadToBeFiredForNumberOfTimes(args ...*Arg) error {
|
||||
tbl := args[0].Table()
|
||||
if len(tbl.Rows[0]) != 2 {
|
||||
return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0]))
|
||||
}
|
||||
|
||||
for _, row := range tbl.Rows {
|
||||
args := []*Arg{
|
||||
StepArgument(""), // ignored
|
||||
StepArgument(row[1]),
|
||||
StepArgument(row[0]),
|
||||
}
|
||||
if err := s.thereWereNumEventsFired(args...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SuiteContext(g Suite) {
|
||||
s := &suiteFeature{}
|
||||
|
||||
g.BeforeScenario(s)
|
||||
|
||||
g.Step(
|
||||
|
@ -151,4 +183,7 @@ func SuiteContext(g Suite) {
|
|||
g.Step(
|
||||
regexp.MustCompile(`^there was event triggered before scenario "([^"]*)"$`),
|
||||
StepHandlerFunc(s.thereWasEventTriggeredBeforeScenario))
|
||||
g.Step(
|
||||
regexp.MustCompile(`^these events had to be fired for a number of times:$`),
|
||||
StepHandlerFunc(s.theseEventsHadToBeFiredForNumberOfTimes))
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче