TestAutomatisering & PerformanceTesten

Creëer meer eenheid in je SpecFlow steps met Step Argument Transformations

Laatst kreeg ik de vraag, “Hoe maak je in je testdata onderscheid tussen een regular expression en een gewone tekst”. Oftewel: Hoe beheer je verschillende soorten steps als ze alleen verschillen in de manier waarop data vergeleken wordt. Je hebt een veld en je wilt controleren of er een bepaalde waarde in staat, maar soms ook of het matcht aan een pattern, of het een waarde bevat of misschien dat het een bepaalde minimum lengte heeft:

# exact value 
Then the name field is equal to "Douglas Adams"

# contains a value
Then the name field contains "Adams"

# matching a pattern
Then the name field is matching the regex "[A-Z][a-z]+ [A-Z][a-z]+"

# minimum field length
Then the name field contains at least 2 characters

Je zal nu geneigd zijn om 1 van de volgende oplossingen te kiezen:

1. Voor elk check-type maak je een aparte stepdefinitie aan. Dit levert in dit geval 4 verschillende steps op die marginaal verschillen in de Assert sectie.
2. Je maakt één step die alle verschillende gevallen aankan door middel van een switch of een if/else if boom.

Beide zijn niet echt ideaal te noemen: Je zal het waarschijnlijk voor veel meer velden willen gaan inbouwen wat óf een wildgroei aan steps op gaat leveren, óf heel veel herhalende code in de vorm van switch statements.

Ik ben al een tijd fan van Fluent Assertions, een library die je assertions in menselijke taal laat noteren. Wat nu als we dat gedrag kunnen nabootsen en daar een generiek component voor gaan schrijven:

# exact value 
Then the name field should be "Douglas Adams"

# contains a value
Then the name field should contain "Adams"

# matching a pattern
Then the name field should match regex "[A-Z][a-z]+ [A-Z][a-z]+"

# minimum field length
Then the name field length should be greater or equal to "2"

De gegenereerde code ziet er als volgt uit:

[Then(@"the name field should be ""(.*)""")]
public void ThenTheNameFieldShouldBe(string expectedValue)
{
  ScenarioContext.Current.Pending();
}

We kunnen de daadwerkelijk compare actie er nu uit halen en die voeden in een switch statement:

[Then(@"the name field (.+) ""(.*)""")]
public void ThenTheNameFieldShouldBe(string compareAction, string expectedValue)
{
  // normally the actual value is retrieved from your application
  var actualValue = "Douglas Adams";

  switch (compareAction)
  {
    case "should be":
      actualValue.Should().Be(expectedValue);
      break;
    case "should contain":
      actualValue.Should().Contain(expectedValue);
      break;
    case "should match regex":
      actualValue.Should().MatchRegex(expectedValue);
      break;
    case "length should be greater or equal to":
      actualValue.Length.Should().BeGreaterOrEqualTo(Convert.ToInt32(expectedValue));
      break;
    default:
      throw new ArgumentException("Compare action is not a valid action");
  }
}

Dit levert nog geen reusable code op, maar je ziet wellicht waar dit heen gaat: we kunnen dit deel ook weer generiek maken. Dat doen we door een step argument transformatie. Hiervoor zetten we een class in de argumentenlijst, SpecFlow zal nu proberen dit te converteren naar dit object met behulp van een zogenaamde StepArgumentTransfomation:

[Then(@"the name field (.+) ""(.*)""")]
public void ThenTheNameFieldShouldBeComparable(ShouldComparer<string> compareAction, string expectedValue)
{
  // normally the actual value is retrieved from your application
  var actualValue = "Douglas Adams";
  
  // ShouldComparer action here...
}

De ShouldComparer moet gevoed worden met de actie die het moet uitvoeren en bij een call naar een Execute method de daadwerkelijke actie ook uitvoeren:

public class ShouldComparer<T1>
{
  private readonly Action<T1, T1> _action;
  
  public ShouldComparer(Action<T1, T1> action)
  {
    this._action = action;
  }

  public void Execute(T1 actual, T1 expected) => _action(actual, expected);
}

Als het T1 type je wat verward: In dit geval maken we gebruik van Generics. De T1 staat voor het type waarmee de class wordt aangemaakt, zodat we niet alleen de actie op string types uit kunnen voeren, maar op elk type dat wordt ondersteund door Fluent Assertions. In ons geval maken we een ShouldComparer aan voor strings (dat wordt gedaan door definitie ShouldComparer<string> compareAction in de stepmethod parameters), maar zo kunnen we de ShouldComparer later ook voor andere object typen gebruiken.

