Repository: go-bdd/gobdd
Pull request #152 (2024-05-03 12:17:40)

feat: run rules
New keyword introduced in gherkin 6: https://cucumber.io/docs/gherkin/reference/#rule

```gherkin
Feature: math operations
  Rule: add things
    Scenario: add two digits
      When I add 1 and 2
      Then the result should equal 3
```

bkielbasa: thanks for your contribution! 

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a8b14e2..39e8c9b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -51,13 +51,13 @@ jobs:
           go test -v -covermode=count -coverprofile=coverage.out
 
       - name: Convert coverage to lcov
-        uses: jandelgado/gcov2lcov-action@v1.0.0
+        uses: jandelgado/gcov2lcov-action@v1
         with:
             infile: coverage.out
             outfile: coverage.lcov
 
       - name: Coveralls
-        uses: coverallsapp/github-action@v1.0.1
+        uses: coverallsapp/github-action@v1
         with:
             github-token: ${{ secrets.github_token }}
             path-to-lcov: coverage.lcov
diff --git a/context/context.go b/context/context.go
index 06369d3..71c17aa 100644
--- a/context/context.go
+++ b/context/context.go
@@ -1,7 +1,7 @@
 package context
 
 import (
-	"github.com/go-bdd/gobdd"
+	gobdd "github.com/go-bdd/gobdd"
 )
 
 // Holds data from previously executed steps
diff --git a/context_get.go b/context_get.go
index 94cfdf4..eade772 100644
--- a/context_get.go
+++ b/context_get.go
@@ -1,12 +1,13 @@
-// Code generated .* DO NOT EDIT.
+// Code generated .* DO NOT EDIT.	
 package gobdd
 
 import "fmt"
 
