Software testen en het makkelijk maken om dat te doen is al een paar jaar een interesse van mij. Deze interesse is nu meer gericht op frontend-applicaties dan vroeger. Meer specifiek, voor reactjs-applicaties. Het is nu een paar maanden geleden dat ik me verdiep in een vijf tot tien jaar oude reactjs-code die me inzichten en uitdagingen biedt.
Ik ontwikkel zelf al een paar jaar reactjs-code; een van de eerste open-sourceprojecten die ik heb gedeeld, heet testable . Het werd ergens rond 2020 uitgebracht en sindsdien heb ik een deel van mijn leerpad gewijd aan reactjs.
Onlangs heb ik gewerkt aan andere open-sourceprojecten die zich richten op het testbaarheidsgedeelte van dingen zoals json-tool en text-tool ; beide applicaties zijn open-source en worden geïmplementeerd op Snapcraft. Daarnaast voer ik regelmatig experimenten uit in een repository genaamd reactjs-playground . Het is de plek waar ik experimenteer met reactjs-functies en mijn leeruren eraan besteed. De ervaring die ik in die jaren heb opgedaan in close-sourced projecten en open-sourceprojecten, gaf me een basis waarmee ik een aantal veelvoorkomende valkuilen en voordelen kan identificeren.
Ontwikkelaars die meedoen aan het reactjs-patroon en de tooling, realiseren zich dat de bouwstenen die de bibliotheek biedt, eenvoudig te begrijpen en samen te stellen zijn. Het meest abstracte concept van een component kan worden gebruikt om snel gebruikersinterfaces samen te stellen. Het kan echter ook een bron van fouten zijn als het gaat om de structuur van de hiërarchie van een component. Het ontleden van systemen is een onderwerp dat al jaren wordt bestudeerd.
De grootte van de eenheid van abstractie in software is ook onderwerp van discussie geweest; sommigen beweren dat methoden en klassen een klein aantal regels moeten hebben, terwijl anderen de voorkeur geven aan een groter, goed gestructureerd stuk. Zoals in elk softwareproject, ongeacht de grootte, ben ik van plan de grootte te prefereren die meer context biedt en minder wrijving veroorzaakt op de cognitieve belasting tijdens het lezen van de code.
Toeval of niet, in mijn ervaring is dit mogelijk als ik duidelijke grenzen heb voor het stukje code waar ik aan werk, samen met de zakelijke context. Ik heb het magische getal daarvoor nog niet gevonden, maar mijn meting werd het aantal sprongen dat ik tussen bestanden moet maken om te begrijpen wat ik moet doen.
Hoe meer sprongen ik moet maken, hoe meer ik de context en informatie in mijn hoofd moet houden; dit wordt lastig bij het onderhouden van de codebase. Het ontleden van die componenten is een uitdaging, maar dit moet in acht worden genomen voor beter onderhoud van de codebase.
Voor professionele ontwikkeling is de globale status een basisvereiste; voor reactjs-applicaties is dat niet anders. De globale status is gemakkelijk te zien door gebruikers van de applicatie. Als u iets koopt en u voegt het product toe aan uw winkelwagen, kunt u het aantal items zien dat u hebt toegevoegd; dit is de globale status.
In reactjs-applicaties is het ooit veelgebruikte global state management-pakket redux nu minder in gebruik ten gunste van kleinere contexten rond grenzen in de applicatie. De community heeft echter verschillende meningen gedeeld over de noodzaak om redux te gebruiken voor global state management; dit leidde tot enkele blogs over het onderwerp:
Ondanks weerstand van de community is de acceptatie van react-redux, een bibliotheek die wordt gebruikt om redux te binden aan reactjs-componenten, de afgelopen vijf jaar gegroeid volgens npm trends . Op 1 februari 2025 is het aantal downloads dat in de npm voor redux wordt weergegeven 6.752.764.
Als je je afvraagt wat het alternatief hiervoor is, dan is het antwoord: reactjs context en hooks met query's.
Voor applicaties die afhankelijk zijn van het redux-pakket, is het een uitdaging om ermee weg te komen. De globale status is een van de afhankelijkheden die de testbaarheid van de applicatie verminderen. In mijn ervaring gaat de vereiste globale context vaak gepaard met een grotere complexiteit van de domeinkennis. Hoewel u misschien alleen een bepaald onderdeel of een deel van uw applicatie wilt testen, kunt u dat niet doen zonder de globale afhankelijkheden te delen die dit deel vereist.
ReactJs adopteren voor bedrijfsapplicaties was op het moment dat dit stuk werd geschreven een juiste beslissing voor onderhoudbaarheid (ondanks het kritieke moment dat Facebook had toen het het licentiemodel voor de bibliotheek veranderde). ReactJs biedt achterwaartse compatibiliteit voor API's die niet langer worden gebruikt, wat adoptie op de lange termijn een van de belangrijkste voordelen maakt.
Alsof componenten niet genoeg zijn als abstract concept om te omzeilen, zijn contexten ook een van de voordelen die reactjs biedt met betrekking tot testen. Ik ga hier dieper op in in een blogpost gewijd aan reactjs context testing . Het niveau van encapsulatie dat reactjs context biedt, is een van de belangrijkste voordelen voor het retrofitten van tests en het mogelijk maken van refactoring in reactjs code bases.
Omdat de decompositie van componenten en bedrijfslogica een uitdaging vormen, zijn contexten de tools die een betere herstructurering van de code mogelijk maken. In het testbaarheidsgedeelte van de dingen is dit ook een voordeel, omdat het een mechanisme is dat het verminderde aantal gebruikte test-doubles mogelijk maakt, wat leidt tot een meer testbare code.
Gezien zo'n lijst tot nu toe, is het testgedeelte hopelijk duidelijk geworden dat het afhangt van hoe de broncode is gestructureerd en hoe de grenzen van de applicatie zijn georganiseerd. Echter, net zoals de testcode afhankelijk is van de productiecode, kunnen de testdubbels worden gebruikt om de scope te helpen afbakenen. Het denkproces voor retrofitting-tests bestaat uit een paar eenvoudige stappen.
2.1. Is de test geslaagd?
2.1.1 - Yes → Check if the feedback is correct 2.1.2 - No → Provide the dependency without questioning
Let op: De hier beschreven aanpak is vergelijkbaar met wat wordt beschreven als de karakteriseringstest die Michael Feathers beschrijft in de
Boek Effectief werken met oude code.
De komst van LLM's kan ook inzichten bieden tijdens het schrijven van de eerste tests en het retrofitten van codebases zonder enige tests. Elke stap in deze strategie is bedoeld om iteratief te zijn; het is ook mogelijk om verschillende stijlen van TDD te combineren. De voorgestelde aanpak is niet om te stoppen met alles te doen en alle mogelijke testcases en alle mogelijke problemen die de codebase heeft te retrofitten, het is een puzzelspel. Elke geteste functionaliteit is een stukje dat in de puzzel past.
Dezelfde aanpak wordt voorgesteld voor refactoring. Ontwikkelaars zouden constant een stuk code moeten kunnen refactoren. Het is geen ander project en het is niet bedoeld om er alleen maar aan gewijd te worden. Het hoofddoel is om de codebase beter te maken dan hij was. Iteratief. Er zijn een paar aspecten die gebruikt kunnen worden om tests in codebases te retrofitten die er geen hebben:
Centraal in deze aanpak staat het leerproces; elke stap in het leren van de codebase wordt meegenomen.
Copilot kan worden gebruikt om de eerste verkennende test, zoals beschreven in de vorige sectie, te automatiseren. Door drie regels te volgen, kunt u beginnen met het identificeren van afhankelijkheden en het schrijven van een set uitgebreide tests die als basis kunnen dienen.
Laten we Testable als voorbeeld nemen; het is een applicatie die meer dan vijf jaar geleden in ReactJS is geschreven en enzyme gebruikt voor het testen. Voor de huidige standaard is de bibliotheek die het heeft overgenomen vitest of jest, samen met de testbibliotheek. Om de testcases voor testable aan te passen en te profiteren van LLM's, kunnen we copilot gebruiken om ons te helpen met het zware werk.
Testable bestaat uit een gamified ervaring met status, levels en gebruikersvoortgang door verschillende uitdagingen. Het component dat in de volgende afbeelding wordt beschreven, is het component dat wordt gebruikt om dialogen te tonen en vooruit te navigeren in de geschiedenis van de ervaring. Met behulp van Copilot voor vscode heb ik het gevraagd om een test te schrijven met de productiecode waarin we geïnteresseerd zijn:
Zodra de vraag is geladen en de code is geanalyseerd, wordt het antwoord gegeven.
Samen met het antwoord merkte Copilot op dat ik nog geen testbibliotheek gebruik en het suggereert dat ik dat wel moet doen, en daarna wordt de gegenereerde testcase getoond. Als de testcase wordt uitgevoerd zoals deze wordt getoond, slaagt de test niet en wordt deze gemarkeerd als rood.
Dit punt is belangrijk omdat het laat zien dat er rekening moet worden gehouden met extra instellingen en Copilot kon er niet achter komen. Deze zijn als volgt:
De eerder in deze sectie beschreven flow is hier ook van toepassing. De feedbackloop is nu om te beginnen met het oplossen van die problemen en de tests uit te voeren totdat de TDD-cyclus is voltooid.