Nu volgt de implementatie: we moeten SpecFlow vertellen hoe het een string zoals “should be” of “should match regex” moet ombouwen naar de juiste actie. Hiervoor maken we een nieuwe StepArgumentTransformation methode aan die in een public class met een binding attribute moet staan. De methode moet een parameter ontvangen van het type waarvan we willen converteren en een object teruggeven van het type waarnaar we willen converteren.

[Binding]
public class ShouldTransformations
{
  [StepArgumentTransformation]
  public ShouldComparer<string> ShouldTransformationWithTypedComparer(string term)
  {
    switch (term)
    {
      case "should be":
        return new ShouldComparer<string>((actual, expected) 
          => actual.Should().Be(expected));
      case "should contain":
        return new ShouldComparer<string>((actual, expected) 
          => actual.Should().Contain(expected));
      case "should match regex":
        return new ShouldComparer<string>((actual, expected)
          => actual.Should().MatchRegex(expected));
      case "should be greater or equal to":
        return new ShouldComparer<string>((actual, expected) 
          => actual.Length.Should().BeGreaterOrEqualTo(Convert.ToInt32(expected)));
      default:
        throw new ArgumentException("the term was not recognized");
    }
  }
}

In dit geval staan hier alleen de vier gevallen die in het scenario staat aangegeven, maar in de praktijk zal je hier alle gevallen zetten die je (potentieel) wilt gebruiken zoals Should().NotBe() of Should().Match().

Nu hoeven we alleen nog de Execute method aan te roepen in de stepdefinitie:

[Then(@"the name field (.+) ""(.*)""")]
public void ThenTheNameFieldShouldBeComparable(ShouldComparer<string> compareAction, string expectedValue)
{
  var actualValue = "Douglas Adams";
  compareAction.Execute(actualValue, expectedValue);
}

Bij nieuwe steps hoef je in je stepdefinitie alleen nog maar een ShouldComparer toe te voegen en je hebt alle functionaliteit die je al eerder hebt geïmplementeerd voor een compare. Als je andere en/of eigen objecten wilt comparen, dan kan dat ook. Je kan je eigen StepArgumentTransformation schrijven voor elk type dat je wilt comparen, of je gebruikt de generieke implementatie:

[StepArgumentTransformation]
public ShouldComparer<T1> ShouldTransformationWithTypedComparer<T1>(string term)
{
  switch (term)
  {
    case "should be":
      return new ShouldComparer<T1>((actual, expected) => actual.Should().Be(expected));
    case "should not be":
      return new ShouldComparer<T1>((actual, expected) => actual.Should().NotBe(expected));
    
    default:
      throw new ArgumentException("the term was not recognized");
  }
}

Deze comparer kan je feeden met elke willekeurige class en de Fluent Assertions library zal automatisch een compare maken.

De CompareAction class kan je eventueel ook nog fluent maken zodat er een nog beter leesbaar statement uit voortkomt: compare.Actual(actualValue).With().Expected(expectedValue);

Conclusie:

In bepaalde SpecFlow steps wil je vaak meer testen dan alleen gelijkheid van data. Het inbouwen van een mechanisme om ook te vergelijken op bijvoorbeeld ongelijkheid, het matchen op een pattern of een minimum veldlengte kan resulteren in een wildgroei aan steps of het opblazen van de step methodes met veel herhaling. Door gebruik te maken van Fluent Assertions en Step Argument Transformations hebben we een generiek bruikbaar patroon die de vergelijkingsactie lostrekt van de overige acties in de glue code. Hiermee maken we onze code meer DRY en verkrijgen we een hogere mate van Separation of Concerns.

Dit bericht is geplaatst in Testautomatisering en getagd in Fluent Assertions SpecFlow

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Nieuws

Blijf op de hoogte

APACHE MPM (op *nix servers)

07/11/2017

Benieuwd naar de impact van het wijzigen van Apache MPM Prefork naar Worker? Lees dan door! Bij één van onze klanten heb ik dit onderzocht. Omdat dit ook interessant kan zijn voor anderen, deel ik mijn resultaten en ervaringen graag. Het is misschien wat technisch allemaal, maar voor performancetesters, de doelgroep, is het vast goed […]

Performancetesten en CI/CD, gaat dat samen?

13/10/2017

