📜 ⬆️ ⬇️

Useful chips SpecFlow

Hello!

On Habré there are excellent articles on SpecFlow. I want to delve into this topic and tell you about parallel execution of tests, data transfer between steps, assist helpers, transformations, hooks, and about using Json as a data source.

Parallel execution and data transfer between steps


In the documentation for transferring data between steps, we find the following example:

// Loading values into ScenarioContext ScenarioContext.Current["id"] = "value"; ScenarioContext.Current["another_id"] = new ComplexObject(); // Retrieving values from ScenarioContext var id = ScenarioContext.Current["id"]; var complexObject = ScenarioContext.Current["another_id"] As ComplexObject; 

This code uses string keys. Memorizing them and stuffing quite tiring.
')
This problem can be solved by creating a static class with the necessary properties:

 public static class ScenarioData { public static ComplexObject Complex { get => ScenarioContext.Current.Get<ComplexObject>(nameof(Complex)); set => ScenarioContext.Current.Set(value, nameof(Complex)); } } 

Data transfer now looks like this:

 // Loading values into ScenarioContext ScenarioData.Complex = new ComplexObject(); // Retrieving values from ScenarioContext var complexObject = ScenarioData.Complex; 

Unfortunately, we will not be able to use ScenarioContext.Current when running tests in parallel until we make the necessary Injection

 //      [Binding] public abstract class ScenarioSteps { protected ScenarioSteps(ScenarioContext scenarioContext, FeatureContext featureContext) { FeatureContext = featureContext; ScenarioContext = scenarioContext; ScenarioData = new ScenarioData(scenarioContext); } public FeatureContext FeatureContext { get; } public ScenarioContext ScenarioContext { get; } public ScenarioData ScenarioData { get; } } //  ScenarioData public class ScenarioData { private readonly ScenarioContext _context; public ScenarioData(ScenarioContext context) { _context = context; } public ComplexObject Complex { get => _context.Get<ComplexObject>(nameof(Complex)); set => _context.Set(value, nameof(Complex)); } } //      [Binding] public class ActionSteps : ScenarioSteps { public ActionSteps(ScenarioContext scenarioContext, FeatureContext featureContext) : base(scenarioContext, featureContext) { } [When(@"user uses complex object")] public void WhenUserUsesComplexObject() { ScenarioData.Complex = new ComplexObject(); } } 

Thus, we solved a couple of problems: we got rid of string keys and made it possible to run tests in parallel. For those who want to experiment, I created a small project.

Assist Helpers and Transformations


Consider the next step

 When user starts rendering | SourceType | PageOrientation | PageMediaSizeName | | set-01-valid | Landscape | A4 | 

Quite often, the data from the table is read as follows.

 [When(@"user starts Rendering")] public async Task WhenUserStartsRendering(Table table) { var sourceType = table.Rows.First()["SourceType"]; var pageOrientation = table.Rows.First()["PageOrientation"]; var pageMediaSizeName = table.Rows.First()["PageMediaSizeName"]; ... } 

With the help of Assist Helpers reading test data looks much more elegant. We need to make a model with the appropriate properties:

 public class StartRenderingRequest { public string SourceType { get; set; } public string PageMediaSizeName { get; set; } public string PageOrientation { get; set; } } 

and use it in CreateInstance

 [When(@"user starts Rendering")] public async Task WhenUserStartsRendering(Table table) { var request = table.CreateInstance<StartRenderingRequest>(); ... } 

With Transformations , the description of the test step can be simplified even more.

Determine the transformation:

 [Binding] public class Transforms { [StepArgumentTransformation] public StartRenderingRequest StartRenderingRequestTransform(Table table) { return table.CreateInstance<StartRenderingRequest>(); } } 

Now we can use the desired type as a parameter in the step:

 [When(@"user starts Rendering")] public async Task WhenUserStartsRendering(StartRenderingRequest request) { // we have implemented transformation, so we use StartRenderingRequest as a parameter ... } 

For those who want to experiment - the same project.

Hooks and using Json as a source of test data


SpecFlow tables are more than enough for simple test data. However, there are test scenarios with a large number of parameters and \ or a complex data structure. To use such test data, while maintaining the readability of the script, we need Hooks . We will use the [BeforeScenario] hook to subtract the required data from the Json file. To do this, we define special tags at the script level.

 @jsonDataSource @jsonDataSourcePath:DataSource\FooResponse.json Scenario: Validate Foo functionality Given user has access to the Application Service When user invokes Foo functionality | FooRequestValue | | input | Then Foo functionality should complete successfully 

and add processing logic to Hooks:

 [BeforeScenario("jsonDataSource")] public void BeforeScenario() { var tags = ScenarioContext.ScenarioInfo.Tags; var jsonDataSourcePathTag = tags.Single(i => i.StartsWith(TagJsonDataSourcePath)); var jsonDataSourceRelativePath = jsonDataSourcePathTag.Split(':')[1]; var jsonDataSourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, jsonDataSourceRelativePath); var jsonRaw = File.ReadAllText(jsonDataSourcePath); ScenarioData.JsonDataSource = jsonRaw; } 

This code subtracts the contents of the json file (the path to the file is relative) to a string variable and stores it in the script data (ScenarioData.JsonDataSource). Thus, we can use this data where required

 [Then(@"Foo functionality should complete successfully")] public void ThenFooFunctionalityShouldCompleteSuccessfully() { var actual = ScenarioData.FooResponse; var expected = JsonConvert.DeserializeObject<FooResponse>(ScenarioData.JsonDataSource); actual.FooResponseValue.Should().Be(expected.FooResponseValue); } 

Since there can be a lot of data in Json, you can also update test data using tags. Those interested can see an example in the same project.

Links


1. Cucumber
2. Gherkin
3. SpecFlow documentation
4. SpecFlow Wiki
5. Executable specification: SpecFlow from A to Z
6. Data Driven Tests & SpecFlow

Source: https://habr.com/ru/post/449574/


All Articles