From 2c669445bc1b454ae30f4baa1dac5b152780f3db Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Fri, 28 Oct 2022 12:39:10 -0500 Subject: [PATCH 1/9] Add support for Rule Backgrounds. Preliminary commit. Lacks tests. --- .../Generation/ScenarioPartHelper.cs | 53 +++++++++++++++++++ .../Generation/UnitTestFeatureGenerator.cs | 1 + .../Generation/UnitTestMethodGenerator.cs | 8 ++- .../TestClassGenerationContext.cs | 5 +- .../Features/Parser/RuleSupport.feature | 21 ++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs index 41ef85535..ed23e8778 100644 --- a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs +++ b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs @@ -50,6 +50,59 @@ public void SetupFeatureBackground(TestClassGenerationContext generationContext) backgroundMethod.Statements.AddRange(statements.ToArray()); } + #region Rule Background Support + + public void SetupRuleBackgroundsStatements(SpecFlowFeature feature, TestClassGenerationContext generationContext) + { + foreach (var ruleBackgroundDefinition in GetRulesWithBackgroundDefinitions(feature)) + { + SetupRuleBackground(generationContext, ruleBackgroundDefinition.Rule, ruleBackgroundDefinition.ScenarioDefinition); + } + } + + public void SetupRuleBackground(TestClassGenerationContext generationContext, Rule rule, StepsContainer background) + { + if (background == null) return; + + var ruleName = rule.Name; + + var statements = new List(); + using (new SourceLineScope(_specFlowConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, background.Location)) + { + foreach (var step in background.Steps) + { + GenerateStep(generationContext, statements, step, null); + } + } + + generationContext.RuleBackgroundStatements[ruleName] = statements; + + } + + public bool TryDoesThisScenarioBelongToARule(StepsContainer scenario, SpecFlowFeature feature, out Rule rule) + { + var scenarioName = scenario.Name; + foreach (var r in feature.Children.OfType()) + { + var scenarioBelongingToRule = r.Children.OfType().Where(sc => sc is not Background).Where(sc => sc.Name == scenarioName).FirstOrDefault(); + + if (scenarioBelongingToRule != null) + { + rule = r; + return true; + } + } + + rule = null; + return false; + } + + private IEnumerable GetRulesWithBackgroundDefinitions(SpecFlowFeature feature) + { + return feature.Children.OfType().SelectMany(rule => rule.Children.OfType().Where(child => child is Background).Select(sd => new ScenarioDefinitionInFeatureFile(sd, feature, rule))); + } + + #endregion public void GenerateStep(TestClassGenerationContext generationContext, List statements, Step gherkinStep, ParameterSubstitution paramToIdentifier) { diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs index 63109e31a..ae494570a 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs @@ -55,6 +55,7 @@ public CodeNamespace GenerateUnitTestFixture(SpecFlowDocument document, string t SetupScenarioStartMethod(generationContext); SetupScenarioInitializeMethod(generationContext); _scenarioPartHelper.SetupFeatureBackground(generationContext); + _scenarioPartHelper.SetupRuleBackgroundsStatements(feature, generationContext); SetupScenarioCleanupMethod(generationContext); SetupTestInitializeMethod(generationContext); diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs index c601e293e..d2858f92b 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs @@ -44,7 +44,7 @@ IEnumerable GetScenarioDefinitionsOfRule(IEnume .Concat(feature.Children.OfType().SelectMany(rule => GetScenarioDefinitionsOfRule(rule.Children, rule))); } - public void CreateUnitTests(SpecFlowFeature feature, TestClassGenerationContext generationContext) + public void CreateUnitTests(SpecFlowFeature feature, TestClassGenerationContext generationContext) { foreach (var scenarioDefinition in GetScenarioDefinitions(feature)) { @@ -265,6 +265,12 @@ internal void GenerateTestMethodBody(TestClassGenerationContext generationContex } } + if (_scenarioPartHelper.TryDoesThisScenarioBelongToARule(scenario, generationContext.Feature, out Rule rule)) + { + IList ruleBackgroundStatements; + var hasRuleBackground = generationContext.RuleBackgroundStatements.TryGetValue(rule.Name, out ruleBackgroundStatements); + if (hasRuleBackground) statementsWhenScenarioIsExecuted.AddRange(ruleBackgroundStatements); + } foreach (var scenarioStep in scenario.Steps) { diff --git a/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs b/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs index 5a6f5cd2f..546259268 100644 --- a/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs +++ b/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs @@ -22,6 +22,7 @@ public class TestClassGenerationContext public CodeMemberMethod ScenarioStartMethod { get; private set; } public CodeMemberMethod ScenarioCleanupMethod { get; private set; } public CodeMemberMethod FeatureBackgroundMethod { get; private set; } + public IDictionary> RuleBackgroundStatements { get; private set; } public CodeMemberField TestRunnerField { get; private set; } public bool GenerateRowTests { get; private set; } @@ -42,7 +43,8 @@ public TestClassGenerationContext( CodeMemberMethod scenarioStartMethod, CodeMemberMethod scenarioCleanupMethod, CodeMemberMethod featureBackgroundMethod, - bool generateRowTests) + bool generateRowTests, + [System.Runtime.InteropServices.Optional] IDictionary> ruleBackgroundStatements) { UnitTestGeneratorProvider = unitTestGeneratorProvider; Document = document; @@ -60,6 +62,7 @@ public TestClassGenerationContext( GenerateRowTests = generateRowTests; CustomData = new Dictionary(); + RuleBackgroundStatements = ruleBackgroundStatements ?? new Dictionary>(); } } } \ No newline at end of file diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature index 17309a782..e4b80210b 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature @@ -32,3 +32,24 @@ Scenario: Should be able to execute a simple passing scenario Then the execution summary should contain | Total | | 5 | + + + + Scenario: Should be able to execute scenarios in Rules that have backgrounds + Given there is a feature file in the project as + """ + Feature: Simple Feature + Rule: first rule + Background: first rule background + Given something + + Scenario: Scenario for the first rule + When I do something + + """ + Given all steps are bound and pass + When I execute the tests + Then the execution summary should contain + | Total | + | 1 | + From bb93147812e08c61102f0eb5036cf3c61a793716 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Sat, 29 Oct 2022 13:47:35 -0500 Subject: [PATCH 2/9] Add support for Rule Backgrounds.Revised Commit to simplify approach and code. Lacks tests. --- .../Generation/ScenarioPartHelper.cs | 25 ++++++++----------- .../Generation/UnitTestFeatureGenerator.cs | 1 - .../Generation/UnitTestMethodGenerator.cs | 7 +++--- .../TestClassGenerationContext.cs | 5 +--- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs index ed23e8778..151c7b7f1 100644 --- a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs +++ b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs @@ -51,20 +51,9 @@ public void SetupFeatureBackground(TestClassGenerationContext generationContext) } #region Rule Background Support - - public void SetupRuleBackgroundsStatements(SpecFlowFeature feature, TestClassGenerationContext generationContext) - { - foreach (var ruleBackgroundDefinition in GetRulesWithBackgroundDefinitions(feature)) - { - SetupRuleBackground(generationContext, ruleBackgroundDefinition.Rule, ruleBackgroundDefinition.ScenarioDefinition); - } - } - - public void SetupRuleBackground(TestClassGenerationContext generationContext, Rule rule, StepsContainer background) + private IEnumerable GenerateBackgroundStatements(TestClassGenerationContext generationContext, Rule rule, StepsContainer background) { - if (background == null) return; - - var ruleName = rule.Name; + if (background == null) return new List(); var statements = new List(); using (new SourceLineScope(_specFlowConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, background.Location)) @@ -75,8 +64,7 @@ public void SetupRuleBackground(TestClassGenerationContext generationContext, Ru } } - generationContext.RuleBackgroundStatements[ruleName] = statements; - + return statements; } public bool TryDoesThisScenarioBelongToARule(StepsContainer scenario, SpecFlowFeature feature, out Rule rule) @@ -101,7 +89,14 @@ private IEnumerable GetRulesWithBackgroundDefin { return feature.Children.OfType().SelectMany(rule => rule.Children.OfType().Where(child => child is Background).Select(sd => new ScenarioDefinitionInFeatureFile(sd, feature, rule))); } + + public IEnumerable GenerateBackgroundStatementsForRule(TestClassGenerationContext context, SpecFlowFeature feature, Rule rule) + { + var backgroundDefinition = GetRulesWithBackgroundDefinitions(feature).Where(rd => rd.Rule.Name == rule.Name).FirstOrDefault(); + if (backgroundDefinition == null) return new List(); + return GenerateBackgroundStatements(context, rule, backgroundDefinition.ScenarioDefinition); + } #endregion public void GenerateStep(TestClassGenerationContext generationContext, List statements, Step gherkinStep, ParameterSubstitution paramToIdentifier) diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs index ae494570a..63109e31a 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestFeatureGenerator.cs @@ -55,7 +55,6 @@ public CodeNamespace GenerateUnitTestFixture(SpecFlowDocument document, string t SetupScenarioStartMethod(generationContext); SetupScenarioInitializeMethod(generationContext); _scenarioPartHelper.SetupFeatureBackground(generationContext); - _scenarioPartHelper.SetupRuleBackgroundsStatements(feature, generationContext); SetupScenarioCleanupMethod(generationContext); SetupTestInitializeMethod(generationContext); diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs index d2858f92b..a949c20c7 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs @@ -44,7 +44,7 @@ IEnumerable GetScenarioDefinitionsOfRule(IEnume .Concat(feature.Children.OfType().SelectMany(rule => GetScenarioDefinitionsOfRule(rule.Children, rule))); } - public void CreateUnitTests(SpecFlowFeature feature, TestClassGenerationContext generationContext) + public void CreateUnitTests(SpecFlowFeature feature, TestClassGenerationContext generationContext) { foreach (var scenarioDefinition in GetScenarioDefinitions(feature)) { @@ -267,9 +267,8 @@ internal void GenerateTestMethodBody(TestClassGenerationContext generationContex if (_scenarioPartHelper.TryDoesThisScenarioBelongToARule(scenario, generationContext.Feature, out Rule rule)) { - IList ruleBackgroundStatements; - var hasRuleBackground = generationContext.RuleBackgroundStatements.TryGetValue(rule.Name, out ruleBackgroundStatements); - if (hasRuleBackground) statementsWhenScenarioIsExecuted.AddRange(ruleBackgroundStatements); + IEnumerable ruleBackgroundStatements = _scenarioPartHelper.GenerateBackgroundStatementsForRule(generationContext, generationContext.Feature, rule); + statementsWhenScenarioIsExecuted.AddRange(ruleBackgroundStatements); } foreach (var scenarioStep in scenario.Steps) diff --git a/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs b/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs index 546259268..5a6f5cd2f 100644 --- a/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs +++ b/TechTalk.SpecFlow.Generator/TestClassGenerationContext.cs @@ -22,7 +22,6 @@ public class TestClassGenerationContext public CodeMemberMethod ScenarioStartMethod { get; private set; } public CodeMemberMethod ScenarioCleanupMethod { get; private set; } public CodeMemberMethod FeatureBackgroundMethod { get; private set; } - public IDictionary> RuleBackgroundStatements { get; private set; } public CodeMemberField TestRunnerField { get; private set; } public bool GenerateRowTests { get; private set; } @@ -43,8 +42,7 @@ public TestClassGenerationContext( CodeMemberMethod scenarioStartMethod, CodeMemberMethod scenarioCleanupMethod, CodeMemberMethod featureBackgroundMethod, - bool generateRowTests, - [System.Runtime.InteropServices.Optional] IDictionary> ruleBackgroundStatements) + bool generateRowTests) { UnitTestGeneratorProvider = unitTestGeneratorProvider; Document = document; @@ -62,7 +60,6 @@ public TestClassGenerationContext( GenerateRowTests = generateRowTests; CustomData = new Dictionary(); - RuleBackgroundStatements = ruleBackgroundStatements ?? new Dictionary>(); } } } \ No newline at end of file From a6d4664aff104cb00dc9dd4185722027985ca444 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 2 Nov 2022 14:22:14 -0500 Subject: [PATCH 3/9] Refactored signature of method: UnitTestMethodGenerator.GenerateTestMethodBody to accept a ScenarioDefinitionInFeatureFile instead of an AST Scenario. This allows simpler tracking of the relationship between Scenario and Rule. Refactored Rule Background support in ScenarioPartHelper to simplify the code and accept changes suggested during PR review. --- .../Generation/ScenarioPartHelper.cs | 50 +++++++------------ .../Generation/UnitTestMethodGenerator.cs | 12 ++--- 2 files changed, 24 insertions(+), 38 deletions(-) diff --git a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs index 151c7b7f1..c5e5c53b4 100644 --- a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs +++ b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs @@ -51,7 +51,25 @@ public void SetupFeatureBackground(TestClassGenerationContext generationContext) } #region Rule Background Support - private IEnumerable GenerateBackgroundStatements(TestClassGenerationContext generationContext, Rule rule, StepsContainer background) + + public void GenerateRuleBackgroundStepsApplicableForThisScenario(TestClassGenerationContext generationContext, ScenarioDefinitionInFeatureFile scenarioDefinition, List statementsWhenScenarioIsExecuted) + { + if (scenarioDefinition.Rule != null) + { + var rule = scenarioDefinition.Rule; + IEnumerable ruleBackgroundStatements = GenerateBackgroundStatementsForRule(generationContext, rule); + statementsWhenScenarioIsExecuted.AddRange(ruleBackgroundStatements); + } + } + + private IEnumerable GenerateBackgroundStatementsForRule(TestClassGenerationContext context, Rule rule) + { + var background = rule.Children.OfType().FirstOrDefault(); + + return GenerateBackgroundStatements(context, background); + } + + private IEnumerable GenerateBackgroundStatements(TestClassGenerationContext generationContext, StepsContainer background) { if (background == null) return new List(); @@ -67,36 +85,6 @@ private IEnumerable GenerateBackgroundStatements(TestClassGenerat return statements; } - public bool TryDoesThisScenarioBelongToARule(StepsContainer scenario, SpecFlowFeature feature, out Rule rule) - { - var scenarioName = scenario.Name; - foreach (var r in feature.Children.OfType()) - { - var scenarioBelongingToRule = r.Children.OfType().Where(sc => sc is not Background).Where(sc => sc.Name == scenarioName).FirstOrDefault(); - - if (scenarioBelongingToRule != null) - { - rule = r; - return true; - } - } - - rule = null; - return false; - } - - private IEnumerable GetRulesWithBackgroundDefinitions(SpecFlowFeature feature) - { - return feature.Children.OfType().SelectMany(rule => rule.Children.OfType().Where(child => child is Background).Select(sd => new ScenarioDefinitionInFeatureFile(sd, feature, rule))); - } - - public IEnumerable GenerateBackgroundStatementsForRule(TestClassGenerationContext context, SpecFlowFeature feature, Rule rule) - { - var backgroundDefinition = GetRulesWithBackgroundDefinitions(feature).Where(rd => rd.Rule.Name == rule.Name).FirstOrDefault(); - if (backgroundDefinition == null) return new List(); - - return GenerateBackgroundStatements(context, rule, backgroundDefinition.ScenarioDefinition); - } #endregion public void GenerateStep(TestClassGenerationContext generationContext, List statements, Step gherkinStep, ParameterSubstitution paramToIdentifier) diff --git a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs index a949c20c7..fca3e76fa 100644 --- a/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs +++ b/TechTalk.SpecFlow.Generator/Generation/UnitTestMethodGenerator.cs @@ -201,7 +201,7 @@ private void GenerateTestBody( GenerateScenarioInitializeCall(generationContext, scenarioDefinition, testMethod); - GenerateTestMethodBody(generationContext, scenarioDefinition, testMethod, paramToIdentifier, feature); + GenerateTestMethodBody(generationContext, scenarioDefinitionInFeatureFile, testMethod, paramToIdentifier, feature); GenerateScenarioCleanupMethodCall(generationContext, testMethod); } @@ -238,8 +238,10 @@ private void AddVariableForArguments(CodeMemberMethod testMethod, ParameterSubst } } - internal void GenerateTestMethodBody(TestClassGenerationContext generationContext, StepsContainer scenario, CodeMemberMethod testMethod, ParameterSubstitution paramToIdentifier, SpecFlowFeature feature) + internal void GenerateTestMethodBody(TestClassGenerationContext generationContext, ScenarioDefinitionInFeatureFile scenarioDefinition, CodeMemberMethod testMethod, ParameterSubstitution paramToIdentifier, SpecFlowFeature feature) { + var scenario = scenarioDefinition.Scenario; + var statementsWhenScenarioIsIgnored = new CodeStatement[] { new CodeExpressionStatement(CreateTestRunnerSkipScenarioCall()) }; var callScenarioStartMethodExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), generationContext.ScenarioStartMethod.Name); @@ -265,11 +267,7 @@ internal void GenerateTestMethodBody(TestClassGenerationContext generationContex } } - if (_scenarioPartHelper.TryDoesThisScenarioBelongToARule(scenario, generationContext.Feature, out Rule rule)) - { - IEnumerable ruleBackgroundStatements = _scenarioPartHelper.GenerateBackgroundStatementsForRule(generationContext, generationContext.Feature, rule); - statementsWhenScenarioIsExecuted.AddRange(ruleBackgroundStatements); - } + _scenarioPartHelper.GenerateRuleBackgroundStepsApplicableForThisScenario(generationContext, scenarioDefinition, statementsWhenScenarioIsExecuted); foreach (var scenarioStep in scenario.Steps) { From 565afffaa3d0eef01b05fdb9fbc0fa187602ee29 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 2 Nov 2022 14:50:19 -0500 Subject: [PATCH 4/9] Adding a feature file in the Specs tests to demonstrate a Feature with multiple Rules, each having a Background; the Background steps should only be executed for the Scenario(s) within their respective Rule. --- .../Execution/RuleBackgroundExecution.feature | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature new file mode 100644 index 000000000..1fbeeaa27 --- /dev/null +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature @@ -0,0 +1,116 @@ +Feature: Rule Background Steps Execution + + Scenario: Should be able to execute scenarios in Rules that have backgrounds + Given the following binding class + """ + using TechTalk.SpecFlow; + + [Binding] + public class RuleSteps1 + { + + [Given("something first as background")] + public void GivenSomethingFirst() { + global::Log.LogStep(); + } + } + """ + Given there is a feature file in the project as + """ + Feature: Simple Feature + Rule: first rule + Background: first rule background + Given something first as background + + Scenario: Scenario for the first rule + When I do something + """ + + + Given all steps are bound and pass + When I execute the tests + Then the binding method 'GivenSomethingFirst' is executed + + + Scenario: Should be able to execute backgrounds from multiple Rules + Given the following binding class + """ + using TechTalk.SpecFlow; + + [Binding] + public class RuleSteps2 + { + private bool first_background_executed = false; + private bool second_background_executed = false; + + [Given("a first thing as background")] + public void GivenaFirst() { + global::Log.LogStep(); + first_background_executed = true; + } + + [Then("the first of two background item was executed")] + public void ThenFirstOfTwoWasExecuted() { + global::Log.LogStep(); + if (!first_background_executed) { + throw new ApplicationException("First Background Step should have been executed"); + } + } + + [Then("the first background item was not executed")] + public void ThenFirstWasNotExecuted() { + global::Log.LogStep(); + if (first_background_executed) { + throw new ApplicationException("First Background Step should not have been executed"); + } + } + + [Given("something second as background")] + public void GivenSomethingSecond() { + global::Log.LogStep(); + second_background_executed = true; + } + + [Then("the second background item was executed")] + public void ThenSecondWasExecuted() { + global::Log.LogStep(); + if (!second_background_executed) { + throw new ApplicationException("Second Background Step should have been executed"); + } + } + + [Then("the second background item was not executed")] + public void ThenSecondWasNotExecuted() { + global::Log.LogStep(); + if (second_background_executed) { + throw new ApplicationException("Second Background Step should not have been executed"); + } + } + } + """ + Given there is a feature file in the project as + """ + Feature: Simple Feature + Rule: first rule + Background: first rule background + Given a first thing as background + + Scenario: Scenario for the first rule + When I do something + Then the first of two background item was executed + And the second background item was not executed + + Rule: second rule + Background: bg for second rule + Given something second as background + + Scenario:Scenario for the second rule + When I do something + Then the second background item was executed + And the first background item was not executed + + """ + + Given all steps are bound and pass + When I execute the tests + Then all tests should pass From 85f54d52db5e05be0369522ec3eeccdc6774b815 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:04:39 -0500 Subject: [PATCH 5/9] Further simplification of Rule support in SPH by merging two private methods together. --- .../Generation/ScenarioPartHelper.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs index c5e5c53b4..a8fa70c1b 100644 --- a/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs +++ b/TechTalk.SpecFlow.Generator/Generation/ScenarioPartHelper.cs @@ -66,19 +66,14 @@ private IEnumerable GenerateBackgroundStatementsForRule(TestClass { var background = rule.Children.OfType().FirstOrDefault(); - return GenerateBackgroundStatements(context, background); - } - - private IEnumerable GenerateBackgroundStatements(TestClassGenerationContext generationContext, StepsContainer background) - { if (background == null) return new List(); var statements = new List(); - using (new SourceLineScope(_specFlowConfiguration, _codeDomHelper, statements, generationContext.Document.SourceFilePath, background.Location)) + using (new SourceLineScope(_specFlowConfiguration, _codeDomHelper, statements, context.Document.SourceFilePath, background.Location)) { foreach (var step in background.Steps) { - GenerateStep(generationContext, statements, step, null); + GenerateStep(context, statements, step, null); } } From c5348725cbfeff67f6adb5b0ff622b70fb60e59f Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:03:31 -0600 Subject: [PATCH 6/9] Corrected feature's binding code. Tests now pass. --- .../Execution/RuleBackgroundExecution.feature | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature index 1fbeeaa27..a7961bc3d 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature @@ -1,3 +1,4 @@ +@gherkin6 Feature: Rule Background Steps Execution Scenario: Should be able to execute scenarios in Rules that have backgrounds @@ -13,6 +14,11 @@ Feature: Rule Background Steps Execution public void GivenSomethingFirst() { global::Log.LogStep(); } + + [When("I do something")] + public void WhenSomethingDone() { + global::Log.LogStep(); + } } """ Given there is a feature file in the project as @@ -27,7 +33,6 @@ Feature: Rule Background Steps Execution """ - Given all steps are bound and pass When I execute the tests Then the binding method 'GivenSomethingFirst' is executed @@ -86,6 +91,12 @@ Feature: Rule Background Steps Execution throw new ApplicationException("Second Background Step should not have been executed"); } } + + [When("I do something")] + public void WhenSomethingDone() { + global::Log.LogStep(); + } + } """ Given there is a feature file in the project as @@ -111,6 +122,5 @@ Feature: Rule Background Steps Execution """ - Given all steps are bound and pass When I execute the tests Then all tests should pass From fbff15f1eb41a3e7365e73b5207751d29b629c46 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Tue, 8 Nov 2022 14:07:31 -0600 Subject: [PATCH 7/9] Updating changelog --- changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.txt b/changelog.txt index 1eb8fb29a..0f72b40ab 100644 --- a/changelog.txt +++ b/changelog.txt @@ -12,6 +12,7 @@ Features: + Support Rule tags (can be used for hook filters, scoping and access through 'ScenarioInfo.CombinedTags') + Support for async step argument transformations. Fixes #2230 + Support for ValueTask and ValueTask binding methods (step definitions, hooks, step argument transformations) ++ Rules now support Background blocks Changes: + Existing step definition expressions detected to be either regular or cucumber expression. Check https://docs.specflow.org/projects/specflow/en/latest/Guides/UpgradeSpecFlow3To4.html for potential upgrade issues. From 4a8487487f1877334116e4e66c072fdfe0bc32a3 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 9 Nov 2022 09:11:29 -0600 Subject: [PATCH 8/9] Reverting this feature back to the version present in SpecFlowOSS/SpecFlow. --- .../Features/Parser/RuleSupport.feature | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature index e4b80210b..236d745e9 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Parser/RuleSupport.feature @@ -31,25 +31,4 @@ Scenario: Should be able to execute a simple passing scenario When I execute the tests Then the execution summary should contain | Total | - | 5 | - - - - Scenario: Should be able to execute scenarios in Rules that have backgrounds - Given there is a feature file in the project as - """ - Feature: Simple Feature - Rule: first rule - Background: first rule background - Given something - - Scenario: Scenario for the first rule - When I do something - - """ - Given all steps are bound and pass - When I execute the tests - Then the execution summary should contain - | Total | - | 1 | - + | 5 | \ No newline at end of file From ab1a52e0b01967a8f7f48cc110eda664af206ae5 Mon Sep 17 00:00:00 2001 From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com> Date: Wed, 9 Nov 2022 10:13:04 -0600 Subject: [PATCH 9/9] Refactored/revised to improve readabliity and formatting --- .../Execution/RuleBackgroundExecution.feature | 190 ++++++++++-------- 1 file changed, 105 insertions(+), 85 deletions(-) diff --git a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature index a7961bc3d..ab75a588c 100644 --- a/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature +++ b/Tests/TechTalk.SpecFlow.Specs/Features/Execution/RuleBackgroundExecution.feature @@ -1,124 +1,144 @@ @gherkin6 -Feature: Rule Background Steps Execution +Feature: Rule Background Steps Are Added to Scenarios Belonging to Rules Scenario: Should be able to execute scenarios in Rules that have backgrounds Given the following binding class - """ - using TechTalk.SpecFlow; + """ + using TechTalk.SpecFlow; - [Binding] - public class RuleSteps1 - { + [Binding] + public class RuleStepsForFirstScenario + { - [Given("something first as background")] - public void GivenSomethingFirst() { - global::Log.LogStep(); - } + [Given("a background step")] + public void TheBackgroundStep() + { + global::Log.LogStep(); + } - [When("I do something")] - public void WhenSomethingDone() { - global::Log.LogStep(); + [When("I do something")] + public void WhenSomethingDone() + { + global::Log.LogStep(); + } } - } - """ + """ + Given there is a feature file in the project as """ Feature: Simple Feature - Rule: first rule - Background: first rule background - Given something first as background + Rule: A Rule + Background: rule background + Given a background step - Scenario: Scenario for the first rule - When I do something + Scenario: Scenario for the first rule + When I do something """ - When I execute the tests - Then the binding method 'GivenSomethingFirst' is executed + Then the binding method 'TheBackgroundStep' is executed Scenario: Should be able to execute backgrounds from multiple Rules Given the following binding class """ - using TechTalk.SpecFlow; - - [Binding] - public class RuleSteps2 - { - private bool first_background_executed = false; - private bool second_background_executed = false; - - [Given("a first thing as background")] - public void GivenaFirst() { - global::Log.LogStep(); - first_background_executed = true; + using System; + using TechTalk.SpecFlow; + + internal class StepInvocationTracker + { + private bool first_background_step_executed = false; + private bool second_backgroun_step_executed = false; + + public void MarkFirstStepAsExecuted() => first_background_step_executed = true; + + public void MarkSecondStepAsExecuted() => second_backgroun_step_executed = true; + + public bool WasFirstStepInvoked => first_background_step_executed; + public bool WasSecondStepInvoked => second_backgroun_step_executed; + } - [Then("the first of two background item was executed")] - public void ThenFirstOfTwoWasExecuted() { - global::Log.LogStep(); - if (!first_background_executed) { - throw new ApplicationException("First Background Step should have been executed"); + [Binding] + public class RuleStepsForFeatureContainingMultipleRules + { + + private StepInvocationTracker invocationTracker = new StepInvocationTracker(); + + [Given("a background step for the first rule")] + public void GivenaFirst() + { + global::Log.LogStep(); + invocationTracker.MarkFirstStepAsExecuted(); } - } - [Then("the first background item was not executed")] - public void ThenFirstWasNotExecuted() { - global::Log.LogStep(); - if (first_background_executed) { - throw new ApplicationException("First Background Step should not have been executed"); + [Then("the first background step was executed")] + public void ThenFirstOfTwoWasExecuted() + { + global::Log.LogStep(); + if (!invocationTracker.WasFirstStepInvoked) + { + throw new ApplicationException("First Background Step should have been executed"); + } } - } - [Given("something second as background")] - public void GivenSomethingSecond() { - global::Log.LogStep(); - second_background_executed = true; - } + [Then("the step from the first background was not executed")] + public void ThenFirstWasNotExecuted() + { + global::Log.LogStep(); + if (invocationTracker.WasFirstStepInvoked) + { + throw new ApplicationException("First Background Step should not have been executed"); + } + } - [Then("the second background item was executed")] - public void ThenSecondWasExecuted() { - global::Log.LogStep(); - if (!second_background_executed) { - throw new ApplicationException("Second Background Step should have been executed"); + [Given("a background step for the second rule")] + public void GivenSecondBackgroundStepExecuted() + { + global::Log.LogStep(); + invocationTracker.MarkSecondStepAsExecuted(); } - } - [Then("the second background item was not executed")] - public void ThenSecondWasNotExecuted() { - global::Log.LogStep(); - if (second_background_executed) { - throw new ApplicationException("Second Background Step should not have been executed"); + [Then("the second background step was executed")] + public void ThenSecondWasExecuted() + { + global::Log.LogStep(); + if (!invocationTracker.WasSecondStepInvoked) + { + throw new ApplicationException("Second Background Step should have been executed"); + } } - } - [When("I do something")] - public void WhenSomethingDone() { - global::Log.LogStep(); + [Then("the step from the second background was not executed")] + public void ThenSecondWasNotExecuted() + { + global::Log.LogStep(); + if (invocationTracker.WasSecondStepInvoked) + { + throw new ApplicationException("Second Background Step should not have been executed"); + } + } } - - } """ Given there is a feature file in the project as """ - Feature: Simple Feature - Rule: first rule - Background: first rule background - Given a first thing as background + Feature: A Feature with multiple Rules, each with their own Backgrounds + + Rule: first Rule + Background: first Rule's background + Given a background step for the first rule - Scenario: Scenario for the first rule - When I do something - Then the first of two background item was executed - And the second background item was not executed - - Rule: second rule - Background: bg for second rule - Given something second as background - - Scenario:Scenario for the second rule - When I do something - Then the second background item was executed - And the first background item was not executed + Scenario: Scenario for the first rule + Then the first background step was executed + And the step from the second background was not executed + + Rule: second Rule + Background: second Rule's background + Given a background step for the second rule + + Scenario:Scenario for the second rule + Then the second background step was executed + And the step from the first background was not executed """