De afgelopen najaarseditie van Testnet stond onder het thema Continuous Everything vooral stil bij CI/CD en natuurlijk testen. Gezien DevOps en CI/CD ook grote invloed hebben op het vakgebied performance (testen), zijn wij blij dat we vanuit PerformanceArchitecten een bijdrage mochten leveren door middel van het delen van onze visie hierop. Onze collega René Meijboom […]

Een eerste indruk van Gauge

08/09/2017

Tijdens één van onze kennismiddagen hebben we gekeken naar het testtool Gauge. Doel van de sessie was om een beeld te krijgen wat de toegevoegde waarde van Gauge is voor een tester. Benieuwd naar onze ervaringen? Lees dan snel verder! Gauge is een open source project, gesponsord door ThoughtWorks en belooft in het kort het […]

Test Automation Framework BDD

16/06/2017

De afgelopen tijd heb ik in een opdracht collega’s mogen adviseren over de positieve impact van het Test Automation Framework BDD rondom het thema Agile. In een vorige blog ‘Transformation’ is te lezen wat BDD voor mij betekent en hoe BDD voor synergie zorgt tussen verschillende disciplines binnen Agile-teams met als doel het leveren van kwalitatief hoogwaardig […]

Creëer meer eenheid in je SpecFlow steps met Step Argument Transformations

11/04/2017

Laatst kreeg ik de vraag, “Hoe maak je in je testdata onderscheid tussen een regular expression en een gewone tekst”. Oftewel: Hoe beheer je verschillende soorten steps als ze alleen verschillen in de manier waarop data vergeleken wordt. Je hebt een veld en je wilt controleren of er een bepaalde waarde in staat, maar soms […]

SSL/TLS versie en cipher in HP LoadRunner

29/03/2017

In deze blog wil ik even stilstaan bij de resultaten van een performance test die niet overeenkwamen met de verwachtingen die wij als team hadden. Een aantal transacties gingen in responstijd omhoog en het CPU gebruik nam flink toe. Omdat het ons veel tijd heeft gekost, deel ik dit graag met jullie zodat wij performance […]

Regular Expressions en Testautomatisering, twee problemen of juist een oplossing?

25/03/2017

Bij geautomatiseerde checks wil je regelmatig een verwachte waarde controleren tegen een actuele waarde. Vroeg of laat kom je dan in aanraking met wildcards: Je wilt bijvoorbeeld weten of de tekst “Er zijn 42 resultaten gevonden” voorkomt, maar het aantal, hier 42, kan variabel zijn. Van 42 wil je dan een wildcard maken. De meest […]

Automated Approval Testing with Dynamic Data Sets

21/02/2017

For some tests, you’ll need to check if files or documents are the same as they were before, or that they comply with a certain format. When something differs, you need to “reject” or “approve“ them, or even make them the new baseline if you are welcoming the changes. We call this Approval Testing. For more […]

Transformation

20/01/2017

Inmiddels hebben veel organisaties de overstap gemaakt van de traditionele waterval methode naar een iteratieve ontwikkelmethode als Agile/Scrum. Een aantal organisaties zijn in hun werkwijze een stap verder gegaan en hebben DevOps geïntroduceerd. Waar de methodiek Agile vooral focust op het tevredenstellen van de klant, richt DevOps zich daarnaast op het dichten van de kloof […]

Cucumber, Selenium en het gebruik van het Page Object Pattern

14/12/2016

Als je Selenium gebruikt om je testen aan te sturen, kan je gebruik maken van het Page Object Pattern om een laag van abstractie aan te maken: Het maakt een model van je pagina zodat het makkelijker is om acties op die pagina uit te voeren. Dit heeft grote voordelen: Zodra er een wijziging op de […]

SpecFlow Extensies voor Dummies

21/11/2016

TechTalk heeft voor SpecFlow natuurlijk al een handige set aan extensies uitgebracht die vooral het gebruik van eigen datatypes in combinatie met SpecFlow steps mogelijk maken. Dit is slimme, maar vrij geavanceerde set waarbij je best wel wat ervaring moet hebben met zowel het gebruik van SpecFlow als C#. De beginnende gebruiker zal eerder behoefte […]

SpecFlow Extensies voor Dummies, de code

21/11/2016

Hieronder vind je de code die behoort bij het artikel SpecFlow Extensies voor Dummies  Feature File: Feature: Table Transformations In order to have convenient datatypes As a SpecFlow user I want to transform tables into dictionaries or datatables Scenario: Transform a vertical table into a dictionary Given I have the following table: | Movie | […]