Nützliche SpecFlow-Funktionen

Hallo allerseits!

Auf Habré gibt es ausgezeichnete Artikel über SpecFlow. Ich möchte mich mit diesem Thema befassen und über das parallele Ausführen von Tests, das Übergeben von Daten zwischen Schritten, das Helfen von Helfern, Transformationen, Hooks und die Verwendung von Json als Datenquelle sprechen.

Parallele Ausführung und Datenübertragung zwischen Schritten


In der Dokumentation zum Übertragen von Daten zwischen Schritten finden Sie das folgende Beispiel:

// 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; 

Dieser Code verwendet Zeichenfolgenschlüssel. Das Auswendiglernen und Schreiben ist ziemlich mühsam.

Dieses Problem kann gelöst werden, indem eine statische Klasse mit den erforderlichen Eigenschaften erstellt wird:

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

Die Datenübertragung sieht jetzt so aus:

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

Leider können wir ScenarioContext.Current nicht verwenden, wenn wir Tests parallel ausführen, bis wir die erforderliche Injektion vorgenommen haben

 //      [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(); } } 

So haben wir ein paar Probleme gelöst: Wir haben die String-Schlüssel entfernt und die Möglichkeit bereitgestellt, Tests parallel auszuführen. Für diejenigen, die experimentieren möchten, habe ich ein kleines Projekt erstellt.

Helfer und Transformationen unterstützen


Betrachten Sie den nächsten Schritt

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

Sehr oft werden Daten aus einer Tabelle so abgezogen

 [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"]; ... } 

Mit Assist Helpers sieht das Lesen von Testdaten viel eleganter aus. Wir müssen ein Modell mit den entsprechenden Eigenschaften erstellen:

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

und verwenden Sie es in CreateInstance

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

Mit Transformationen kann die Beschreibung des Testschritts noch weiter vereinfacht werden.

Definieren Sie die Transformation:

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

Jetzt können wir den gewünschten Typ als Parameter im Schritt verwenden:

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

Für diejenigen, die experimentieren wollen - das gleiche Projekt.

Hooks und Verwendung von Json als Quelle für Testdaten


SpecFlow-Tabellen sind mehr als genug für einfache Testdaten. Es gibt jedoch Testszenarien mit einer großen Anzahl von Parametern und / oder einer komplexen Datenstruktur. Um solche Testdaten zu verwenden und gleichzeitig die Lesbarkeit des Skripts zu gewährleisten, benötigen wir Hooks . Wir werden den Hook [BeforeScenario] verwenden, um die erforderlichen Daten aus der Json-Datei zu lesen. Definieren Sie dazu spezielle Tags auf Skriptebene

 @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 

und fügen Sie die Verarbeitungslogik zu den Hooks hinzu:

 [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; } 

Dieser Code subtrahiert den Inhalt der JSON-Datei (relativer Pfad zur Datei) in eine Zeichenfolgenvariable und speichert ihn in den Skriptdaten (ScenarioData.JsonDataSource). Daher können wir diese Daten bei Bedarf verwenden

 [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); } 

Da Json viele Daten enthalten kann, kann die Aktualisierung der Testdaten auch über Tags implementiert werden. Interessenten können ein Beispiel im selben Projekt sehen.

Referenzen


1. Gurke
2. Essiggurke
3. SpecFlow-Dokumentation
4. SpecFlow Wiki
5. Ausführbare Spezifikation: SpecFlow A bis Z.
6. Datengesteuerte Tests & SpecFlow

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


All Articles