Skip to content

Commit

Permalink
Refactored Test Class generation to match Reqnroll v2.2 changes in Fe…
Browse files Browse the repository at this point in the history
…ature class start-up. Modified FeatureInfo constructor to accept the FeatureMessages object as optional parameter. Had to modify a Runtime test to accomodate that change.

Also changed Generation such that we're more thoroughly emitting the 'global::' prefix on Reqnroll types (for Messages related types).
Marked the Non-Compliant CCK test scenarios as [Ignore].
  • Loading branch information
clrudolphi committed Nov 7, 2024
1 parent 77d8333 commit 452ac85
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 71 deletions.
117 changes: 49 additions & 68 deletions Reqnroll.Generator/Generation/UnitTestFeatureGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class UnitTestFeatureGenerator : IFeatureGenerator
private readonly UnitTestMethodGenerator _unitTestMethodGenerator;
private readonly LinePragmaHandler _linePragmaHandler;
private readonly ICucumberConfiguration _cucumberConfiguration;
private CodeMemberMethod _cucumberMessagesInitializeMethod;

public UnitTestFeatureGenerator(
IUnitTestGeneratorProvider testGeneratorProvider,
Expand Down Expand Up @@ -197,6 +198,7 @@ private void DeclareFeatureInfoMember(TestClassGenerationContext generationConte
new CodeFieldReferenceExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(ProgrammingLanguage))),
_codeDomHelper.TargetLanguage.ToString()),
new CodeMethodInvokeExpression(null, _cucumberMessagesInitializeMethod.Name),
new CodeFieldReferenceExpression(null, GeneratorConstants.FEATURE_TAGS_VARIABLE_NAME));

generationContext.TestClass.Members.Add(featureInfoField);
Expand All @@ -213,32 +215,53 @@ private void DeclareFeatureMessagesFactoryMembers(TestClassGenerationContext gen
CodeDelegateCreateExpression picklesFunc;

string sourceFileLocation;
sourceFileLocation = Path.Combine(generationContext.Document.DocumentLocation.FeatureFolderPath, generationContext.Document.DocumentLocation.SourceFilePath);

// Adding three static methods to the class: one each as Factory methods for source, gherkinDocument, and pickles Messages
// Bodies of these methods are added later inside the try/catch block
sourceFunc = new CodeDelegateCreateExpression(new CodeTypeReference(typeof(Func<Io.Cucumber.Messages.Types.Source>)), new CodeTypeReferenceExpression(generationContext.TestClass.Name), "SourceFunc");
var sourceFuncBody = new CodeMemberMethod();
sourceFuncBody.Attributes = MemberAttributes.Private | MemberAttributes.Static;
sourceFuncBody.ReturnType = new CodeTypeReference(typeof(Io.Cucumber.Messages.Types.Source));
sourceFuncBody.Name = sourceFunc.MethodName;
generationContext.TestClass.Members.Add(sourceFuncBody);

gherkinDocumentFunc = new CodeDelegateCreateExpression(new CodeTypeReference(typeof(Func<Io.Cucumber.Messages.Types.GherkinDocument>)), new CodeTypeReferenceExpression(generationContext.TestClass.Name), "GherkinDocumentFunc");
var gherkinDocumentFuncBody = new CodeMemberMethod();
gherkinDocumentFuncBody.Attributes = MemberAttributes.Private | MemberAttributes.Static;
gherkinDocumentFuncBody.ReturnType = new CodeTypeReference(typeof(Io.Cucumber.Messages.Types.GherkinDocument));
gherkinDocumentFuncBody.Name = gherkinDocumentFunc.MethodName;
generationContext.TestClass.Members.Add(gherkinDocumentFuncBody);

picklesFunc = new CodeDelegateCreateExpression(new CodeTypeReference(typeof(Func<System.Collections.Generic.IEnumerable<Io.Cucumber.Messages.Types.Pickle>>)), new CodeTypeReferenceExpression(generationContext.TestClass.Name), "PicklesFunc");
var picklesFuncBody = new CodeMemberMethod();
picklesFuncBody.Attributes = MemberAttributes.Private | MemberAttributes.Static;
picklesFuncBody.ReturnType = new CodeTypeReference(typeof(System.Collections.Generic.IEnumerable<Io.Cucumber.Messages.Types.Pickle>));
picklesFuncBody.Name = picklesFunc.MethodName;
generationContext.TestClass.Members.Add(picklesFuncBody);
sourceFunc = new CodeDelegateCreateExpression(new CodeTypeReference(typeof(Func<Io.Cucumber.Messages.Types.Source>), CodeTypeReferenceOptions.GlobalReference), new CodeTypeReferenceExpression(generationContext.TestClass.Name), "SourceFunc");
var sourceFactoryMethod = new CodeMemberMethod();
sourceFactoryMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
sourceFactoryMethod.ReturnType = new CodeTypeReference(typeof(Io.Cucumber.Messages.Types.Source), CodeTypeReferenceOptions.GlobalReference);
sourceFactoryMethod.Name = sourceFunc.MethodName;
generationContext.TestClass.Members.Add(sourceFactoryMethod);

gherkinDocumentFunc = new CodeDelegateCreateExpression(new CodeTypeReference(typeof(Func<Io.Cucumber.Messages.Types.GherkinDocument>), CodeTypeReferenceOptions.GlobalReference), new CodeTypeReferenceExpression(generationContext.TestClass.Name), "GherkinDocumentFunc");
var gherkinDocumentFactoryMethod = new CodeMemberMethod();
gherkinDocumentFactoryMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
gherkinDocumentFactoryMethod.ReturnType = new CodeTypeReference(typeof(Io.Cucumber.Messages.Types.GherkinDocument), CodeTypeReferenceOptions.GlobalReference);
gherkinDocumentFactoryMethod.Name = gherkinDocumentFunc.MethodName;
generationContext.TestClass.Members.Add(gherkinDocumentFactoryMethod);

picklesFunc = new CodeDelegateCreateExpression(new CodeTypeReference(typeof(Func<System.Collections.Generic.IEnumerable<Io.Cucumber.Messages.Types.Pickle>>), CodeTypeReferenceOptions.GlobalReference), new CodeTypeReferenceExpression(generationContext.TestClass.Name), "PicklesFunc");
var picklesFactoryMethod = new CodeMemberMethod();
picklesFactoryMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
picklesFactoryMethod.ReturnType = new CodeTypeReference(typeof(System.Collections.Generic.IEnumerable<Io.Cucumber.Messages.Types.Pickle>), CodeTypeReferenceOptions.GlobalReference);
picklesFactoryMethod.Name = picklesFunc.MethodName;
generationContext.TestClass.Members.Add(picklesFactoryMethod);

// Create a new method that will be added to the test class.
// It will be called to provide the FeatureCucumberMessages property value of the FeatureInfo object when that object is constructed
var CucumberMessagesInitializeMethod = new CodeMemberMethod();
CucumberMessagesInitializeMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
CucumberMessagesInitializeMethod.Name = "InitializeCucumberMessages";
CucumberMessagesInitializeMethod.ReturnType = new CodeTypeReference(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureLevelCucumberMessages)));
generationContext.TestClass.Members.Add(CucumberMessagesInitializeMethod);
_cucumberMessagesInitializeMethod = CucumberMessagesInitializeMethod;

