End-to-end-testning med Joomla! och Cypress - Mina första steg och tankar

Automatiserade tester är inget speciellt verktyg för mjukvaruutvecklare i stora projekt. Speciellt för tillägg är automatiserade tester en hjälp för att snabbt identifiera problem. De hjälper till att säkerställa att tillägg fungerar smidigt i nyare Joomla-versioner.

Joomla Core-utvecklarna vill att mjukvaruutvecklare från tredje part ska testa sina tillägg, för att hitta buggar innan en användare hittar dem. Detta kräver mycket tid och är tråkigt arbete. Därför görs det ofta inte. Speciellt inte om det måste göras manuellt av människor för varje ny utgåva. Automatiserad testning gör det möjligt att upprepa de manuella stegen för varje utgåva utan att en människa själv utför stegen. På så sätt hittas buggar innan en användare stöter på dem när de kommer åt livesystemet.

Förresten, alla som vill kolla in Cypress kommer att tycka att den här texten är ett bra ställe att börja. Du kan testa problem utan att behöva installera och konfigurera allt själv. I Github-förrådet i Joomla-projektet är allt klart att köra.

Intro

"Även om det är sant att kvalitet inte kan testas, är det lika uppenbart att utan testning är det omöjligt att utveckla något av kvalitet." – [James A. Whittaker]

Innan jag först stötte på Cypress kunde jag inte föreställa mig att de barriärer som ofta stod i vägen för mina tester faktiskt skulle flyttas åt sidan något.Jag ägnade mycket tid åt att testa mjukvara – och tidigare ännu mer tid på att ta itu med problemen som uppstod på grund av bristande testning! Nu är jag övertygad om att tester som är:

  • så nära programmeringen som möjligt,
  • automatiskt,
  • ofta - helst efter varje programbyte,

    ta in mer än vad de kostar. Och vad mer är: att testa kan till och med vara kul.

Det är värt att lära sig testmetoder! Testmetoder är långvariga, eftersom de inte bara kan användas med vilket programmeringsspråk som helst, de kan appliceras på nästan alla mänskliga arbeten. Du bör testa nästan allt som är viktigt i livet då och då. Testmetoder är oberoende av specifika mjukvaruverktyg. Till skillnad från programmeringstekniker eller programmeringsspråk, som ofta är på modet och ur mode, är kunskapen om hur man lägger upp bra tester tidlös.

Vem ska läsa denna text?

Alla som tycker att mjukvarutestning är ett slöseri med tid bör ta en titt på den här artikeln. Speciellt skulle jag vilja bjuda in de utvecklare att läsa detta som alltid har velat skriva tester för sin mjukvara - men som aldrig har gjort för olika Cypress kan vara ett sätt att ta bort sådana hinder.

Någon teori

Den magiska triangeln

Den magiska triangeln beskriver förhållandet mellan kostnaderna, den tid som krävs och den kvalitet som kan uppnås. Ursprungligen uppmärksammades och beskrevs denna relation i projektledning. Du har dock säkert hört talas om denna spänning även inom andra områden. fråga i nästan alla operativa processer i ett företag.

Det antas till exempel generellt att en högre kostnad har en positiv inverkan på kvalitet och/eller slutdatum - det vill säga tid.

 

Den magiska triangeln i projektledning – Om mer pengar investeras i projektet har detta en positiv inverkan på kvalitet eller tid.

Tvärtom kommer en kostnadsbesparing att tvinga kvaliteten att sjunka och/eller att färdigställandet försenas.

Den magiska triangeln i projektledning – Om mindre pengar investeras i projektet har detta en negativ inverkan på kvalitet eller tid.

Nu spelar magin in: Vi övervinner sambandet mellan tid, kostnader och kvalitet!För att i det långa loppet kan detta faktiskt övervinnas.

Sambandet mellan tid, kostnader och kvalitet kan övervinnas på sikt.

Kanske har du också i praktiken upplevt att en kvalitetssänkning inte ger kostnadsbesparingar på lång sikt. Den tekniska skulden som detta skapar leder ofta till och med till kostnadsökningar och mer tid.

På lång sikt kan korrelationen mellan kostnad, tid och kvalitet faktiskt övervinnas.

Teknisk skuld hänvisar till den extra ansträngning som krävs för att göra ändringar och förbättringar av dålig programmerad programvara jämfört med välskriven programvara.Martin Fowler särskiljer följande typer av tekniska skulder: De som man har ingått medvetet och de som man har ingått av misstag Han skiljer också mellan försiktiga och hänsynslösa tekniska skulder.

Teknisk skuld

Kostnader och fördelar

I litteraturen hittar du förödande statistik om möjligheterna att lyckas med mjukvaruprojekt. Lite har förändrats i den negativa bild som redan registrerades i en studie av AW Feyhl på 1990-talet. Här, i en analys av 162 projekt i 50 organisationer , kostnadsavvikelsen jämfört med den ursprungliga planeringen fastställdes: 70 % av projekten visade en kostnadsavvikelse på minst 50 %! Något stämmer inte! Det kan man väl inte bara acceptera?

En lösning skulle vara att helt avstå från kostnadsuppskattningar och följa #NoEstimates-rörelsens argumentation . Denna rörelse anser att kostnadsuppskattningar i ett mjukvaruprojekt är meningslösa. Ett mjukvaruprojekt innehåller enligt #NoEstimates-rörelsens åsikt alltid produktionen av något nytt Det nya är inte jämförbart med redan existerande erfarenheter och därmed inte förutsägbart.

Ju mer erfarenhet jag får, desto mer kommer jag till slutsatsen att extrema åsikter inte är bra. Lösningen är nästan alltid i mitten. Undvik extremer i mjukvaruprojekt också och leta efter en mittpunkt. Jag behöver inte ha en 100% säker plan. Men du bör inte heller starta ett nytt projekt naivt. Även om projektledning för programvara och särskilt kostnadsuppskattning är ett viktigt ämne, kommer jag inte att tråka ut dig med det längre i den här texten. Fokus i denna artikel är att visa hur E2E testning kan integreras i det praktiska arbetsflödet för mjukvaruutveckling.

