top of page

Zoekresultaten

55 items gevonden voor ""

  • Transformation

    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 tussen ontwikkeling en beheer. Een goede toepassing van Agile leidt tot hogere kwaliteit van het product (software), hogere klanttevredenheid en flexibiliteit voor wijzigingen vanuit gebruikersorganisatie. Vanuit mijn perspectief zijn veel teams door een Agile werkwijze te hanteren in plaats van de traditionele waterval methode, efficiënter en effectiever geworden, met de nadruk op onderlinge samenwerking en communicatie. Toch valt het mij in verschillende organisaties vaak op dat sommige onderdelen binnen het (Agile) project kristalhelder zijn voor het ene teamlid, maar zwarte magie voor een ander. Het gevolg: te veel documentatie, details, miscommunicatie en daardoor ook meer inspanning vanuit verschillende disciplines voor het creëren van een gemeenschappelijk begrip voor het ontwikkelen van software! In dit blog houd ik me bezig met het beantwoorden van de volgende vragen… Hoe gaan we ervoor zorgen dat alle inspanningen van verschillende disciplines binnen Agile goed op elkaar afgestemd worden om het gewenste product juist te bouwen? Hoe gaan we valideren of hetgeen gebouwd is ook daadwerkelijk voldoet aan de wensen/eisen van de eindgebruikers? Hoe gaan we een definitieve brug slaan tussen Business en IT? Het antwoord voor mij is: Behaviour Driven Development, hierna te noemen “BDD”! Het doel van BDD is synergie creëren tussen enerzijds het Development Team (IT) en anderzijds de eindgebruiker (Business). Hierbij is het belangrijk om eerst het gedrag van deze gebruiker te specificeren en vervolgens te automatiseren. Het product is uiteindelijk bedoeld voor de eindgebruiker! BDD is voor mij een recept waarbij je in natuurlijke taal (Given-When-Then) testscenario’s schrijft die begrijpelijk en levend zijn voor alle stakeholders (testers, developers, architecten, analysten, gebruikers, designers). De focus is om de opgestelde acceptatiecriteria in de user stories op hun beurt te automatiseren (Step Defintions). Op deze manier creëer je een gemeenschappelijk begrip tussen verschillende disciplines waarin je effectief en efficiënt met elkaar samenwerkt en communiceert. Een gereedschap waarmee je als team echt verder komt: “Adding value to software testtooling”. Het is aan het team om samen het BDD-principe te implementeren en te kiezen welk framework het best past binnen Agile. Wel moet er gewaakt worden dat er een duidelijke scheiding wordt gemaakt tussen de intentie van de eindgebruiker en de implementatie van het team. Zo hebben wij bijvoorbeeld in een van mijn vorige Agile projecten BDD geïmplementeerd met het gebruik van SpecFlow. SpecFlow is een Test Automation Framework dat BDD ondersteunt. Voor meer info, ga naar http://www.specflow.org/. Het voordeel van het gebruik van SpecFlow voor ons was het creëren van levende documentatie voor de stakeholders en focus op geautomatiseerde acceptatiecriteria. De ontwikkelaars zijn meer betrokken bij het testproces en de testers zijn veel actiever en kritischer bij het opstellen van acceptatiecriteria door de businessanalisten. Tegelijkertijd was het een mindset om bugs te voorkomen in plaats van deze te vinden. Op deze manier kregen we meer grip, vertrouwen en overlapping tussen de verschillende disciplines. Komt de bovenstaande uitdaging je bekend voor en wil je meer weten van BDD, dan beveel ik je graag aan om Specification by Example & Bridging the communication Gap van Gojko Adzic te lezen. In mijn volgende blog zal ik dieper ingaan op het Test Automation Framework BDD met daarin de drie centrale kwaliteitsattributen, namelijk: leesbaarheid, herbruikbaarheid en onderhoudbaarheid!

  • Cucumber, Selenium en het gebruik van het Page Object Pattern

    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 pagina plaatsvindt, hoef je alleen het model aan te passen en niet je test acties. Een tester kan daarnaast redelijk eenvoudig teststappen opbouwen zonder de achterliggende Selenium code te hoeven beheersen. Het spreekt voor zich dat je het Page Object Pattern graag zou willen gebruiken als je wilt testen met het Cucumber framework: Je glue code (of steps) kunnen dan op een hoog abstractielaag gemaakt worden waardoor je het lostrekt van het onderliggende uitvoerende mechanisme. Voor een enkele pagina kan je dat als volgt doen: public class CustomerStep { // maak een page object aan voor de customer page static CustomerPage customerPage = new CustomerPage(); @Given("^I navigate to the customers page$") public void I_navigate_to_the_customers_page() throws Throwable { // roep deze methode aan op het page object customerPage.makeActivePage(); } } public class CustomerPage { // laten we hier een webDriver aanmaken zodat we kunnen // interacteren met de pagina. public WebDriver webDriver; public CustomerPage() { WebDriver webDriver = new FirefoxDriver(); } public void makeActivePage() { webDriver.navigate().to("http://foo.com/customer.html"); } } Nu hebben we een werkend geheel waarbij we het Page Object Pattern kunnen gebruiken binnen Cucumber. We krijgen alleen een probleem als we meerdere pagina’s gaan gebruiken. Voor elke pagina wordt er namelijk een nieuwe webdriver aangemaakt. Niet handig als je meerdere scenario’s op dezelfde context (dus in dezelfde browsersessie) uit wilt voeren. Dat lossen we als volgt op: We maken een class aan met een static webdriver die overerfbaar is. public class CommonPage { // De webDriver verplaatsen we hiernaartoe. We maken hem // static zodat er maar 1 webdriver aangemaakt wordt, // onafhankelijk van het aantal pagina instanties die er // gebruikt worden. public static WebDriver webDriver; protected CommonPage() { if (webDriver == null) webDriver = new FirefoxDriver(); } } Wanneer we een nieuw Page Object maken, dan overerven we van deze class. Op die manier maken we altijd gebruik van dezelfde webdriver. Hieronder zien we hoe de het CustomerPage object eruit ziet met een CommonPage als super class: Public class CustomerPage extends CommonPage { // We gebruiken de webDriver van de Super class public void makeActivePage() { webDriver.navigate().to("http://foo.com/customer.html"); } } De class ziet er dus nog simpeler uit, terwijl we toch altijd gebruik kunnen maken van de webdriver. Een ander voordeel is, is dat we generieke acties, die dus niet specifiek tot een pagina behoren, kwijt kunnen in deze super class: Alle pagina objecten hebben vanaf dan toegang tot deze aspecifieke methodes. public class CommonPage { // [ ... ] // Op alle pagina’s komen secties voor. Laten we hier een // herbruikbare methode van maken. Alle pagina objecten kunnen // nu gebruik maken van “getSections” public List getSections(String sectionName) { return webDriver.findElements( By.cssSelector("section." + sectionName)); } } Nu is de method “getSections” te gebruiken door iedere PageStep library. Op deze manier kunnen we dus ook een Common PageStep library maken waar we alle Common Steps in plaatsen: public class CommonSteps { // We gebruiken geen specifieke page om onze acties op te // doen, we kunnen natuurlijk wel een common page object // gebruiken. CommonPage commonPage = new CommonPage(); @Before public void beforeScenario() { // Plaats hier acties die altijd moeten gebeuren voordat // een scenario uitgevoerd moet worden. // Bedenk eens wat je hier allemaal zou kunnen doen! } // Een pagina aspecifieke actie. Die kunnen we het beste // hier kwijt. @Then("^it contains a \"([^\"]*)\" section$") public void it_contains_a_section(String sectionName) throws Throwable { List sections = commonPage.getSections(sectionName); assertTrue("a section "+sectionName+" could be found.", !sections.isEmpty()); } } Daarnaast hebben we meteen een mooie placeholder voor de acties die voor, met de @Before annotatie, en na, met behulp van de @After annotatie, een scenario moeten gebeuren. Door slim gebruik te maken van pagina objecten, overerving en een static webdriver kan een net en overzichtelijke codebasis gemaakt worden voor Cucumber testen die gebruik maken van Selenium. We splitsen de stap op in een beoogde actie op de pagina door een call naar het pagina object en de specifiek invulling van de actie door Selenium. Ook splitsen we generieke acties van specifieke pagina acties door gebruik te maken van een common library.

  • SpecFlow Extensies voor Dummies, de code

    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 | Rating | | The Matrix | 8.5 | | It | 8.1 | When I transform this table into a dictionary Then the dictionary should have a key "The Matrix" with a value "8.5" And the dictionary should have a key "It" with a value "8.1" Scenario: Transform a table into a datatable Given I have the following table: | Movie | Rating | Director | | The Matrix | 8.5 | The Wachowskis | | It | 8.1 | Tommy Lee Wallace | When I transform this table into a datatable Then the datatable should have the following record: | Column | Value | | Movie | The Matrix | | Rating | 8.5 | | Director | The Wachowskis | And the datatable should have the following record: | Column | Value | | Movie | It | | Rating | 8.1 | | Director | Tommy Lee Wallace | Step Definiton: using System.Collections.Generic; using System.Linq; using TechTalk.SpecFlow; using SpecFlowExamples.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Data; namespace SpecFlowExamples.Steps { [Binding] public class TableSteps { [Given(@"I have the following table:")] public void GivenIHaveTheFollowingTable(Table table) { ScenarioContext.Current.Add("table", table); } [When(@"I transform this table into a dictionary")] public void WhenITransformThisTableIntoADictionary() { var table = (Table)ScenarioContext.Current["table"]; var dictionary = table.ToDictionary(); ScenarioContext.Current.Add("dictionary", dictionary); } [Then(@"the dictionary should have a key ""(.*)"" with a value ""(.*)""")] public void ThenTheDictionaryShouldHaveAKeyWithAValue(string key, string value) { var dictionary = (Dictionary)ScenarioContext.Current["dictionary"]; Assert.IsTrue(dictionary.ContainsKey(key), "The dictionary contains key " + key); Assert.AreEqual(value, dictionary[key]); } [When(@"I transform this table into a datatable")] public void WhenITransformThisTableIntoADatatable() { var table = (Table)ScenarioContext.Current["table"]; var dataTable = table.ToDataTable(); ScenarioContext.Current.Add("datatable", dataTable); } [Then(@"the datatable should have the following record:")] public void ThenTheDatatableShouldHaveTheFollowingRecord(Table table) { var dataTable = (DataTable)ScenarioContext.Current["datatable"]; var expectedRecord = table.ToDictionary(); foreach (DataRow row in dataTable.Rows) { var matchingRowFound = true; foreach (var keyValuePair in expectedRecord) { var column = keyValuePair.Key; var value = keyValuePair.Value; if (!value.Equals(row[column])) { matchingRowFound = false; break; } } if (matchingRowFound) return; } Assert.Fail("The table does not contain the record."); } } } SpecFlow.Table Extension: using System.Collections.Generic; using System.Linq; using TechTalk.SpecFlow; using SpecFlowExamples.Extensions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Data; namespace SpecFlowExamples.Steps { [Binding] public class TableSteps { [Given(@"I have the following table:")] public void GivenIHaveTheFollowingTable(Table table) { ScenarioContext.Current.Add("table", table); } [When(@"I transform this table into a dictionary")] public void WhenITransformThisTableIntoADictionary() { var table = (Table)ScenarioContext.Current["table"]; var dictionary = table.ToDictionary(); ScenarioContext.Current.Add("dictionary", dictionary); } [Then(@"the dictionary should have a key ""(.*)"" with a value ""(.*)""")] public void ThenTheDictionaryShouldHaveAKeyWithAValue(string key, string value) { var dictionary = (Dictionary)ScenarioContext.Current["dictionary"]; Assert.IsTrue(dictionary.ContainsKey(key), "The dictionary contains key " + key); Assert.AreEqual(value, dictionary[key]); } [When(@"I transform this table into a datatable")] public void WhenITransformThisTableIntoADatatable() { var table = (Table)ScenarioContext.Current["table"]; var dataTable = table.ToDataTable(); ScenarioContext.Current.Add("datatable", dataTable); } [Then(@"the datatable should have the following record:")] public void ThenTheDatatableShouldHaveTheFollowingRecord(Table table) { var dataTable = (DataTable)ScenarioContext.Current["datatable"]; var expectedRecord = table.ToDictionary(); foreach (DataRow row in dataTable.Rows) { var matchingRowFound = true; foreach (var keyValuePair in expectedRecord) { var column = keyValuePair.Key; var value = keyValuePair.Value; if (!value.Equals(row[column])) { matchingRowFound = false; break; } } if (matchingRowFound) return; } Assert.Fail("The table does not contain the record."); } } }

  • JavaScript in OpenText LoadRunner

    Soms heb je een eigen gemaakte functie nodig in LoadRunner. Bijvoorbeeld een functie ten behoeve van string manipulatie zoals de zoek-en-vervang functie. Voor het schrijven van deze functie ben je in VUGen over het algemeen aangewezen op programmeertaal C. C is niet altijd de makkelijkste taal om functies in te schrijven. Het is mogelijk om voor HTTP/HTML VUGen scripts gebruik te maken van JavaScript. JavaScript bevat vele standaard functies en is uitermate geschikt om functies mee te schrijven. Met behulp van functie web_js_run kan je JavaScript code uitvoeren. Je moet dan wel in de Run Time Settings (RTS) het runnen van JavaScript enablen (Internet Protocol > Preferences > Options…). Alles wat in web_js_run aan JavaScript code wordt uitgevoerd/gelezen, wordt bewaard in een zogenaamde context (per VUser). Dus gedefinieerde variabelen, functies, etcetera blijven beschikbaar voor opvolgende web_js_run functies. Deze worden in dezelfde context uitgevoerd. Je kan de context resetten en daarbij dus de gebruikte memory vrijgeven door middel van functie web_js_reset. Ook wordt de context gereset bij een nieuwe script iteratie wanneer “Simulate a new user on each iteration” is ingesteld. Hieronder wordt een voorbeeld weergegeven van het aanroepen van een functie in een andere file. web_js_run( “Code = funcReplace(LR.getParam(‘rtprm_value’), ‘a’, ‘b’);”, “ResultParam = rtprm_result“, SOURCES, “File = INCLUDEFILES / js_code.js“, ENDITEM, LAST ); Wat onder SOURCES staat, wordt eerst geëvalueerd. Dit kan code zijn of, zoals in dit voorbeeld, een verwijzing naar een bestand (met code). De locatie van het bestand wordt weergegeven met een relatief pad ten opzichte van de VUGen script folder. In dit voorbeeld bevindt de file js_code.js zich in folder INCLUDEFILES in de VUGen script folder. Onder SOURCES kunnen meerdere items komen te staan; het hoeft niet één file of één stuk code te zijn. Het eerste argument van de web_js_run functie bevat de uit te voeren JavaScript code. Dit kan code zijn, zoals in dit voorbeeld, of een bestand. Anders dan bij SOURCES, mag slecht één item gebruikt worden. In de JavaScript code kunnen verschillende LoadRunner Ajax TruClient functies aangeroepen worden, zoals LR.getParam. In deze regel code wordt funcReplace aangeroepen. Deze functie bevindt zich in de file js_code.js en ziet er als volgt uit: function funcReplace(strSource, strReplace, strReplaceWith) { var re = new RegExp(strReplace, ”g”); return strSource.replace(re, strReplaceWith); } Het tweede argument van de web_js_run functie bevat de naam van de parameter waarin het resultaat van de JavaScript code wordt opgeslagen. In dit geval de waarde van parameter rtprm_value waarbij de a’s zijn vervangen door b’s.

  • SOAP requests via OpenText LoadRunner Web (HTTP/HTML) protocol

    De makkelijkste manier om een SOAP request te versturen met LoadRunner is via het protocol Web Services. Je kan dan door het importeren van een WSDL heel simpel een zogenaamde web_service_call maken. Er zijn echter meerdere redenen te bedenken waarom je uiteindelijk niet voor dit protocol kiest, maar voor het Web (HTTP/HTML) protocol: Je hebt geen (correcte) WSDL waardoor de toegevoegde waarde van Web Services protocol wegvalt; Je hebt geen Web Services protocol in je licentie (dit hoeft vanaf LoadRunner 12 geen probleem meer te zijn met de Community License); Je hebt onvoldoende virtual users voor Web Services protocol in de licentie zitten, maar wel genoeg users van het type Web (HTTP/HTML); Je wilt kosten besparen: het Web Services protocol is beschikbaar in de duurdere licentie bundels van de leverancier terwijl je ook via het Web (HTTP/HTML) protocol soap requests kan versturen. En dit protocol zit ook in de goedkopere licentie bundels. Om een script te kunnen maken met Web (HTTP/HTML) protocol dat een SOAP requests verstuurt, heb je het volgende nodig: Het URL end-point; De SOAP-request (inhoud); De waarde voor SOAPAction header. Wanneer je een WSDL hebt, kan je uit de WSDL het URL end-point (address location) en de waarde voor SOAPAction header (operation soapAction) halen. De SOAP request zelf is niet zo makkelijk te bepalen. Je zou hiervoor een voorbeeldbericht moeten hebben of deze zelf moeten maken via bijvoorbeeld SoapUI of LoadRunner (met protocol Web Services). Deze kan je met tools als Fiddler en Wireshark dan achterhalen. Wanneer bovenstaande data compleet is, kunnen we het script maken in LoadRunner: Creëer een nieuw script in LoadRunner VUGen met het Web (HTTP/HTML) protocol; Kies voor Insert New Step (Alt+Insert of Design > Insert in Script > New Step); Kies voor web_custom_request; Nu zie je het volgende scherm: 5. Verander de Method naar POST; 6. Vul de URL end-point in bij URL; 7. Vul de SOAP request in bij Body. In Body alles selecteren en via rechtermuisknop kiezen voor Convert to C Format; 8. Klik op OK; 9. Nu zie je scriptcode waaraan je twee web_add_headers moet toevoegen (voor de web_custom_request): a. web_add_header(“SOAPAction”, “< waarde voor SOAPAction header >”); b. web_add_header(“Content-Type”, “text/xml;charset=UTF-8”); 10. Klaar! Hopelijk heb je hier wat aan gehad. Mocht je hierover vragen of andere tips en tricks hebben of wil je gewoon reageren? Wij horen het graag!

  • Gebruik de ‘Record Steps…’ functionaliteit!

    Hoe laat je je business analisten of testers een scenario beschrijven? Vaak is dat met behulp van Word, een aantal screenshots erin geplakt met al dan niet een begeleidende tekst wat er op de schermen gebeurt. Waarschijnlijk is dat na het lezen van dit artikel verleden tijd! Windows 7 of hoger heeft een nieuwe functionaliteit: de “Record steps to reproduce a problem” tool. Dit is een screenrecorder die bij elke klik een screenshot maakt van de kliklocatie en het onderliggende object, een beschrijving geeft van wat de gebruiker deed: ‘User left click on ‘Reports – Show Report’” en een additional details sectie met actieve programma’s, UI elementen etc. Daarnaast kan de gebruiker commentaar toevoegen. De opname wordt opgeslagen als .mth bestand, zodat het makkelijk in je browser uit te lezen is als document of als slideshow of je kan met Word het bestand bewerken. Microsoft heeft deze tool goed verborgen, het makkelijkst is door te zoeken met de Windows toets en “record steps” in te voeren (engelstalige versie). De tool zal in de gevonden resultaten getoond worden, vanaf hier kan je ook makkelijk een shortcut maken. Na de opname zal je gevraagd worden om de opname op te slaan, dit zal in een .zip file geplaatst worden. De zip file bevat 1 bestand: de .mht. Waarschijnlijk heeft Microsoft deze tool ontworpen om issues in hun eigen software snel door niet technische gebruikers te laten toesturen via mail, vandaar het automatisch zippen. In tegenstelling tot de teleurstellende Snipping tool is dit een heel handige tool voor het visueel beschrijven van performance test scenario’s, ook door niet technische personen. Daarnaast kan het een prima aanvulling om snel een finding of bug mee te loggen, het eigenlijke doel van deze tool. Zie hieronder een voorbeeldje:

  • Frontend performance meting – als aanvulling op de traditionele manier van performancetesten

    Vaak ligt de eerste prioriteit binnen het performancetesten op het IT-perspectief. Dit is ergens wel terecht te noemen. Voordat (eind)gebruikers een systeem kunnen gebruiken, is het natuurlijk als eerste van belang dat het systeem het doet en niet omvalt wanneer er meerdere gebruikers tegelijk bezig zijn. Maar hoe ervaart de eindgebruiker het systeem? In deze blog sta ik graag even stil bij hoe hier het antwoord op te vinden. Performancetesten worden uitgevoerd om applicaties en systemen te testen op verschillende performance aspecten. Vanuit IT-perspectief gaat het dan om bijvoorbeeld throughput en resource gebruik (CPU, memory e.d.). Vanuit gebruikersperspectief gaat het om responstijden, hoeveel mensen er gelijktijdig kunnen werken en de perceptie van snelheid. De traditionele performancetest tools zijn uitstekend in staat om het eerste aspect te testen, maar zijn minder sterk in het meten van de werkelijke responstijd zoals de eindgebruiker die ervaart. Dat komt omdat deze performancetest tools vooral meten binnen de (systeem)grenzen van de organisatie zelf. Hierbij wordt echter de vertraging die wordt veroorzaakt door internet en de software en hardware van de eindgebruiker (klant), buiten beschouwing gelaten. En juist door de groeiende rol van Ajax/dynamische websites/HTML 5 wordt alles wat er in de browser gebeurt van steeds grotere invloed op de eindgebruiker responstijd. Hoe kunnen we dan toch goed meten wat de eindgebruiker voor responstijd ervaart? De oplossing hiervoor staat schematisch weergegeven in onderstaand plaatje: Synthetic monitoring of RUM. Synthetic monitoring betreft het uitvoeren van gescripte transacties door externe agents op een web applicatie in een gecontroleerde omgeving. Synthetic monitoring: Is een simulatie van het gedrag van eindgebruikers Maakt gebruik van externe agents Meet op vaste intervallen Vanaf één of meerdere vaste locaties Toepasbaar in test- en productieomgeving Real User Monitoring (RUM) meet de daadwerkelijke performance van de internet applicatie zoals die door de eindgebruiker in productie wordt ervaren. Real User Monitoring: Meet het daadwerkelijke gedrag van eindgebruikers Op het device, met de browser en de internet snelheid van de eindgebruiker gebruikt dus geen gescripte transacties Maakt gebruik van Javascript en/of agents Is alleen toepasbaar in productieomgeving Als je beide oplossingen naast elkaar zet, worden de verschillen misschien wat duidelijker. RUM Performance gemeten zoals de eindgebruiker die ervaart in productie Geeft informatie over elke gedeelte van de internet applicatie die door klanten bezocht wordt Alleen informatie als er bezoekers op de site komen Monitoring van een release die reeds in productie genomen is Synthetic monitoring Performance gemeten door simulatie van eindgebruikers in test- of productieomgeving Geeft informatie over een vaste set van stappen in een vast interval vanaf een vaste locatie Elke x minuten komt er informatie over response tijden binnen Monitoring in zowel test- als productieomgeving De keuze voor RUM of synthetic monitoring hangt vooral af van de behoefte van de klant. Is inzicht in de daadwerkelijke responstijd van eindgebruikers in productie nodig? Of is monitoring in test en productieomgeving nodig om voor oplevering al inzicht in responstijden te krijgen? Indien beide vragen met ja beantwoord worden, dan is ook een combinatie van beide oplossingen mogelijk. Wil je meer informatie over dit onderwerp? Bekijk dan mijn TestNet-presentatie: Of neem contact met op met PerformanceArchitecten. We spreken graag de voor- en nadelen van beide oplossingen met u door.

bottom of page