// Create a FeatureLevelCucumberMessages object and add it to featureInfo
var featureLevelCucumberMessagesExpression = new CodeObjectCreateExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureLevelCucumberMessages)),
sourceFunc,
gherkinDocumentFunc,
picklesFunc,
new CodePrimitiveExpression(sourceFileLocation));

CucumberMessagesInitializeMethod.Statements.Add(
new CodeMethodReturnStatement(
featureLevelCucumberMessagesExpression));

try
{
sourceFileLocation = Path.Combine(generationContext.Document.DocumentLocation.FeatureFolderPath, generationContext.Document.DocumentLocation.SourceFilePath);

// Cucumber IDs can be UUIDs or stringified integers. This is configurable by the user.
var IDGenStyle = _cucumberConfiguration.IDGenerationStyle;
Expand All @@ -253,7 +276,7 @@ private void DeclareFeatureMessagesFactoryMembers(TestClassGenerationContext gen
sourceExpression = new CodeObjectCreateExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(Io.Cucumber.Messages.Types.Source)),
new CodePrimitiveExpression(featureSource.Uri),
new CodePrimitiveExpression(featureSource.Data),
new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(Io.Cucumber.Messages.Types.SourceMediaType)), featureSource.MediaType.ToString()));
new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(new CodeTypeReference(typeof(Io.Cucumber.Messages.Types.SourceMediaType), CodeTypeReferenceOptions.GlobalReference)), featureSource.MediaType.ToString()));

// generate a CodeDom expression to create the GherkinDocument object from the featureGherkinDocumentMessage
var gherkinDocumentExpressionGenerator = new CucumberGherkinDocumentExpressionGenerator(_codeDomHelper);
Expand All @@ -265,11 +288,11 @@ private void DeclareFeatureMessagesFactoryMembers(TestClassGenerationContext gen

// wrap these expressions in Func<T>

sourceFuncBody.Statements.Add(new CodeMethodReturnStatement(sourceExpression));
sourceFactoryMethod.Statements.Add(new CodeMethodReturnStatement(sourceExpression));

gherkinDocumentFuncBody.Statements.Add(new CodeMethodReturnStatement(gherkinDocumentExpression));
gherkinDocumentFactoryMethod.Statements.Add(new CodeMethodReturnStatement(gherkinDocumentExpression));

picklesFuncBody.Statements.Add(new CodeMethodReturnStatement(picklesExpression));
picklesFactoryMethod.Statements.Add(new CodeMethodReturnStatement(picklesExpression));

}
catch (Exception e)
Expand All @@ -295,48 +318,6 @@ private void SetupTestClassInitializeMethod(TestClassGenerationContext generatio
_testGeneratorProvider.SetTestClassInitializeMethod(generationContext);
}