Integrera programvarutestning i ditt arbetsflöde

Du har bestämt dig för att testa din programvara. Bra! När är den bästa tiden att göra detta? Låt oss ta en titt på kostnaden för att fixa en bugg i de olika projektfaserna. Ju tidigare du hittar en bugg, desto lägre kostnad för att fixa den .

Relativa kostnader för felsökning i olika projektled

Testning och felsökning: Det finns ord som ofta nämns i samma andetag och vars betydelser därför är utjämnade. Vid närmare eftertanke står dock termerna för olika tolkningar. Testning och felsökning hör till dessa ord. De två termerna har gemensamt att de upptäcker funktionsfel, men det finns också skillnader i innebörden.

  • Tester hittar okända fel under utveckling. Att hitta felet är och dyrt, medan lokalisering och eliminering av felet är billigt.
  • Debuggers fixar fel som upptäcks efter att produkten är färdig. Att hitta felet är gratis, men att lokalisera och åtgärda felet är dyrt.

Slutsats: Det är mest meningsfullt att börja integrera tester så tidigt som möjligt. Tyvärr är detta svårt att implementera i ett projekt med öppen källkod som Joomla med mestadels frivilliga bidragsgivare.

Kontinuerlig integration (CI)
Kontinuerlig integrering av tester

Föreställ dig följande scenario: En ny version av ett populärt innehållshanteringssystem är på väg att släppas. Allt som utvecklarna i teamet har bidragit med sedan den senaste releasen används nu tillsammans för första gången. Spänningen stiger! fungerar? Kommer alla tester att lyckas - om projektet överhuvudtaget integrerar tester. Eller måste releasen av den nya versionen skjutas upp igen och nervkittlande timmar av buggfixning väntar? Att skjuta upp releasedatumet är förresten inte heller bra för bilden av mjukvaruprodukten! Ingen utvecklare gillar att uppleva det här scenariot. Det är mycket bättre att när som helst veta i vilket tillstånd mjukvaruprojektet är för närvarande? Kod som inte passar in i den befintliga bör först integreras efter de har "gjorts för att passa".Särskilt i tider då det är allt vanligare att en säkerhetslucka måste åtgärdas bör ett projekt alltid kunna skapa en release!Och det är här kontinuerlig integration spelar in.

Vid kontinuerlig integration integreras enskilda delar av mjukvaran permanent. Mjukvaran skapas och testas i små cykler. På så sätt stöter du på problem vid integration eller felaktiga tester i ett tidigt skede och inte dagar eller veckor senare. Med lyckad integration, Felsökning är mycket enklare eftersom fel upptäcks nära tidpunkten för programmering och vanligtvis är det bara en liten del av programmet som påverkas Joomla integrerar ny kod med hjälp av kontinuerlig integration Ny kod integreras först när alla tester är godkända.

Med en kontinuerlig integration av ny mjukvara blir felsökningen mycket enklare eftersom felen upptäcks nära tidpunkten för programmering och oftast är det bara en liten del av programmet som påverkas.

För att säkerställa att du har tester för alla programdelar tillgängliga hela tiden under kontinuerlig integration bör du utveckla testdriven mjukvara.

Testdriven utveckling (TDD)

Testdriven utveckling är en programmeringsteknik som använder utveckling i små steg. Först skriver du testkoden. Först sedan skapar du programkoden som ska testas. Eventuell ändring av programmet görs först efter att testkoden för den ändringen har har skapats. Så dina tester misslyckas direkt efter skapande. Den önskade funktionen är ännu inte implementerad i programmet. Först då skapar du den faktiska programkoden - det vill säga programkoden som uppfyller testet.

TDD-testerna hjälper dig att skriva programmet korrekt .

När du först hör talas om den här tekniken kanske du inte är bekväm med konceptet. ""Människa"" vill trots allt alltid göra något produktivt först. Och att skriva test verkar inte vara produktivt vid första anblicken. Testa det. Ibland du blir vän med en ny teknik först efter att ha lärt känna den!I projekt med hög testtäckning känner jag mig mer bekväm när jag lägger till nya funktioner.

Om du går igenom övningsdelen i slutet av texten kan du prova. Skapa först testet och skriv sedan koden för Joomla Core. Skicka sedan in allt tillsammans som en PR på Github . Om alla skulle göra detta , Joomla skulle ha idealisk testtäckning.

Beteendedriven utveckling (BDD)

BDD är inte en annan programmeringsteknik eller testteknik, utan en sorts bästa praxis för mjukvaruutveckling. BDD används helst tillsammans med TDD. I princip står Behaviour-Driven-Development för att testa inte implementeringen av programkoden, utan exekveringen - dvs programmets beteende Ett test kontrollerar om specifikationen, dvs kundens krav, är uppfylld.

När du utvecklar programvara på ett beteendestyrt sätt hjälper tester dig inte bara att skriva programmet korrekt, utan tester hjälper dig också att skriva rätt program .

Vad menar jag med det: "Skriv rätt program"? Det händer att användare ser saker annorlunda än utvecklare. Arbetsflödet att ta bort en artikel i Joomla är ett exempel. Om och om igen möter jag användare som klickar på statusikonen i papperskorgen och blir förvånade. Användaren antar vanligtvis intuitivt att objektet nu är permanent raderat, men det växlas från papperskorgen till att aktiveras. För utvecklaren är att klicka på statusikonen en ändring av status, en växling. i alla andra vyer. Varför skulle detta vara annorlunda i papperskorgen? För utvecklaren är funktionen implementerad utan fel. Joomla fungerar korrekt. Men i mina ögon är funktionen inte den rätta på den platsen, eftersom de flesta användare skulle beskriva /begära det helt annorlunda .

