Support for Cucumber Messages #202
Replies: 27 comments 118 replies
-
@clrudolphi started the work on this:
|
Beta Was this translation helpful? Give feedback.
-
It might seem like an obvious question, but I would like to know: when would Reqnroll output Gherkin messages? |
Beta Was this translation helpful? Give feedback.
-
I have another question I would like to drop in: How do we handle parallel execution?
|
Beta Was this translation helpful? Give feedback.
-
I don't believe the CCK has any examples that specifically cover this. |
Beta Was this translation helpful? Give feedback.
-
Requirements clarification
When we use the Gherkin Pickle Compiler on this scenario text, it fails b/c there is no Table to provide values for the "product" variable.
|
Beta Was this translation helpful? Give feedback.
-
Design Question about Bindings. When a step is ambiguous, Cucumber Messages wants to include the detail about all of the StepDefinitions that match the step text. Are those available as StepDefinitionBinding objects during a StepStarted or StepFinishedEvent? Or during one of the step Binding events? |
Beta Was this translation helpful? Give feedback.
-
Implementation Question: the Cucumber Message called Meta holds information about the name and version of the SUT, the runtime (in our case .NET, v8), the OS, and the CPU; along with info about the git commit and the CI build. |
Beta Was this translation helpful? Give feedback.
-
CCK alignment question - Skipped Scenarios. The CCK includes a set of scenarios to demonstrate how 'skip' works and the Cucumber Messages that result. The first such example from the CCK is this:
The '@Skip' tag invokes a BeforeScenario hook that signals that the scenario should be skipped (in the provided Typescript example implementation). Reqnroll doesn't support programmatically skipping scenarios from within Hooks (according to this part of the documentation ). I have experimented by changing the 'skip' tag into an 'ignore' tag. That results in a set of Cucumber Messages that seriously diverges from the CCK. The ignore tag causes Reqnroll to completely jump over the scenario, with no Messages as a result, while the CCK actually executes the scenario, at least as far as that 'skip' hook goes with a set of resulting Messages (TestCase, TestStart, TestStep, etc). I'm open to suggestions on how to proceed. |
Beta Was this translation helpful? Give feedback.
-
Implementation Questions: Attachments - Cucumber Messages schema requires that when an attachment is added that the contents of the attachment be included with the Messages (typically as BASE64). It also requires a MIME type. First question: when the ExecutionEvent AttachmentAddedEvent fires there is no context about which Feature or Scenario to which this event is associated. How do you suggest we make that connection so that the info about the attachment can be inserted into the correct Message stream? Second question: The Reqnroll event for an added attachment includes only the file name, which means that the MIME type must be inferred or detected by inspection. |
Beta Was this translation helpful? Give feedback.
-
Design/Implementation Question: alignment of hook execution to Features and Scenarios.
There are two Hook related ExecutionEvents I can subscribe to: HookStarted/Finished and HookBindingStarted/Finished. The HookStarted event includes info on which Feature and Scenario is involved, but only includes the HookType of the hooks that will be invoked. The HookBinding event indicates which specific Hook class/method is being invoked, but doesn't include enough context to know which test execution thread from which it is coming. When multiple Features are running simultaneously (and/or multiple Scenarios in parallel within a Feature), we don't have enough context to piece together the relationship between a fired Hook and the feature/scenario/step to which it belongs. Would anyone have any concerns with a change to the HookBindingStarted/Finished events payload to include Feature/Scenario/Step context? |
Beta Was this translation helpful? Give feedback.
-
The other Cucumber implementations have, and the CCK expects, the ability to set a (optional) "Name" on a hook. Reqnroll doesn't support this and for now our Cucumber Messages implementation won't be strictly compliant in that regard. |
Beta Was this translation helpful? Give feedback.
-
StepArgumentTransformations: the analogue within Messages of a StepArgumentTransformation is called a ParameterType. ParameterType's have two boolean properties: preferForRegularExpressionMatch and useForSnippets. I'm not seeing an obvious mapping from StepArgumentTransformation for these values. |
Beta Was this translation helpful? Give feedback.
-
@gasparnagy Would you please look at the interplay between methods OnScenarioEndAsync() and FireEventsAsync() in the TestExecutionEngine? It seems to me that if a user-hook throws an exception in an AfterScenario hook, then the ScenarioFinishedEvent is never fired. public virtual async Task OnScenarioEndAsync()
{
try
{
if (_contextManager.ScenarioContext.ScenarioExecutionStatus != ScenarioExecutionStatus.Skipped)
{
await FireScenarioEventsAsync(HookType.AfterScenario);
}
_testThreadExecutionEventPublisher.PublishEvent(new ScenarioFinishedEvent(FeatureContext, ScenarioContext));
}
finally
{
_contextManager.CleanupScenarioContext();
}
} Would it be as simple as moving the invocation of the PublishEvent() to the finally block? |
Beta Was this translation helpful? Give feedback.
-
Another side issue (perhaps?). The Meta message would like to include the name and version of the Gherkin tool being used. In our case, we would be providing a property of the Meta message called Implementation with sub attributes of "name":"Reqnroll" and "version":"2.1.0". private string GetReqnrollVersion()
{
return VersionInfo.AssemblyInformationalVersion;
} The VersionInfo class pulls info from the EntryAssembly and pulls from the AssemblyInformationVersionAttribute to obtain the version info. internal class VersionInfo
{
internal static string AssemblyVersion { get; }
internal static string AssemblyFileVersion { get; }
internal static string AssemblyInformationalVersion { get; }
internal static string NuGetVersion { get; }
static VersionInfo()
{
var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
AssemblyVersion = assembly.GetName().Version.ToString();
AssemblyFileVersion = assembly.GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version;
AssemblyInformationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
NuGetVersion = AssemblyInformationalVersion?.Split(new[] { '+' }, 2)[0];
}
} When I use the above code paths to generate a Meta message from within the TestExplorer of VisualStudio I end up with a version attribute of: "version":"16.4.0", which is the version number of the microsoft.net.test.sdk, not that of Reqnroll. Same result when I execute via dotnet test on the command line directly. Is there some other mechanism that I should be using? |
Beta Was this translation helpful? Give feedback.
-
Scope Expressions: After('@some-tag or @some-other-tag', function () {
throw new Error('Exception in conditional hook')
}) Reqnroll doesn't support this, right? We would expect the hook developer to use two Scope attributes or look at the Tags property of the ScenarioContext within the code of the hook. This will result in a discrepancy in the Messages emitted for that CCK test case.
I'm not suggesting we attempt to 'fix' this; just raising awareness of the discrepancy. |
Beta Was this translation helpful? Give feedback.
-
Another minor difference, would like confirmation that I can ignore it: In GherkinDocuments, the language of the document is specified using typical ISO monikers, like 'en-US' etc. The CCK is giving only the language portion of this, not the culture suffix. So in the previous example, the expectation is that the GherkinDocument would have a language attribute of 'en'. The C# Gherkin parser is the component creating this (to my understanding). |
Beta Was this translation helpful? Give feedback.
-
The CCK demonstrates that when Cucumber parses a step and binds it to a stepDef, it captures and reports the individual group matches that make up the arguments that will be passed to the StepDef. In most situations Reqnroll will be able to provide this (one value per argument). |
Beta Was this translation helpful? Give feedback.
-
Another Hook concern: order of execution of AfterScenario hooks. According to the Gherkin team over on Discord, the expected order of execution of After hooks is in reverse order of their definition (absent any other explicit ordering).
For the Messages implementation, I propose ignoring this and skip validation against the CCK for the ordering of hooks. |
Beta Was this translation helpful? Give feedback.
-
I could use some pointers/help on how to deal with file-based IO that is needed during test execution during a Github build. The Messages will be persisted to a file. I have a rudimentary .config feature that provides a means for telling the Cucumber Messages plug-in where to store the output file(s). |
Beta Was this translation helpful? Give feedback.
-
Design Question: @gasparnagy How open would you be to changes to the structure of the generated test classes such that the PickleIds are included as arguments to test methods? So, to take an example, here is what an Examples driven test would generate. Note the pickleIds as part of the DataRowAttribute and the use of a helper function to find a PickleStep.Id given a PickleId and step text: [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Valid product prices are calculated")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "External Data from CSV file")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("DataSource:products.csv")]
[Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("Chocolate", "2.5", null, "pickleId1")]
[Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("Apple", "1.0", null, "pickleId2")]
[Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("Orange", "1.2", null, "pickleId3")]
public async System.Threading.Tasks.Task ValidProductPricesAreCalculated(string product, string price, string[] exampleTags, string pickleId)
{
string[] @__tags = new string[] {
"DataSource:products.csv"};
if ((exampleTags != null))
{
@__tags = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Concat(@__tags, exampleTags));
}
string[] tagsOfScenario = @__tags;
System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary();
argumentsOfScenario.Add("product", product);
argumentsOfScenario.Add("price", price);
global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Valid product prices are calculated", "\tThe scenario will be treated as a scenario outline with the examples from the CS" +
"V file.", tagsOfScenario, argumentsOfScenario, featureTags, pickleId);
#line 4
this.ScenarioInitialize(scenarioInfo);
#line hidden
if ((global::Reqnroll.TagHelper.ContainsIgnoreTag(scenarioInfo.CombinedTags) || global::Reqnroll.TagHelper.ContainsIgnoreTag(featureTags)))
{
testRunner.SkipScenario();
}
else
{
await this.ScenarioStartAsync();
#line 6
string pickleStepId;
pickleStepId = FindPickleStepId(pickleId, string.Format("the customer has put 1 pcs of {0} to the basket", product));
await testRunner.GivenAsync(string.Format("the customer has put 1 pcs of {0} to the basket", product), ((string)(null)), ((global::Reqnroll.Table)(null)), pickleStepId, "Given ");
#line hidden
#line 7
pickleStepId = FindPickleStepId(pickleId, "the basket price is calculated");
await testRunner.WhenAsync("the basket price is calculated", ((string)(null)), ((global::Reqnroll.Table)(null)), pickleStepId, "When ");
#line hidden
#line 8
pickleStepId = FindPickleStepId(pickleId, "he basket price should be greater than zero");
await testRunner.ThenAsync("the basket price should be greater than zero", ((string)(null)), ((global::Reqnroll.Table)(null)), pickleStepId, "Then ");
#line hidden
}
await this.ScenarioCleanupAsync();
} With the above, the PickleId associated with each test case could be included with each relevant ExecutionEvent. This will allow a singleton subscriber to those events to be able to route those events to the correct component that is tracking the progress of that particular test case. |
Beta Was this translation helpful? Give feedback.
-
Implementation Question: I have somewhat liberally used the ITraceListener.WriteToolOutput and WriteTestOutput throughout the code as a mechanism to help me debug/understand the behavior of the Cucumber Messages subsystem as it evolves.
|
Beta Was this translation helpful? Give feedback.
-
Implementation Question for Example-driven tests: [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Log JSON")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Cucumber Messages Smoke Outline Test")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("some-tag")]
[Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("\"red\"", "9", null)]
[Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("\"green\"", "11", null)]
[Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("\"blue\"", "13", null)]
public async System.Threading.Tasks.Task LogJSON(string color, string @pickleId, string[] exampleTags)
{
string[] @__tags = new string[] {
"some-tag"};
if ((exampleTags != null))
{
@__tags = System.Linq.Enumerable.ToArray(System.Linq.Enumerable.Concat(@__tags, exampleTags));
}
string[] tagsOfScenario = @__tags;
System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary();
argumentsOfScenario.Add("color", color);
string m_pickleId = @pickleId;
global::Reqnroll.CucumberMessages.RuntimeSupport.PickleStepSequence m_pickleStepSequence = testRunner.FeatureContext.FeatureInfo.FeatureCucumberMessages.PickleJar.PickleStepSequenceFor(m_pickleId);
global::Reqnroll.ScenarioInfo scenarioInfo = new global::Reqnroll.ScenarioInfo("Log JSON", null, tagsOfScenario, argumentsOfScenario, featureTags, m_pickleId, m_pickleStepSequence);
#line 3
My question is about naming of these system generated parameter names and how to avoid naming conflicts with what the user may use as a column name in a table. As you can see above, I used the ampersand character at the start of the parameter name (valid C# syntax) in an attempt to avoid name clashes.
|
Beta Was this translation helpful? Give feedback.
-
Design Issue: Support for Parallel Execution of Scenarios and Cross Process Coordination When the upcoming PR is accepted that supports parallel execution of tests at the scenario(aka test case) level, the test execution framework may execute those tests in separate processes (or for the purposes of this discussion, in separate AppDomains). This significantly complicates the design of Messages support. We will need a means to coordinate across these executing processes to coordinate access to the results file and to ensure metadata records such as the StepDefinition messages are written only once. All messages will have to be sent to a central coordinator whether that is one of the processes executing the tests or some other dedicated process. Is there anyway for the user to configure their test fixture's use of process models? If so we might insist that these two features are incompatible(for now). Otherwise does anyone have suggestions on technologies/frameworks to use? |
Beta Was this translation helpful? Give feedback.
-
Implementation Question: ISkippedStepHandler - there is a separate list of SkippedStepHandlers maintained at the Scenario level; when a step is skipped, the TestExecutionEngine iterates through the list and invokes each handler. Given that this is a mechanism by which Reqnroll could be calling out to user-supplied code, was considering modifying this interface to async/await. But I can find no implementation methods that actually use it (the only references to it are in the tests).
|
Beta Was this translation helpful? Give feedback.
-
Design & Implementation Notes on Message properties not populated
|
Beta Was this translation helpful? Give feedback.
-
R27 of Cucumber Messages was just released this morning. Here are the release notes:
I will be working to adapt to this (adding the TestRunHookStarted and Finished messages). Questions:
So, we have a few options to consider: Let me know your thoughts on direction. |
Beta Was this translation helpful? Give feedback.
-
Code Structure/Design Question: in the ExecutionEvent.cs file, I've created new class constructors for HookBindingStartedEvent, HookBindingFinishedEvent, OutputAddedEvent and AttachmentAddedEvent. For each class, the new constructor takes additional parameters, then delegates back to the original constructor. (The additional parameters are required to indicate to the Messages code where in the stream of execution these events belong, ie, to which Feature/Scenario/Step) Perhaps other than in tests, the original constructors are no longer used in the main body of Reqnroll; I've replaced all those invocations with calls to the new (larger) constructors. Should these old constructors be marked as [Obsolete], leave them as-is, or should these be refactored away completely? |
Beta Was this translation helpful? Give feedback.
-
Cucumber Messages is a "standard" JSON format that describes the feature files and the test executions. Most Cucumber-family tools can produce Cucumber Messages JSON output so it allows creating reporting and other tools that work with all cucumbers.
The message classes and the JSON schema are described in the GitHub project: https://github.com/cucumber/messages
Reqnroll does not currently produce cucumber messages output. LivingDoc generation and other tooling could benefit from this.
Beta Was this translation helpful? Give feedback.
All reactions