// Generation of Cucumber Messages relies on access to the parsed AST.
private void PersistStaticCucumberMessagesToFeatureInfo(TestClassGenerationContext generationContext, CodeMemberMethod testClassInitializeMethod)
{
CodeObjectCreateExpression sourceExpression;
CodeExpression gherkinDocumentExpression;
CodeExpression picklesExpression;
CodeDelegateCreateExpression sourceFunc;
CodeDelegateCreateExpression gherkinDocumentFunc;
CodeDelegateCreateExpression picklesFunc;

string sourceFileLocation;

// Create a new method that will be added to the test class. It will be called to initialize the FeatureCucumberMessages property of the FeatureInfo object
var CucumberMessagesInitializeMethod = new CodeMemberMethod();
CucumberMessagesInitializeMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
CucumberMessagesInitializeMethod.Name = "InitializeCucumberMessages";
CucumberMessagesInitializeMethod.Parameters.Add(new CodeParameterDeclarationExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureInfo)), "featureInfo"));
generationContext.TestClass.Members.Add(CucumberMessagesInitializeMethod);

// Create a FeatureLevelCucumberMessages object and add it to featureInfo
var featureLevelCucumberMessagesExpression = new CodeObjectCreateExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(FeatureLevelCucumberMessages)),
sourceFunc,
gherkinDocumentFunc,
picklesFunc,
new CodePrimitiveExpression(sourceFileLocation));

CucumberMessagesInitializeMethod.Statements.Add(
new CodeAssignStatement(
new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("featureInfo"), "FeatureCucumberMessages"),
featureLevelCucumberMessagesExpression));

// Create a CodeMethodInvokeExpression to invoke the CucumberMessagesInitializeMethod
var invokeCucumberMessagesInitializeMethod = new CodeMethodInvokeExpression(
null,
CucumberMessagesInitializeMethod.Name,
new CodeVariableReferenceExpression("featureInfo"));

// Add the CodeMethodInvokeExpression to the testClassInitializeMethod statements
testClassInitializeMethod.Statements.Add(invokeCucumberMessagesInitializeMethod);

}

private void SetupTestClassCleanupMethod(TestClassGenerationContext generationContext)
{
var testClassCleanupMethod = generationContext.TestClassCleanupMethod;
Expand Down Expand Up @@ -368,7 +349,7 @@ private void SetupTestInitializeMethod(TestClassGenerationContext generationCont
var getTestRunnerExpression = new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression(_codeDomHelper.GetGlobalizedTypeName(typeof(TestRunnerManager))),
nameof(TestRunnerManager.GetTestRunnerForAssembly),
_codeDomHelper.CreateOptionalArgumentExpression("featureHint",
_codeDomHelper.CreateOptionalArgumentExpression("featureHint",
new CodeVariableReferenceExpression(GeneratorConstants.FEATUREINFO_FIELD)));

testInitializeMethod.Statements.Add(
Expand Down
5 changes: 3 additions & 2 deletions Reqnroll/FeatureInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public class FeatureInfo
public string CucumberMessages_PickleId { get; set; }

public FeatureInfo(CultureInfo language, string folderPath, string title, string description, params string[] tags)
: this(language, folderPath, title, description, ProgrammingLanguage.CSharp, tags)
: this(language, folderPath, title, description, ProgrammingLanguage.CSharp, null, tags)
{
}

public FeatureInfo(CultureInfo language, string folderPath, string title, string description, ProgrammingLanguage programmingLanguage, params string[] tags)
public FeatureInfo(CultureInfo language, string folderPath, string title, string description, ProgrammingLanguage programmingLanguage, FeatureLevelCucumberMessages featureLevelCucumberMessages = null, params string[] tags)
{
if (language.IsNeutralCulture)
{
Expand All @@ -43,6 +43,7 @@ public FeatureInfo(CultureInfo language, string folderPath, string title, string
Title = title;
Description = description;
GenerationTargetLanguage = programmingLanguage;
FeatureCucumberMessages = featureLevelCucumberMessages;
Tags = tags ?? Array.Empty<string>();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public void NonCCKScenarios(string testName, string featureNameText)
CCKScenarios(testName, featureNameText);
}

[Ignore]
[TestMethod]
[DataRow("attachments", "Attachments")]
[DataRow("skipped", "Skipping scenarios")]
Expand Down
2 changes: 1 addition & 1 deletion Tests/Reqnroll.RuntimeTests/Bindings/StepContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@ private ScenarioInfo CreateScenarioInfo(string[] directScenarioTags = null, stri
=> new("Sample scenario", null, directScenarioTags ?? Array.Empty<string>(), new OrderedDictionary(), inheritedScenarioTags ?? Array.Empty<string>());

private FeatureInfo CreateFeatureInfo(string[] featureTags = null) =>
new(new CultureInfo("en-US"), @"C:\MyProject", "Sample feature", null, ProgrammingLanguage.CSharp, featureTags ?? Array.Empty<string>());
new(new CultureInfo("en-US"), @"C:\MyProject", "Sample feature", null, ProgrammingLanguage.CSharp, null, featureTags ?? Array.Empty<string>());
}

0 comments on commit 452ac85

Please sign in to comment.