I Behavior Driven Development beskrivs kraven för mjukvaran genom exempel som kallas scenarios eller user stories.Kännetecken för Behavior Driven Development är

  • ett starkt engagemang av slutanvändaren i utvecklingsprocessen av programvaran,
  • dokumentationen av alla projektfaser med användarberättelser/fallexempel i textform - vanligtvis på beskrivningsspråket på beskrivningsspråket Gherkin,
  • automatisk testning av dessa användarberättelser/fallstudier,
  • successiv implementering. Således kan en beskrivning av programvaran som ska implementeras nås när som helst. Med hjälp av denna beskrivning kan du kontinuerligt säkerställa att den redan implementerade programkoden är korrekt.

Joomla-projektet har introducerat BDD i ett Google Summer of Code-projekt . Man hoppades att användare utan programmeringskunskaper skulle kunna delta lättare genom att använda Gherkin ). Tillvägagångssättet följdes inte upp konsekvent. Då använde Joomla Codeception som en testverktyg Med Cypress är BDD-utveckling också möjlig att utveckla på BDD-sätt.

Planera

Testtyper
  • Enhetstest: Ett enhetstest är ett test som testar de minsta programenheterna oberoende.
  • Integrationstest: Ett integrationstest är ett test som testar samverkan mellan enskilda enheter.
  • E2E-tester eller acceptanstest: Ett acceptanstest kontrollerar om programmet uppfyller den uppgift som definierades i början.
Strategier

Om du vill lägga till en ny funktion i Joomla och säkra den med tester kan du gå vidare på två sätt.

Top-down och bottom-up är två fundamentalt olika tillvägagångssätt för att förstå och presentera komplexa frågor. Top-down går steg för steg från det abstrakta och allmänna till det konkreta och specifika. För att illustrera detta med ett exempel: Ett innehållshanteringssystem som Joomla presenterar i allmänhet webbplatser i en webbläsare. Konkret finns det dock ett antal små deluppgifter i denna process.En av dem är uppgiften att visa en specifik text i en rubrik.

Bottom-up beskriver den motsatta riktningen: vid det här laget är det värt att återigen komma ihåg att en del av beteendedriven utveckling är skapandet av en textuell beskrivning av programvarans beteende. Denna beskrivning av acceptanskriterier hjälper till att skapa tester - särskilt toppen -nivå end-to-end-tester eller acceptanstest.

Det vanliga tillvägagångssättet för att skapa tester idag är från botten Om du föredrar beteendedriven mjukvaruutveckling bör du använda den motsatta strategin Du bör använda top-down-strategin. Med en top-down-strategi identifieras ett missförstånd tidigt i designfasen.

Teststrategier: Top-down-testning och Bottom-up-testning

  • Top-down-testning: När man tillämpar top-down-strategin börjar man med acceptanstesterna – dvs med den del av systemet som är närmast kopplad till användarkraven.För programvara skriven för mänskliga användare är detta vanligtvis användargränssnittet Fokus ligger på att testa hur en användare interagerar med systemet. En nackdel med top-down-testning är att mycket tid måste läggas på att skapa testdubletter. Komponenter som ännu inte är integrerade måste ersättas av platshållare. är ingen riktig programkod i början. Därför måste saknade delar skapas artificiellt. Gradvis ersätts sedan denna artificiella data med riktigt beräknade data.

  • Bottom-up-testning: Om du följer bottom-up-strategin börjar du med enhetstester. I början har utvecklaren måltillståndet i åtanke. Han bryter dock först ner detta mål i enskilda komponenter. Problemet med Bottom-up-metoden är att det är svårt att testa hur en komponent senare kommer att användas i verkliga situationer.Fördelen med bottom-up-testning är att vi har färdigställt mjukvarudelar väldigt snabbt. Dessa delar bör dock användas med försiktighet. De fungerar korrekt. Det är vad enhetstesten säkerställer, men om slutresultatet verkligen är vad kunden föreställer sig att programvaran är är inte säkerställt.

Testpyramiden av Mike Cohn

Hur många tester ska implementeras av vilken testtyp? Testpyramiden av Mike Cohn beskriver ett koncept för användningen av de automatiserade mjukvarutesterna. Pyramiden består av tre nivåer, strukturerade efter användningsfrekvens och relevans. ‍

Helst bildas basen av testpyramiden av många snabba och lätta att underhålla enhetstester.På detta sätt kan de flesta fel upptäckas snabbt.

På mellannivå finns integrationstesterna. De tillhandahåller tjänster för riktad testning av kritiska gränssnitt. Utförandetiderna för integrationstester är längre och deras underhåll är också mer komplext än enhetstester.

Toppen av pyramiden består av långsamma E2E-tester, som ibland kräver mycket underhåll.E2E-tester är mycket användbara för att testa applikationen som ett komplett system.

Krav

Vilken utrustning behöver du för att arbeta med följande praktiska del?

Vilka krav har du för att arbeta aktivt med följande praktiska del? Du behöver inte uppfylla särskilt många krav för att kunna arbeta med innehållet i denna manual. Självklart måste du ha en dator. En utvecklingsmiljö med Git, NodeJS och Composer och en lokal webbserver bör installeras eller installeras på den.

Vilken kunskap bör du ha personligen?

Du bör känna till grundläggande programmeringstekniker. Helst har du redan programmerat en liten webbapplikation. I alla fall bör du veta var du ska lagra filer på din utvecklingsdator och hur du laddar dem i din webbläsare ut nya saker.

Testa det. Integrera tester i ditt nästa projekt. Kanske kommer din första upplevelse av ett test att spara dig en tråkig felsökningssession eller en pinsam bugg i det verkliga systemet. Trots allt, med ett skyddsnät av tester kan du utveckla programvara med mindre påfrestning.

Installation

Konfigurera Cypress med Joomla!

I utvecklarversionen som finns tillgänglig på Github är Joomla Cypress färdigkonfigurerad. Det finns redan tester som du kan använda som guide. Så det är inte nödvändigt att ställa in allt själv för att få en första överblick. På så sätt kan du experimentera med Cypress , lär dig om dess fördelar och nackdelar och bestäm själv om du vill använda testverktyget.

