Skip to content

Commit

Permalink
#23: Added support of Gherkin Background in BDD framework
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcinCelej committed Apr 30, 2023
1 parent 4855920 commit 26e58aa
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 53 deletions.
1 change: 1 addition & 0 deletions Behaviours/Synergy.Behaviours.Testing/Feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public abstract class Feature<TFeature> : IFeature
{
public virtual TFeature Background() => Self;
public virtual TFeature Given() => Self;
public virtual TFeature When() => Self;
public virtual TFeature Then() => Self;
Expand Down
136 changes: 102 additions & 34 deletions Behaviours/Synergy.Behaviours.Testing/FeatureGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static void Generate<TBehaviour>(
}

public static string Generate<TBehaviour>(
this TBehaviour feature,
this TBehaviour featureClass,
string from,
bool withMoreover = false,
string[]? include = null,
Expand All @@ -31,14 +31,14 @@ public static string Generate<TBehaviour>(
)
{
StringBuilder code = new StringBuilder();
string className = feature.GetType().Name;
string className = featureClass.GetType().Name;
var gherkinPath = Path.Combine(Path.GetDirectoryName(callerFilePath), from);
var gherkins = File.ReadAllLines(gherkinPath);

code.AppendLine("// <auto-generated />");
code.AppendLine("using System.CodeDom.Compiler;");
code.AppendLine();
code.AppendLine($"namespace {feature.GetType().Namespace};");
code.AppendLine($"namespace {featureClass.GetType().Namespace};");
code.AppendLine();
code.AppendLine($"[GeneratedCode(\"{typeof(FeatureGenerator).Assembly.FullName}\", \"{typeof(FeatureGenerator).Assembly.GetName().Version.ToString()}\")]");
code.AppendLine($"public partial class {className}");
Expand All @@ -53,14 +53,18 @@ public static string Generate<TBehaviour>(
bool includeScenario = ResetInclude();
List<string>? tags = null;
int lineNo = 0;
string? backgroundMethod = null;
string? backgroundStarted = null;
string? featureName = null;
string? ruleName = null;

foreach (var line in gherkins)
{
lineNo++;
if (line.Contains("#"))
{
tags = null;
includeScenario = ResetInclude();
//scenarioMethod = Moreover();
code.AppendLine(line.Replace("#", "//"));
continue;
}
Expand All @@ -69,15 +73,43 @@ public static string Generate<TBehaviour>(
{
tags = null;
includeScenario = ResetInclude();
scenarioMethod = CloseScenario();

continue;
}

var feature = Regex.Match(line, "\\s*Feature\\: (.*)");
if (feature.Success)
{
featureName = feature.Groups[1]
.Value;
continue;
}

var rule = Regex.Match(line, "\\s*Rule\\: (.*)");
if (rule.Success)
{
CloseBackground();
CloseScenario();

ruleName = rule.Groups[1]
.Value;

code.AppendLine($" // {line.Trim()}");
code.AppendLine();

continue;
}

var background = Regex.Match(line, "\\s*Background\\: (.*)");
var background = Regex.Match(line, "\\s*Background\\:");
if (background.Success)
{
throw new NotSupportedException($"Background keyword is not supported\nLine {lineNo}: {line.Trim()}");
CloseScenario();

backgroundMethod = FeatureGenerator.ToMethod(ruleName ?? featureName ?? "Feature") + "Background";
backgroundStarted = backgroundMethod;
code.AppendLine($" private {className} {backgroundMethod}() // {line.Trim()}");
code.Append($" => ");
continue;
}

var outline = Regex.Match(line, "\\s*Scenario (Outline|Template)\\: (.*)");
Expand All @@ -92,17 +124,10 @@ public static string Generate<TBehaviour>(
throw new NotSupportedException($"Examples keyword is not supported\nLine {lineNo}: {line.Trim()}");
}

// if (line.TrimStart().StartsWithAny("scenario", "rule", "background", "example", "@") &&
// scenarioMethod != null
// )
// {
// tags = null;
// includeScenario = ResetInclude();
// scenarioMethod = CloseScenario();
// }

if (line.Trim().StartsWith("@"))
{
CloseScenario();

tags = Regex.Matches(line, "\\@\\w+")
.Select(m=>m.Value).ToList();

Expand All @@ -116,74 +141,117 @@ public static string Generate<TBehaviour>(

// if (include)
// code.AppendLine($" [Xunit.Trait({string.Join(", ", tags.Select(t => "\"" + t.TrimStart('@') + "\""))})]");
continue;
}

if (includeScenario == false)
if (includeScenario == false && backgroundStarted == null)
continue;

// TODO: add Rule handling

// TODO: Marcin Celej [from: Marcin Celej on: 08-04-2023]: Add 'Background' handling - generate method with background steps and call it in each following scenario

var scenario = Regex.Match(line, "\\s*Scenario\\: (.*)");
if (scenario.Success)
{
CloseBackground();
CloseScenario();

scenarioMethod = FeatureGenerator.ToMethod(scenario.Groups[1]
.Value);
code.AppendLine(" [Xunit.Fact]");
if (tags != null)
code.AppendLine($" // {String.Join(" ", tags)}");
code.AppendLine($" public void {scenarioMethod}() // {line.Trim()}");
code.Append($" => ");
code.Append($" => {nameof(Feature<object>.Background)}()");
if (backgroundMethod == null)
code.AppendLine();
else
{
code.AppendLine($".{backgroundMethod}()");
}
continue;
}

var given = Regex.Match(line, "\\s*Given (.*)");
if (given.Success)
code.AppendLine($" Given().{FeatureGenerator.ToMethod(given.Groups[1].Value)}() // {line.Trim()}");
{
if (backgroundStarted == null)
{
code.Append($" .");
}
else
{
code.Append($" ");
}

code.AppendLine($"{nameof(Feature<object>.Given)}().{FeatureGenerator.ToMethod(given.Groups[1].Value)}() // {line.Trim()}");
continue;
}

var and = Regex.Match(line, "\\s*And (.*)");
if (and.Success)
code.AppendLine($" .And().{FeatureGenerator.ToMethod(and.Groups[1].Value)}() // {line.Trim()}");
{
code.AppendLine($" .{nameof(Feature<object>.And)}().{FeatureGenerator.ToMethod(and.Groups[1].Value)}() // {line.Trim()}");
continue;
}

var asterisk = Regex.Match(line, "\\s*\\* (.*)");
if (asterisk.Success)
code.AppendLine($" .And().{FeatureGenerator.ToMethod(asterisk.Groups[1].Value)}() // {line.Trim()}");
{
code.AppendLine($" .{nameof(Feature<object>.And)}().{FeatureGenerator.ToMethod(asterisk.Groups[1].Value)}() // {line.Trim()}");
continue;
}

var but = Regex.Match(line, "\\s*But (.*)");
if (but.Success)
code.AppendLine($" .But().{FeatureGenerator.ToMethod(but.Groups[1].Value)}() // {line.Trim()}");
{
code.AppendLine($" .{nameof(Feature<object>.But)}().{FeatureGenerator.ToMethod(but.Groups[1].Value)}() // {line.Trim()}");
continue;
}

var when = Regex.Match(line, "\\s*When (.*)");
if (when.Success)
code.AppendLine($" .When().{FeatureGenerator.ToMethod(when.Groups[1].Value)}() // {line.Trim()}");
{
code.AppendLine($" .{nameof(Feature<object>.When)}().{FeatureGenerator.ToMethod(when.Groups[1].Value)}() // {line.Trim()}");
continue;
}

var then = Regex.Match(line, "\\s*Then (.*)");
if (then.Success)
code.AppendLine($" .Then().{FeatureGenerator.ToMethod(then.Groups[1].Value)}() // {line.Trim()}");
{
code.AppendLine($" .{nameof(Feature<object>.Then)}().{FeatureGenerator.ToMethod(then.Groups[1].Value)}() // {line.Trim()}");
continue;
}
}

CloseBackground();
CloseScenario();

// code.AppendLine();
// code.AppendLine($" partial class {featureClass}");
// code.AppendLine(" {");
// code.AppendLine(" }");
code.AppendLine("}");

return code.ToString();

string? CloseScenario()
void CloseScenario()
{
if (scenarioMethod != null)
{
if (withMoreover)
code.AppendLine($" .Moreover().Verify{scenarioMethod}();");
else
code.AppendLine($" ;");

code.AppendLine();
}

scenarioMethod = null;
return scenarioMethod;
}

void CloseBackground()
{
if (backgroundStarted != null)
{
code.AppendLine($" ;");
code.AppendLine();
}

backgroundStarted = null;
}

bool ResetInclude()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,37 @@ namespace Synergy.Behaviours.Tests.Samples;
[GeneratedCode("Synergy.Behaviours.Testing, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "1.0.0.0")]
public partial class CalculatorFeature
{
private CalculatorFeature CalculatorBackground() // Background:
=> Given().UserOpenedCalculator() // Given User opened calculator
;



// Rule: Adding numbers

[Xunit.Fact]
// @Add
public void AddTwoNumbers() // Scenario: Add two numbers
=> Given().TheFirstNumberIs50() // Given the first number is 50
.And().TheSecondNumberIs70() // And the second number is 70
.When().TheTwoNumbersAreAdded() // When the two numbers are added
.Then().TheResultShouldBe120() // Then the result should be 120
=> Background().CalculatorBackground()
.Given().TheFirstNumberIs50() // Given the first number is 50
.And().TheSecondNumberIs70() // And the second number is 70
.When().TheTwoNumbersAreAdded() // When the two numbers are added
.Then().TheResultShouldBe120() // Then the result should be 120
;

[Xunit.Fact]
// @Add
public void AddTwoNumbersInDifferentWay() // Scenario: Add two numbers in different way
=> Given().TwoNumbers() // Given Two numbers:
.And().TheFirstNumberIs50() // * the first number is 50
.And().TheSecondNumberIs70() // * the second number is 70
.When().TheTwoNumbersAreAdded() // When the two numbers are added
.Then().TheResultShouldBe120() // Then the result should be 120
=> Background().CalculatorBackground()
.Given().TwoNumbers() // Given Two numbers:
.And().TheFirstNumberIs50() // * the first number is 50
.And().TheSecondNumberIs70() // * the second number is 70
.When().TheTwoNumbersAreAdded() // When the two numbers are added
.Then().TheResultShouldBe120() // Then the result should be 120
;

// Rule: Subtracting numbers

// Rule: Dividing numbers




// Rule: Multiplying numbers

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@ public async Task GenerateFeature()
//exclude: new[] { "@Divide" }
);

await Verifier.Verify(code, "cs").UseFileName("Calculator.Feature");
await Verifier
.Verify(code, "cs")
.UseFileName("Calculator.Feature");
}

private CalculatorFeature UserOpenedCalculator()
{
return this;
}

private int _firstNumber;

private CalculatorFeature TheFirstNumberIs50()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ Feature: Calculator
Link to a feature: [Calculator](SpecFlowCalculator.Specs/Features/Calculator.feature)
***Further read***: **[Learn more about how to generate Living Documentation](https://docs.specflow.org/projects/specflow-livingdoc/en/latest/LivingDocGenerator/Generating-Documentation.html)**

Background:
Given User opened calculator

Rule: Adding numbers

@Add
Scenario: Add two numbers
Given the first number is 50
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,13 @@
</None>
</ItemGroup>

<ItemGroup>
<Compile Update="Samples\Calculator.Feature.verified.cs">
<DependentUpon>Calculator.feature</DependentUpon>
</Compile>
<Compile Update="Samples\Calculator.Steps.cs">
<DependentUpon>Calculator.feature</DependentUpon>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Technical Debt for Synergy.Contracts

Total: 3
Total: 1

## [FeatureGenerator.cs](../../Synergy.Behaviours.Testing/FeatureGenerator.cs)
- TODO: Support tags above the feature
- TODO: add Rule handling
- TODO: Marcin Celej [from: Marcin Celej on: 08-04-2023]: Add 'Background' handling - generate method with background steps and call it in each following scenario

0 comments on commit 26e58aa

Please sign in to comment.