+
 func (ctx Context) GetString(key interface{}, defaultValue ...string) (string, error) {
 	if len(defaultValue) > 1 {
-		return "", fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return "", fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -24,8 +25,8 @@ func (ctx Context) GetString(key interface{}, defaultValue ...string) (string, e
 
 func (ctx Context) GetInt(key interface{}, defaultValue ...int) (int, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -43,8 +44,8 @@ func (ctx Context) GetInt(key interface{}, defaultValue ...int) (int, error) {
 
 func (ctx Context) GetInt8(key interface{}, defaultValue ...int8) (int8, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -62,8 +63,8 @@ func (ctx Context) GetInt8(key interface{}, defaultValue ...int8) (int8, error)
 
 func (ctx Context) GetInt16(key interface{}, defaultValue ...int16) (int16, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -81,8 +82,8 @@ func (ctx Context) GetInt16(key interface{}, defaultValue ...int16) (int16, erro
 
 func (ctx Context) GetInt32(key interface{}, defaultValue ...int32) (int32, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -100,8 +101,8 @@ func (ctx Context) GetInt32(key interface{}, defaultValue ...int32) (int32, erro
 
 func (ctx Context) GetInt64(key interface{}, defaultValue ...int64) (int64, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -119,8 +120,8 @@ func (ctx Context) GetInt64(key interface{}, defaultValue ...int64) (int64, erro
 
 func (ctx Context) GetFloat32(key interface{}, defaultValue ...float32) (float32, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -138,8 +139,8 @@ func (ctx Context) GetFloat32(key interface{}, defaultValue ...float32) (float32
 
 func (ctx Context) GetFloat64(key interface{}, defaultValue ...float64) (float64, error) {
 	if len(defaultValue) > 1 {
-		return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return 0, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -157,8 +158,8 @@ func (ctx Context) GetFloat64(key interface{}, defaultValue ...float64) (float64
 
 func (ctx Context) GetBool(key interface{}, defaultValue ...bool) (bool, error) {
 	if len(defaultValue) > 1 {
-		return false, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
-	}
+        return false, fmt.Errorf("allowed to pass only 1 default value but %d got", len(defaultValue))
+    }
 
 	if _, ok := ctx.values[key]; !ok {
 		if len(defaultValue) == 1 {
@@ -173,3 +174,4 @@ func (ctx Context) GetBool(key interface{}, defaultValue ...bool) (bool, error)
 	}
 	return value, nil
 }
+
diff --git a/features/background.feature b/features/background.feature
index 40287d4..124abad 100644
--- a/features/background.feature
+++ b/features/background.feature
@@ -4,3 +4,11 @@ Feature: using background steps
 
   Scenario: the background step should be executed
     Then the result should equal 3
+
+  Rule: adding and concat
+    Background: concat
+      When I concat word Hello and text " World!"
+
+    Scenario: the background steps should be executed
+      Then the result should equal 3
+      Then the result should equal text "Hello World!"
diff --git a/features/example.feature b/features/example.feature
index 8053737..c6a9dc1 100644
--- a/features/example.feature
+++ b/features/example.feature
@@ -2,3 +2,4 @@ Feature: math operations
   Scenario: add two digits
     When I add 1 and 2
     Then the result should equal 3
+    
diff --git a/features/example_rule.feature b/features/example_rule.feature
new file mode 100644
index 0000000..3ae6b41
--- /dev/null
+++ b/features/example_rule.feature
@@ -0,0 +1,6 @@
+Feature: math operations
+  Rule: add things
+    Scenario: add two digits
+      When I add 1 and 2
+      Then the result should equal 3
+
diff --git a/features/ignored_rule_tags.feature b/features/ignored_rule_tags.feature
new file mode 100644
index 0000000..eec09f1
--- /dev/null
+++ b/features/ignored_rule_tags.feature
@@ -0,0 +1,9 @@
+
+Feature: ignored tags
+  @ignore
+  Rule: this rule should be ignored
+    Scenario: the scenario should be ignored
+      Then fail the test
+  Rule: this rule should run
+    Scenario: the scenario should pass
+      Then the test should pass
diff --git a/features/ignored_tags.feature b/features/ignored_tags.feature
index dbdfdd6..fae2f60 100644
--- a/features/ignored_tags.feature
+++ b/features/ignored_tags.feature
@@ -2,3 +2,5 @@ Feature: ignored tags
   @ignore
   Scenario: the scenario should be ignored
     Then fail the test
+  Scenario: the scenario should pass
+    Then the test should pass
diff --git a/features/tags.feature b/features/tags.feature
index 7f53005..0b87f25 100644
--- a/features/tags.feature
+++ b/features/tags.feature
@@ -7,4 +7,15 @@ Feature: ignored tags
     Then the test should pass
     Examples:
   Scenario: the test should never be executed
-    Then fail the test
\ No newline at end of file
+    Then fail the test
+
+  Rule: the rule should never be executed
+    Scenario: the test in ignored rule should never be executed
+      Then fail the test
+
+  Rule: this rule should run
+    @tag
+    Scenario: the test in executed rule should pass
+      Then the test should pass
+    Scenario: the test in executed rule should never be executed
+      Then fail the test
\ No newline at end of file
diff --git a/go.mod b/go.mod
index a04da9f..196bc52 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,17 @@ go 1.17
 
 require (
 	github.com/cucumber/gherkin/go/v28 v28.0.0
-	github.com/cucumber/messages/go/v24 v24.0.1
-	github.com/go-bdd/assert v0.0.0-20190820124234-20d47a68475d
+	github.com/cucumber/messages/go/v24 v24.1.0
+	github.com/go-bdd/assert v0.0.0-20200713105154-236f01430281
+	github.com/stretchr/testify v1.9.0
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/gofrs/uuid v4.4.0+incompatible // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
-	github.com/stretchr/testify v1.8.4
+	github.com/pmezard/go-difflib v1.0.0 // indirect
 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index fa4a012..71af307 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,14 @@
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cucumber/gherkin/go/v28 v28.0.0 h1:SBqwscPOhe83JF0ukpEj+4QZ2ScOpPQByC0gD3cXBkg=
 github.com/cucumber/gherkin/go/v28 v28.0.0/go.mod h1:HVwDrzWvtsVbkxHw6KVZFA79x5uSLb+ajzS0BXuHiE8=
-github.com/cucumber/messages/go/v24 v24.0.1 h1:jajAQDk3fPa4RhIANE+NOxGdCKQdi7RYjd8wdKXnOu4=
 github.com/cucumber/messages/go/v24 v24.0.1/go.mod h1:ns4Befq4c4n9/B5APpTlBu5kXL1DVE4+5bbe0vSV4fc=
+github.com/cucumber/messages/go/v24 v24.1.0 h1:JMpspvV3IoGwcbEUbsKuxr0tRkFP7aqcQ5SvoanS/DA=
+github.com/cucumber/messages/go/v24 v24.1.0/go.mod h1:PR0+ygYqqyT1/C4EmioDdvgR8YG9+SZEkxEKw2jfd8g=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-bdd/assert v0.0.0-20190820124234-20d47a68475d h1:zQazu3kApPoajWmXj9zFpCNE+UDefwwFRijKjzvHNCM=
-github.com/go-bdd/assert v0.0.0-20190820124234-20d47a68475d/go.mod h1:dOoqt7g2I/fpR7/Pyz0P19J3xjDj5lsHn3v9EaFLRjM=
+github.com/go-bdd/assert v0.0.0-20200713105154-236f01430281 h1:jfonqldVSNVekjmLvHqelFzBba7SwylNIZ3fbM/lKVs=
+github.com/go-bdd/assert v0.0.0-20200713105154-236f01430281/go.mod h1:dOoqt7g2I/fpR7/Pyz0P19J3xjDj5lsHn3v9EaFLRjM=
 github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
 github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -21,10 +22,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/gobdd.go b/gobdd.go
index 8f85254..aab3987 100644
--- a/gobdd.go
+++ b/gobdd.go
@@ -172,10 +172,13 @@ type TestingT interface {
 // TestingTKey is used to store reference to current *testing.T instance
 type TestingTKey struct{}
 
-// FeatureKey is used to store reference to current *msgs.GherkinDocument_Feature instance
+// FeatureKey is used to store reference to current *msgs.Feature instance
 type FeatureKey struct{}
 
-// ScenarioKey is used to store reference to current *msgs.GherkinDocument_Feature_Scenario instance
+// RuleKey is used to store reference to current *msgs.Rule instance
+type RuleKey struct{}
+
+// ScenarioKey is used to store reference to current *msgs.Scenario instance
 type ScenarioKey struct{}
 
 // Creates a new suites with given configuration and empty steps defined
@@ -214,7 +217,7 @@ func (s *Suite) AddParameterTypes(from string, to []string) {
 	for _, to := range to {
 		_, err := regexp.Compile(to)
 		if err != nil {
-			s.t.Fatalf(`the regular expresion for key %s doesn't compile: %s`, from, to)
+			s.t.Fatalf(`the regular expression for key %s doesn't compile: %s`, from, to)
 		}
 
 		s.parameterTypes[from] = append(s.parameterTypes[from], to)
@@ -347,37 +350,29 @@ func (s *Suite) executeFeature(feature feature) error {
 }
 
 func (s *Suite) runFeature(feature *msgs.Feature) error {
-	for _, tag := range feature.Tags {
-		if contains(s.options.ignoreTags, tag.Name) {
-			s.t.Logf("the feature (%s) is ignored ", feature.Name)
-			return nil
-		}
+	if s.shouldSkipFeatureOrRule(feature.Tags) {
+		s.t.Logf("the feature (%s) is ignored ", feature.Name)
+		return nil
 	}
 
 	hasErrors := false
 
 	s.t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(feature.Keyword), feature.Name), func(t *testing.T) {
-		var bkgSteps *msgs.Background
+		backgrounds := []*msgs.Background{}
 
 		for _, child := range feature.Children {
 			if child.Background != nil {
-				bkgSteps = child.Background
+				backgrounds = append(backgrounds, child.Background)
 			}
 
-			scenario := child.Scenario
-			if scenario == nil {
-				continue
+			if rule := child.Rule; rule != nil {
+				s.runRule(feature, rule, backgrounds, t)
 			}
-
-			if s.skipScenario(append(feature.Tags, scenario.Tags...)) {
-				t.Log(fmt.Sprintf("Skipping scenario %s", scenario.Name))
-				continue
+			if scenario := child.Scenario; scenario != nil {
+				ctx := NewContext()
+				ctx.Set(FeatureKey{}, feature)
+				s.runScenario(ctx, scenario, backgrounds, t, feature.Tags)
 			}
-			ctx := NewContext()
-			ctx.Set(FeatureKey{}, feature)
-			ctx.Set(ScenarioKey{}, scenario)
-
-			s.runScenario(ctx, scenario, bkgSteps, t)
 		}
 	})
 
@@ -390,8 +385,7 @@ func (s *Suite) runFeature(feature *msgs.Feature) error {
 
 func (s *Suite) getOutlineStep(
 	steps []*msgs.Step,
-	examples []*msgs.Examples,
-) []*msgs.Step {
+	examples []*msgs.Examples) []*msgs.Step {
 	stepsList := make([][]*msgs.Step, len(steps))
 
 	for i, outlineStep := range steps {
@@ -419,19 +413,17 @@ func (s *Suite) getOutlineStep(
 
 func (s *Suite) stepsFromExamples(
 	sourceStep *msgs.Step,
-	example *msgs.Examples,
-) []*msgs.Step {
+	example *msgs.Examples) []*msgs.Step {
 	steps := []*msgs.Step{}
 
-	placeholders := []*msgs.TableCell{}
-	if example.TableHeader != nil {
-		placeholders = example.TableHeader.Cells
-	}
 	placeholdersValues := []string{}
 
-	for _, placeholder := range placeholders {
-		ph := "<" + placeholder.Value + ">"
-		placeholdersValues = append(placeholdersValues, ph)
+	if example.TableHeader != nil {
+		placeholders := example.TableHeader.Cells
+		for _, placeholder := range placeholders {
+			ph := "<" + placeholder.Value + ">"
+			placeholdersValues = append(placeholdersValues, ph)
+		}
 	}
 
 	text := sourceStep.Text
@@ -451,9 +443,13 @@ func (s *Suite) stepsFromExamples(
 
 		// clone a step
 		step := &msgs.Step{
-			Location: sourceStep.Location,
-			Keyword:  sourceStep.Keyword,
-			Text:     stepText,
+			Location:    sourceStep.Location,
+			Keyword:     sourceStep.Keyword,
+			Text:        stepText,
+			KeywordType: sourceStep.KeywordType,
+			DocString:   sourceStep.DocString,
+			DataTable:   sourceStep.DataTable,
+			Id:          sourceStep.Id,
 		}
 
 		steps = append(steps, step)
@@ -464,8 +460,7 @@ func (s *Suite) stepsFromExamples(
 
 func (s *Suite) stepFromExample(
 	stepName string,
-	row *msgs.TableRow, placeholders []string,
-) (string, string) {
+	row *msgs.TableRow, placeholders []string) (string, string) {
 	expr := stepName
 
 	for i, ph := range placeholders {
@@ -500,20 +495,51 @@ func (s *Suite) callAfterSteps(ctx Context) {
 		f(ctx)
 	}
 }
+func (s *Suite) runRule(feature *msgs.Feature, rule *msgs.Rule,
+	backgrounds []*msgs.Background, t *testing.T) {
+	ruleTags := feature.Tags
+	ruleTags = append(ruleTags, rule.Tags...)
+
+	if s.shouldSkipFeatureOrRule(ruleTags) {
+		s.t.Logf("the rule (%s) is ignored ", feature.Name)
+		return
+	}
+
+	ruleBackgrounds := []*msgs.Background{}
+	ruleBackgrounds = append(ruleBackgrounds, backgrounds...)
 
+	t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(rule.Keyword), rule.Name), func(t *testing.T) {
+		for _, ruleChild := range rule.Children {
+			if ruleChild.Background != nil {
+				ruleBackgrounds = append(ruleBackgrounds, ruleChild.Background)
+			}
+			if scenario := ruleChild.Scenario; scenario != nil {
+				ctx := NewContext()
+				ctx.Set(FeatureKey{}, feature)
+				ctx.Set(RuleKey{}, rule)
+				s.runScenario(ctx, scenario, ruleBackgrounds, t, ruleTags)
+			}
+		}
+	})
+}
 func (s *Suite) runScenario(ctx Context, scenario *msgs.Scenario,
-	bkg *msgs.Background, t *testing.T,
-) {
+	backgrounds []*msgs.Background, t *testing.T, parentTags []*msgs.Tag) {
+	if s.shouldSkipScenario(append(parentTags, scenario.Tags...)) {
+		t.Logf("Skipping scenario %s", scenario.Name)
+		return
+	}
+
 	t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(scenario.Keyword), scenario.Name), func(t *testing.T) {
 		// NOTE consider passing t as argument to scenario hooks
+		ctx.Set(ScenarioKey{}, scenario)
 		ctx.Set(TestingTKey{}, t)
 		defer ctx.Set(TestingTKey{}, nil)
 
 		s.callBeforeScenarios(ctx)
 		defer s.callAfterScenarios(ctx)
 
-		if bkg != nil {
-			steps := s.getBackgroundSteps(bkg)
+		if len(backgrounds) > 0 {
+			steps := s.getBackgroundSteps(backgrounds)
 			s.runSteps(ctx, t, steps)
 		}
 		steps := scenario.Steps
@@ -642,7 +668,17 @@ func (s *Suite) findStepDef(text string) (stepDef, error) {
 	return sd, nil
 }
 
-func (s *Suite) skipScenario(scenarioTags []*msgs.Tag) bool {
+func (s *Suite) shouldSkipFeatureOrRule(featureOrRuleTags []*msgs.Tag) bool {
+	for _, tag := range featureOrRuleTags {
+		if contains(s.options.ignoreTags, tag.Name) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func (s *Suite) shouldSkipScenario(scenarioTags []*msgs.Tag) bool {
 	for _, tag := range scenarioTags {
 		if contains(s.options.ignoreTags, tag.Name) {
 			return true
@@ -662,8 +698,13 @@ func (s *Suite) skipScenario(scenarioTags []*msgs.Tag) bool {
 	return true
 }
 
-func (s *Suite) getBackgroundSteps(bkg *msgs.Background) []*msgs.Step {
-	return bkg.Steps
+func (s *Suite) getBackgroundSteps(backgrounds []*msgs.Background) []*msgs.Step {
+	result := []*msgs.Step{}
+	for _, background := range backgrounds {
+		result = append(result, background.Steps...)
+	}
+
+	return result
 }
 
 // contains tells whether a contains x.


Test patch:
diff --git a/context_get_test.go b/context_get_test.go
index 432c8de..da1ea3f 100644
--- a/context_get_test.go
+++ b/context_get_test.go
@@ -1,4 +1,4 @@
-// Code generated .* DO NOT EDIT.
+// Code generated .* DO NOT EDIT.	
 package gobdd
 
 import "testing"
@@ -17,6 +17,7 @@ func TestContext_GetError(t *testing.T) {
 	}
 }
 
+
 func TestContext_GetString(t *testing.T) {
 	ctx := NewContext()
 	expected := string("example text")
@@ -45,7 +46,7 @@ func TestContext_GetString_WithDefaultValue(t *testing.T) {
 func TestContext_GetString_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetString("test", "example text", "example text")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetString should return an error")
 	}
 }
@@ -53,7 +54,7 @@ func TestContext_GetString_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testi
 func TestContext_GetString_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetString("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetString should return an error")
 	}
 }
@@ -86,7 +87,7 @@ func TestContext_GetInt_WithDefaultValue(t *testing.T) {
 func TestContext_GetInt_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt("test", 123, 123)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt should return an error")
 	}
 }
@@ -94,7 +95,7 @@ func TestContext_GetInt_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.
 func TestContext_GetInt_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt should return an error")
 	}
 }
@@ -127,7 +128,7 @@ func TestContext_GetInt8_WithDefaultValue(t *testing.T) {
 func TestContext_GetInt8_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt8("test", 123, 123)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt8 should return an error")
 	}
 }
@@ -135,7 +136,7 @@ func TestContext_GetInt8_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing
 func TestContext_GetInt8_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt8("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt8 should return an error")
 	}
 }
@@ -168,7 +169,7 @@ func TestContext_GetInt16_WithDefaultValue(t *testing.T) {
 func TestContext_GetInt16_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt16("test", 123, 123)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt16 should return an error")
 	}
 }
@@ -176,7 +177,7 @@ func TestContext_GetInt16_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testin
 func TestContext_GetInt16_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt16("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt16 should return an error")
 	}
 }
@@ -209,7 +210,7 @@ func TestContext_GetInt32_WithDefaultValue(t *testing.T) {
 func TestContext_GetInt32_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt32("test", 123, 123)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt32 should return an error")
 	}
 }
@@ -217,7 +218,7 @@ func TestContext_GetInt32_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testin
 func TestContext_GetInt32_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt32("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt32 should return an error")
 	}
 }
@@ -250,7 +251,7 @@ func TestContext_GetInt64_WithDefaultValue(t *testing.T) {
 func TestContext_GetInt64_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt64("test", 123, 123)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt64 should return an error")
 	}
 }
@@ -258,7 +259,7 @@ func TestContext_GetInt64_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testin
 func TestContext_GetInt64_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetInt64("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetInt64 should return an error")
 	}
 }
@@ -291,7 +292,7 @@ func TestContext_GetFloat32_WithDefaultValue(t *testing.T) {
 func TestContext_GetFloat32_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetFloat32("test", 123.5, 123.5)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetFloat32 should return an error")
 	}
 }
@@ -299,7 +300,7 @@ func TestContext_GetFloat32_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *test
 func TestContext_GetFloat32_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetFloat32("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetFloat32 should return an error")
 	}
 }
@@ -332,7 +333,7 @@ func TestContext_GetFloat64_WithDefaultValue(t *testing.T) {
 func TestContext_GetFloat64_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetFloat64("test", 123.5, 123.5)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetFloat64 should return an error")
 	}
 }
@@ -340,7 +341,7 @@ func TestContext_GetFloat64_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *test
 func TestContext_GetFloat64_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetFloat64("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetFloat64 should return an error")
 	}
 }
@@ -373,7 +374,7 @@ func TestContext_GetBool_WithDefaultValue(t *testing.T) {
 func TestContext_GetBool_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetBool("test", false, false)
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetBool should return an error")
 	}
 }
@@ -381,7 +382,8 @@ func TestContext_GetBool_ShouldReturnErrorWhenMoreThanOneDefaultValue(t *testing
 func TestContext_GetBool_ErrorOnNotFound(t *testing.T) {
 	ctx := NewContext()
 	_, err := ctx.GetBool("test")
-	if err == nil {
+	if err == nil  {
 		t.Error("the GetBool should return an error")
 	}
 }
+	
diff --git a/gobdd_test.go b/gobdd_test.go
index f3a4a3c..46c1366 100644
--- a/gobdd_test.go
+++ b/gobdd_test.go
@@ -17,7 +17,15 @@ func TestScenarios(t *testing.T) {
 	suite.AddRegexStep(compiled, add)
 	compiled = regexp.MustCompile(`the result should equal (\d+)`)
 	suite.AddRegexStep(compiled, check)
+	suite.Run()
+}
 
+func TestRule(t *testing.T) {
+	suite := NewSuite(t, WithFeaturesPath("features/example_rule.feature"))
+	compiled := regexp.MustCompile(`I add (\d+) and (\d+)`)
+	suite.AddRegexStep(compiled, add)
+	compiled = regexp.MustCompile(`the result should equal (\d+)`)
+	suite.AddRegexStep(compiled, check)
 	suite.Run()
 }
 
@@ -117,6 +125,8 @@ func TestStepFromExample(t *testing.T) {
 func TestBackground(t *testing.T) {
 	suite := NewSuite(t, WithFeaturesPath("features/background.feature"))
 	suite.AddStep(`I add (\d+) and (\d+)`, add)
+	suite.AddStep(`I concat word {word} and text {text}`, concat)
+	suite.AddStep(`the result should equal text {text}`, checkt)
 	suite.AddStep(`the result should equal (\d+)`, check)
 
 	suite.Run()
@@ -175,14 +185,14 @@ func TestWithAfterStep(t *testing.T) {
 	suite := NewSuite(t, WithFeaturesPath("features/background.feature"), WithAfterStep(func(ctx Context) {
 		c++
 
-		// feature should be *msgs.GherkinDocument_Feature
+		// feature should be *msgs.Feature
 		feature, err := ctx.Get(FeatureKey{})
 		require.NoError(t, err)
 		if _, ok := feature.(*msgs.Feature); !ok {
 			t.Errorf("expected feature but got %T", feature)
 		}
 
-		// scenario should be *msgs.GherkinDocument_Feature_Scenario
+		// scenario should be *msgs.Scenario
 		scenario, err := ctx.Get(ScenarioKey{})
 		require.NoError(t, err)
 		if _, ok := scenario.(*msgs.Scenario); !ok {
@@ -191,10 +201,12 @@ func TestWithAfterStep(t *testing.T) {
 	}))
 	suite.AddStep(`I add (\d+) and (\d+)`, add)
 	suite.AddStep(`the result should equal (\d+)`, check)
+	suite.AddStep(`I concat word {word} and text {text}`, concat)
+	suite.AddStep(`the result should equal text {text}`, checkt)
 
 	suite.Run()
 
-	if err := assert.Equals(2, c); err != nil {
+	if err := assert.Equals(6, c); err != nil {
 		t.Error(err)
 	}
 }
@@ -206,22 +218,19 @@ func TestWithBeforeStep(t *testing.T) {
 	}))
 	suite.AddStep(`I add (\d+) and (\d+)`, add)
 	suite.AddStep(`the result should equal (\d+)`, check)
+	suite.AddStep(`I concat word {word} and text {text}`, concat)
+	suite.AddStep(`the result should equal text {text}`, checkt)
 
 	suite.Run()
 
-	if err := assert.Equals(2, c); err != nil {
+	if err := assert.Equals(6, c); err != nil {
 		t.Error(err)
 	}
 }
 
 func TestIgnoredTags(t *testing.T) {
-	suite := NewSuite(t, WithFeaturesPath("features/ignored_tags.feature"), WithIgnoredTags("@ignore"))
-	suite.AddStep(`fail the test`, fail)
-	suite.Run()
-}
-
-func TestIgnorFeatureWithTags(t *testing.T) {
-	suite := NewSuite(t, WithFeaturesPath("features/ignored_feature_tags.feature"), WithIgnoredTags("@ignore"))
+	suite := NewSuite(t, WithFeaturesPath("features/ignored_*tags.feature"), WithIgnoredTags("@ignore"))
+	suite.AddStep(`the test should pass`, pass)
 	suite.AddStep(`fail the test`, fail)
 	suite.Run()
 }
diff --git a/steps_context_test.go b/steps_context_test.go
index 8bf175d..8eeb9a5 100644
--- a/steps_context_test.go
+++ b/steps_context_test.go
@@ -3,7 +3,7 @@ package gobdd_test
 import (
 	"testing"
 
-	"github.com/go-bdd/gobdd"
+	gobdd "github.com/go-bdd/gobdd"
 	"github.com/go-bdd/gobdd/context"
 )