Steg för att ställa in den lokala miljön:

Klona förvaret till roten på din lokala webbserver:

$ git clone https://github.com/joomla/joomla-cms.git

Navigera till mappen joomla-cms:

$ cd joomla-cms

Enligt Joomla Roadmap kommer nästa större version 5.0 att släppas i oktober 2023. För att vara uppdaterad använder jag denna utvecklingsversion här.

Ändra till branch 5.0-dev :

$ git checkout 5.0-dev

Installera alla nödvändiga kompositörspaket:

$ composer install

Installera alla nödvändiga npm-paket:

$ npm install

För mer information och hjälp med att ställa in din arbetsstation, se Joomla-dokumentationsartikeln "Ställa in din arbetsstation för Joomla-utveckling" . För Cypress finns information på cypress.io . Men det är inte nödvändigt just nu. Joomla ställer in allt Du behöver bara ställa in dina individuella data via konfigurationsfilen joomla-cms/cypress.config.js.

Ställ in dina individuella data. För detta kan du använda mallen joomla-cms/cypress.config.dist.jssom orientering. I mitt fall ser den här filen ut så här:

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  fixturesFolder: 'tests/cypress/fixtures',
  videosFolder: 'tests/cypress/output/videos',
  screenshotsFolder: 'tests/cypress/output/screenshots',
  viewportHeight: 1000,
  viewportWidth: 1200,
  e2e: {
    setupNodeEvents(on, config) {},
    baseUrl: 'http://localhost/joomla-cms',
    specPattern: [
      'tests/cypress/integration/install/*.cy.{js,jsx,ts,tsx}',
      'tests/cypress/integration/administrator/**/*.cy.{js,jsx,ts,tsx}',
      'tests/cypress/integration/module/**/*.cy.{js,jsx,ts,tsx}',
      'tests/cypress/integration/site/**/*.cy.{js,jsx,ts,tsx}'
    ],
    supportFile: 'tests/cypress/support/index.js',
    scrollBehavior: 'center',
    browser: 'firefox',
    screenshotOnRunFailure: true,
    video: false
  },
  env: {
    sitename: 'Joomla CMS Test',
    name: 'admin',
    email: Den här e-postadressen skyddas mot spambots. Du måste tillåta JavaScript för att se den.',
    username: 'admin',
    password: 'adminadminadmin',
    db_type: 'MySQLi',
    db_host: 'mysql',
    db_name: 'test_joomla',
    db_user: 'root',
    db_password: 'root',
    db_prefix: 'j4_',
  },
})

tests/cypress/integration/module/**/*.cy.{js,jsx,ts,tsx}Konkret lade jag till katalogen specPattern i Arrayen, eftersom jag vill spara test för moduler där senare. Sedan ändrade jag användarnamn och lösenord eftersom jag också vill testa installationen manuellt och komma ihåg de självtilldelade bättre. Jag använder en Docker-container som databas. Därför ändrade jag databasservern och åtkomstdata. Och till slut var jag tvungen att ställa in rot-URL http://localhost/joomla-cmsför min Joomla-installation.

Använd Cypress

Via webbläsare

Ring npm run cypress:openvia CLI i din Joomla rotkatalog. En kort tid senare öppnas Cypress-appen. Vi har tidigare skapat filen. joomla-cms/cypress.config.dist.jsAtt detta detekteras framgår av att E2E Testing är angivet som konfigurerat.

Cypress App öppnas efter att ha ringt 96;npm kör cypress:open96;.

Här kan du välja om du vill köra E2E-testerna och vilken webbläsare du vill använda. Till exempel valde jag alternativet "Börja testa i Firefox".

E2E-tester i Cypress-appen: välj vilken webbläsare du vill använda.

Alla tillgängliga testsviter kommer att listas och du kan klicka på den du vill köra.När du väljer en testsvit kommer testerna att köras och du kan se körningen av testerna i realtid i webbläsaren.

Joomla testsvit i Firefox via Cypress App.

Medan testerna körs kan du se det körda skriptet på ena sidan och resultatet i webbläsaren på höger sida. Det här är inte bara skärmdumpar, utan riktiga ögonblicksbilder av webbläsaren i det ögonblicket, så att du kan se den faktiska HTML-koden Skärmdumpar och till och med videor av testerna är också möjliga.

Joomla installationstest pågår.

Prova det. Om du använder som db_host: 'localhost',du kan testa installationen och därmed har konfigurerat Joomla korrekt för arbetet med följande del av denna text.

Om du, som jag, använder en extern källa (inte lcoalhost; jag använder en docker-container) som , db_hostär testet för denna typ av installation inte klart ännu. I så fall finns det en säkerhetsfråga i installationsrutinen, vilket är ännu inte beaktats i testerna. Installera i så fall Joomla manuellt med informationen som anges i filen . joomla-cms/cypress.config.jsFöljande tester kommer att använda inställningarna från denna konfigurationsfil, till exempel för att logga in på Joomlas administrationsområde. På så sätt gör testutvecklaren behöver inte bry sig om att ange inloggningsdata. Matchande användare och lösenord används alltid automatiskt från konfigurationsfilen.

Huvudlös

Som standard cypress runkörs alla tester utan huvud Följande kommando kör alla redan kodade tester och sparar skärmdumpar i katalogen /joomla-cms/tests/cypress/output/screenshotsi händelse av ett fel. Utdatakatalogen sattes i filen cypress.config.js.

$ npm run cypress:run

Andra CLI-kommandon

Det finns andra användbara kommandon som inte är implementerade som skript i package.jsonJoomla-projektet. Jag kör dem via npx [docs.npmjs.com/commands/npx].

cypress verifiera

Kommandot cypress verifyverifierar att Cypress är korrekt installerat och kan köras.

$ npx cypress verify

✔  Verified Cypress! /.../.cache/Cypress/12.8.1/Cypress
cypress info

Kommandot cypress infomatar ut information om Cypress och den aktuella miljön.

$ npx cypress info
Displaying Cypress info...

Detected 2 browsers installed:

1. Chromium
  - Name: chromium
  - Channel: stable
  - Version: 113.0.5672.126
  - Executable: chromium
  - Profile: /.../snap/chromium/current

2. Firefox
  - Name: firefox
  - Channel: stable
  - Version: 113.0.1
  - Executable: firefox
  - Profile: /.../snap/firefox/current/Cypress/firefox-stable

Note: to run these browsers, pass : to the '--browser' field

Examples:
- cypress run --browser chromium
- cypress run --browser firefox

Learn More: https://on.cypress.io/launching-browsers

Proxy Settings: none detected
Environment Variables: none detected

Application Data: /.../.config/cypress/cy/development
Browser Profiles: /.../.config/cypress/cy/development/browsers
Binary Caches: /.../.cache/Cypress

Cypress Version: 12.8.1 (stable)
System Platform: linux (Ubuntu - 22.04)
System Memory: 4.08 GB free 788 MB
cypress version

Kommandot cypress versionskriver ut den installerade Cypress binära versionen, versionen av Cypress-paketet, versionen av Electron som användes för att skapa Cypress och den medföljande nodversionen.

$ npx cypress version
Cypress package version: 12.8.1
Cypress binary version: 12.8.1
Electron version: 21.0.0
Bundled Node version: 16.16.0

Cypress dokumentation ger mer detaljerad information.

Skriver det första egna testet

Om allt har fungerat hittills kan vi börja skapa våra egna tester.

Få en överblick

Att lära sig av tester som redan utvecklats

I utvecklingsversionen av Joomla CMS finns det redan Cypress-tester. Dessa finns i mappen. /tests/System/integrationDe som gillar att lära sig genom exempel hittar en lämplig introduktion här.

Importera kod för repetitiva uppgifter

Joomla-utvecklarna arbetar med NodeJs- projektet joomla-cypress , som tillhandahåller testkod för vanliga testfall.Dessa importeras under installationen av utvecklarversionen av CMS med hjälp av npm installvia

  • package.jsonoch via
  • supportfil /tests/System/support/index.js. Supportfilen definieras i konfigurationen cypress.config.js.
// package.json
{
  "name": "joomla",
  "version": "5.0.0",
  "description": "Joomla CMS",
  "license": "GPL-2.0-or-later",
  "repository": {
    "type": "git",
    "url": "https://github.com/joomla/joomla-cms.git"
  },
...
  "devDependencies": {
    ...
    "joomla-cypress": "^0.0.16",
    ...
  }
}

Ett exempel är klickningen på en knapp i verktygsfältet, Cypress.Commands.add('clickToolbarButton', clickToolbarButton)gör till exempel att kommandot clickToolbarButton()blir tillgängligt i de anpassade testerna och simuleras via cy.clickToolbarButton('new')ett klick på knappen. Den kod som krävs för detta visas i kodavsnittet nedan.New

// node_modules/joomla-cypress/src/common.js
...
const clickToolbarButton = (button, subselector = null) => {
  cy.log('**Click on a toolbar button**')
  cy.log('Button: ' + button)
  cy.log('Subselector: ' + subselector)

  switch (button.toLowerCase())
  {
    case "new":
      cy.get("#toolbar-new").click()
      break
    case "publish":
      cy.get("#status-group-children-publish").click()
      break
    case "unpublish":
      cy.get("#status-group-children-unpublish").click()
      break
    case "archive":
      cy.get("#status-group-children-archive").click();
      break
    case "check-in":
      cy.get("#status-group-children-checkin").click()
      break
    case "batch":
      cy.get("#status-group-children-batch").click()
      break
    case "rebuild":
      cy.get('#toolbar-refresh button').click()
      break
    case "trash":
      cy.get("#status-group-children-trash").click()
      break
    case "save":
      cy.get("#toolbar-apply").click()
      break
    case "save & close":
      cy.get(".button-save").contains('Save & Close').click()
      break
    case "save & new":
      cy.get("#save-group-children-save-new").click()
      break
    case "cancel":
      cy.get("#toolbar-cancel").click()
      break
    case "options":
      cy.get("#toolbar-options").click()
      break
    case "empty trash":
    case "delete":
      cy.get("#toolbar-delete").click()
      break
    case "feature":
      cy.get("#status-group-children-featured").click()
      break
    case "unfeature":
      cy.get("#status-group-children-unfeatured").click()
      break
    case "action":
      cy.get("#toolbar-status-group").click()
      break
    case "transition":
      cy.get(".button-transition.transition-" + subselector).click()
      break
  }

  cy.log('--Click on a toolbar button--')
}

Cypress.Commands.add('clickToolbarButton', clickToolbarButton)
...

Följande kod visar ett annat exempel, inloggningen till administrationsområdet.

// /node_modules/joomla-cypress/src/user.js
...
const doAdministratorLogin = (user, password, useSnapshot = true) => {
  cy.log('**Do administrator login**')
  cy.log('User: ' + user)
  cy.log('Password: ' + password)

  cy.visit('administrator/index.php')
  cy.get('#mod-login-username').type(user)
  cy.get('#mod-login-password').type(password)
  cy.get('#btn-login-submit').click()
  cy.get('h1.page-title').should('contain', 'Home Dashboard')

  cy.log('--Do administrator login--')
}

Cypress.Commands.add('doAdministratorLogin', doAdministratorLogin)

...
Gemensamma arbetsuppgifter i den individuella miljön

I katalogen /tests/System/supporthittar du vanliga uppgifter i den enskilda miljön För att dessa enkelt ska kunna återanvändas importeras de via supportfilen Ett /tests/System/support/index.jsexempel på en ofta upprepad uppgift är inloggningen till administrationsområdet som hanteras i filen /tests/System/support/commands.jsanvänder funktionen doAdministratorLogin.

Följande kod visar också hur informationen från cypress.config.jskonfigurationen används i testerna. Cypress.env('username')tilldelas värdet på usernameegenskapen inom gruppen env.

Dessutom kan vi se här hur man skriver över kommandon. Cypress.Commands.overwrite('doAdministratorLogin' ...),skriver över koden vi just såg i paketet. joomla-cypressFördelen är att användaren och lösenordet automatiskt används från den individuella konfigurationen.

// /tests/System/support/commands.js
...
Cypress.Commands.overwrite('doAdministratorLogin', (originalFn, username, password, useSnapshot = true) => {
  // Ensure there are valid credentials
  const user = username ?? Cypress.env('username');
  const pw = password ?? Cypress.env('password');

  // Do normal login when no snapshot should be used
  if (!useSnapshot) {
    // Clear the session data
    Cypress.session.clearAllSavedSessions();

    // Call the normal function
    return originalFn(user, pw);
  }

  // Do login through the session
  return cy.session([user, pw, 'back'], () => originalFn(user, pw), { cacheAcrossSpecs: true });
});
...

Installera din egen Joomla Extension

För att se hur du testar din egen kod kommer vi att installera en enkel exempelkomponent via Joomla backend Filen för installation kan laddas ner från Codeberg .

Installera ett eget Joomla-tillägg.

Efter installationen kan du hitta en länk till vyn av Foo-komponenten i vänster sidofält på Joomla-backend.

Vy över exempelkomponenten i Joomla backend.

Vi har nu en testmiljö inrättad och kod för att testa.

Det första egna testet

Krokar

När du testar backend kommer du att märka att du måste starta varje test med en inloggning. Vi kan förhindra denna redundanta kod genom att använda funktionen. beforeEach()Denna så kallade hook exekverar koden vi anger innan varje test körs. Därav namnet beforeEach().

Cypress tillhandahåller flera typer av krokar , inklusive beforekrokar aftersom körs före eller efter testerna i en testgrupp, och beforeEachkrokar afterEachsom körs före eller efter varje enskilt test i gruppen. Krokar kan definieras globalt eller inom ett specifikt describedblock. Nästa kodexempel i filen tests/System/integration/administrator/components/com_foos/FoosList.cy.jsgör att en inloggning utförs i backend före varje test inom describedblocket test com_foos features.

Vi börjar nu med den praktiska delen och skapar filen tests/System/integration/administrator/components/com_foos/FoosList.cy.jsmed nästa kodavsnitt innan vi skriver det första produktiva testet. Vårt första exempel bör framgångsrikt logga in oss i backend innan något test! Vi kommer att testa detta efter att vi har skapat det första testet.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js

describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })

})

Obs: Krokar, som är implementerade i filen /tests/System/support/index.js, gäller för varje testfil i testdräkten.

Ett lyckat test

Komponenten vi installerade för testning innehåller de tre elementen Astridoch Nina. ElmarFörst testar vi om dessa element skapades framgångsrikt.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js

describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })

  it('list view shows items', function () {
    cy.visit('administrator/index.php?option=com_foos')

    cy.get('main').should('contain.text', 'Astrid')
    cy.get('main').should('contain.text', 'Nina')
    cy.get('main').should('contain.text', 'Elmar')

    cy.checkForPhpNoticesOrWarnings()
  })
})

Obs: Funktionen checkForPhpNoticesOrWarnings()du hittar i filen /node_modules/joomla-cypress/src/support.js.

Vi får DOM-elementet mainvia Cypress-kommandot get

Du bör hitta ditt nyss skapade test FooList.cy.jsi listan över tillgängliga tester i den vänstra sidofältet. Om så inte är fallet, stäng webbläsaren och kör npm run cypress:openigen.

Joomla körtest för egen tillägg.

Klicka på namnet på testet för att köra det. Det bör sluta framgångsrikt och du ser gröna meddelanden.

Vyn efter testet har körts framgångsrikt.

Ett misslyckat test

Lägg till raden cy.get('main').should('contain.text', 'Sami')i testfilen så att körningen misslyckas. Det finns inget element med detta namn. Efter att ha sparat testfilen märker Cypress ändringen. Efter varje ändring kör Cypress automatiskt om alla tester i testfilen.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
describe('Test com_foos features', () => {
  beforeEach(() => {
    cy.doAdministratorLogin()
  })

  it('list view shows items', function () {
    cy.visit('administrator/index.php?option=com_foos')

    cy.get('main').should('contain.text', 'Astrid')
    cy.get('main').should('contain.text', 'Nina')
    cy.get('main').should('contain.text', 'Elmar')
    cy.get('main').should('contain.text', 'Sami')

    cy.checkForPhpNoticesOrWarnings()
  })
})

Som väntat misslyckas testet. Det finns röda meddelanden. Du kan se koden för varje teststeg i den vänstra sidofältet. Så det är möjligt att hitta orsaken till felet. För varje steg finns en ögonblicksbild av HTML-dokumentet, så att du kan kontrollera markeringen när som helst. Detta är användbart, särskilt under utveckling.

Vyn efter testet har misslyckats.

Kör bara ett test i en fil

Vår demotillägg innehåller mer än en layout. Lägg till ett test för att testa den tomma tillståndslayouten. Eftersom vi nu har två tester i den här filen kommer Cypress alltid att köra båda testerna varje gång vi sparar filen. Vi kan använda så att endast ett .only()test exekveras:

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js

describe('Test com_foos features', () => {
    beforeEach(() => {
        cy.doAdministratorLogin()
    })

    it('list view shows items', function () {
        cy.visit('administrator/index.php?option=com_foos')

        cy.get('main').should('contain.text', 'Astrid')
        cy.get('main').should('contain.text', 'Nina')
        cy.get('main').should('contain.text', 'Elmar')

        cy.checkForPhpNoticesOrWarnings()
    })

    it.only('emptystate layout', function () {
        cy.visit('administrator/index.php?option=com_foos&view=foos&layout=emptystate')
        cy.get('main').should('contain.text', 'No Foo have been created yet.')
    })
})

Under utveckling är detta mycket bekvämt.

Särskilda testegenskaper

Nu vill vi testa frontend för vår komponent, det gör vi i en separat fil /tests/System/integration/site/components/com_foos/FooItem.cy.js.

För det mesta använder vi en CSS-klass för att få ett element i Joomla-tester. Även om detta är helt giltigt och kommer att fungera, rekommenderas det faktiskt inte. Varför inte? När du använder CSS-klasser eller ID:n binder du dina test till saker som kommer med största sannolikhet att förändras med tiden. Klasser och ID är till för design, layout och ibland via JavaScript för kontroll, vilket lätt kan ändras. Om någon ändrar ett klassnamn eller ID kommer dina tester inte längre att fungera. För att göra dina tester mindre spröda och Cypress är mer framtidssäker och rekommenderar att du skapar speciella dataattribut för dina element specifikt för teständamål.

Jag kommer att använda data-testattributet för elementen, först lägger jag till attributet data-test="foo-main"i produktionskoden.

// /components/com_foos/tmpl/foo/default.php

\defined('_JEXEC') or die;
?>
<div data-test="foo-main">
Hello Foos
</div>

Sedan testar jag produktionskoden genom att söka på attributet [data-test="foo-main"].

// tests/System/integration/site/components/com_foos/FooItem.cy.js
describe('Test com_foo frontend', () => {
  it('Show frondend via query in url', function () {
    cy.visit('index.php?option=com_foos&view=foo')

    cy.get('[data-test="foo-main"]').should('contain.text', 'Hello Foos')

    cy.checkForPhpNoticesOrWarnings()
  })
})
Testa ett menyalternativ och några tankar om händelser, väntan och bästa praxis

Nu gillar jag att testa skapandet av ett menyalternativ för vår komponent. Jag gör detta i en separat fil. /tests/System/integration/administrator/components/com_foos/MenuItem.cy.jsDen här koden är komplex och visar många specialfunktioner.

Först definierade jag en konstant där jag ställer in alla relevanta egenskaper för menyalternativet. Detta har fördelen att vid ändringar av en relevant egenskap måste jag bara justera på ett ställe:

const testMenuItem = {
  'title': 'Test MenuItem',
  'menuitemtype_title': 'COM_FOOS',
  'menuitemtype_entry': 'COM_FOOS_FOO_VIEW_DEFAULT_TITLE'
}

Därefter ser du hela koden för filen MenuItem.cy.js:

// tests/System/integration/administrator/components/com_foos/MenuItem.cy.js

describe('Test menu item', () => {
  beforeEach(() => {
    cy.doAdministratorLogin(Cypress.env('username'), Cypress.env('password'))
  })

  it('creates a new menu item', function () {
    const testMenuItem = {
      'title': 'Test MenuItem',
      'menuitemtype_title': 'COM_FOOS',
      'menuitemtype_entry': 'COM_FOOS_FOO_VIEW_DEFAULT_TITLE'
    }

    cy.visit('administrator/index.php?option=com_menus&view=item&client_id=0&menutype=mainmenu&layout=edit')
    cy.checkForPhpNoticesOrWarnings()
    cy.get('h1.page-title').should('contain', 'Menus: New Item')

    cy.get('#jform_title').clear().type(testMenuItem.title)

    cy.contains('Select').click()
    cy.get('.iframe').iframe('#collapse1-heading').contains(testMenuItem.menuitemtype_title).click()
    cy.get('.iframe').iframe('#collapse1-heading').contains(testMenuItem.menuitemtype_entry).click()

    cy.intercept('index.php?option=com_menus&view=items&menutype=mainmenu').as('item_list')
    cy.clickToolbarButton('Save & Close')
    cy.wait('@item_list')
    cy.get('#system-message-container').contains('Menu item saved.').should('exist')

    // Frontend
    cy.visit('index.php')
    cy.get('.sidebar-right').contains(testMenuItem.title).click()
    cy.get('[data-test="foo-main"]').should('contain.text', 'Hello Foos')
    cy.checkForPhpNoticesOrWarnings()

    // Trash
    cy.visit('administrator/index.php?option=com_menus&view=items&menutype=mainmenu')
    cy.searchForItem(testMenuItem.title)
    cy.checkAllResults()
    cy.clickToolbarButton('Action')
    cy.intercept('index.php?option=com_menus&view=items&menutype=mainmenu').as('item_trash')
    cy.clickToolbarButton('trash')
    cy.wait('@item_trash')
    cy.get('#system-message-container').contains('Menu item trashed.').should('exist')

    // Delete
    cy.visit('administrator/index.php?option=com_menus&view=items&menutype=mainmenu')
    cy.setFilter('published', 'Trashed')
    cy.searchForItem(testMenuItem.title)
    cy.checkAllResults()
    cy.on("window:confirm", (s) => {
      return true;
    });
    cy.intercept('index.php?option=com_menus&view=items&menutype=mainmenu').as('item_delete')
    cy.clickToolbarButton('empty trash');
    cy.wait('@item_delete')
    cy.get('#system-message-container').contains('Menu item deleted.').should('exist')
  })
})
  • I den här koden kan du se ett exempel på att testa något och sedan radera allt - på så sätt återställa det initiala tillståndet. På så sätt kan du upprepa testerna så många gånger du vill. Utan att återställa det initiala tillståndet kommer den andra testkörningen att misslyckas eftersom Joomla kan inte lagra två liknande element.

Obs: Ett test bör vara:

  • repeterbar.
  • Konkret betyder det att det ska testa ett begränsat problem och att koden för detta inte ska vara för omfattande.
  • oberoende av andra tester.
  • Och du kan se hur du använder en avlyssnad rutt definierad med cy.intercept()[^docs.cypress.io/api/commands/intercept] som ett alias, och sedan vänta på rutten definierad som ett alias med cy.wait().

När man skriver tester för sådana applikationer är man frestad att använda slumpmässiga värden som cy.wait(2000);i cy.waitkommandot. Problemet med detta tillvägagångssätt är att även om detta kan fungera bra i utvecklingen. Det är dock inte garanterat att det alltid fungerar. Varför? Eftersom det underliggande systemet beror på saker som är svåra att förutse, därför är det alltid bättre att definiera exakt vad du väntar på.

  • Koden visar också hur man väntar på en varning och bekräftar den.
cy.on("window:confirm", (s) => {
  return true;
});
  • Sist men inte minst innehåller testkoden Cypress inbyggd och Joomla-typiska funktioner som kan återanvändas av tilläggsutvecklare, till exempel eller är cy.setFilter('published', 'Trashed')funktioner cy.clickToolbarButton('Save & Close')där lösningar för enskilda tester kan hittas i allmänhet och som Joomla-utvecklare i synnerhet ofta behöver .
Blanda Async och Sync Code

Cypress-kommandon är asynkrona, det vill säga de returnerar inget värde utan generatedet. När vi startar Cypress kör den inte kommandona direkt utan läser dem i serie och köar dem. Om du blandar asynkron och synkron kod i tester kan du kan få oväntade resultat. Om du kör följande kod kommer du att få ett felmeddelande mot förväntan. Du hade säkert också förväntat dig att det mainText = $main.text()ändrar värdet . mainTextMen mainText === 'Initial'är fortfarande giltigt i slutet. Varför är det så? Cypress kör först den synkrona koden kl. början och slutet Först då anropar den den asynkrona delen inuti . then()Det betyder att variabeln mainTextinitieras och omedelbart efteråt kontrolleras den om den har ändrats - vilket naturligtvis inte är fallet.

let mainText = 'Initial';
cy.visit('administrator/index.php?option=com_foos&view=foos&layout=emptystate')
cy.get("main").then(
  ($main) => (mainText = $main.text())
);

if (mainText === 'Initial') {
  throw new Error(`Der Text hat sich nicht geändert. Er lautet: ${mainText}`);
}

Bearbetningen av kön blir ganska tydlig och visuell om man observerar exekveringen av följande kod i webbläsarens konsol: Texten "Cypress Test." visas långt innan innehållet i elementet visas, även om kodraderna mainär i en annan ordning.

cy.get('main').then(function(e){
  console.log(e.text())
})
console.log('Cypress Test.')
Stubbar och spioner

A stubär ett sätt att simulera beteendet hos den funktion som testerna beror på. Istället för att anropa den faktiska funktionen ersätter stubben den funktionen och returnerar ett fördefinierat objekt. Det används vanligtvis i enhetstester, men kan också användas för slut -to-end testning.

A spyliknar , stubmen inte exakt samma. Det ändrar inte beteendet hos en funktion, utan lämnar det som det är. Det fångar upp lite information om hur funktionen anropas. Till exempel för att kontrollera om funktionen anropas med rätt parametrar, eller för att räkna hur ofta funktionen anropas.

Följande exempel visar a spyoch a stubi aktion. Via skapar const stub = cy.stub()vi stubelementet och bestämmer i nästa steg som falsereturneras för det första anropet och trueför det andra. Med hjälp av cy.on('window:confirm', stub)gör vi det stubanvändas för window:confirm'. I nästa steg skapar vi med cy.spy(win, 'confirm').as('winConfirmSpy')elementet Spy, som observerar anropet av 'window:confirm'. Nu testar vi att vid det första anropet avvisas borttagningen av kategorin och vid det andra anropet bekräftas det. Genom att göra det säkerställer vi att stubvi säkert kan förvänta oss vilka returvärden levereras. 'window:confirm'är inkapslad. @winConfirmSpyhjälper till att säkerställa att funktionen faktiskt anropades - och hur ofta den anropades.

// tests/System/integration/administrator/components/com_foos/FoosList.cy.js
...
const stub = cy.stub()

stub.onFirstCall().returns(false)
stub.onSecondCall().returns(true)

cy.on('window:confirm', stub)

cy.window().then(win => {
  cy.spy(win, 'confirm').as('winConfirmSpy')
})

cy.intercept('index.php?option=com_categories&view=categories&extension=com_foos').as('cat_delete')
cy.clickToolbarButton('empty trash');

cy.get('@winConfirmSpy').should('be.calledOnce')
cy.get('main').should('contain.text', testFoo.category)


cy.clickToolbarButton('empty trash');
cy.wait('@cat_delete')

cy.get('@winConfirmSpy').should('be.calledTwice')

cy.get('#system-message-container').contains('Category deleted.').should('exist')
...

Om det bara handlar om att ställa in ett fast värde för samtalet, 'window:confirm'kommer följande kod att göra jobbet.

cy.on("window:confirm", (s) => {
  return true;
});

slutsats

I den här artikeln har du sett grundläggande teori och praktiska funktioner för E2E-testning med Cypress. Jag använde Joomla-installationen för att demonstrera hur man skriver olika tester för att säkerställa att en Joomla-komponent på en webbplats fungerar som förväntat. Jag visade också hur man anpassar Cypress Testa Runner i filen cypress.json och hur man använder anpassade Cypress-kommandon. Detta gjordes med hjälp av lätta att följa exempel.

Jag hoppas att du gillade turnén genom Cypress med Joomla som exempel och att du kunde ta bort mycket kunskap och inspiration till dig själv.

An online collaborative community manual for Joomla! users, developers or anyone interested in learning more about Joomla! Currently, we have more than 9,000 articles written, maintained, and translated by our Joomla! community members. 

Observera att denna webbplats använder ett automatiskt översättningssystem för att hjälpa till med översättningen för de olika språken.Vi ber om ursäkt för eventuella fel eller felskrivningar som kan visas i de olika texterna.