Zoeken…


Invoering

Het testen van eenheden is een integraal onderdeel van testgestuurde ontwikkeling en een belangrijk kenmerk voor het bouwen van een robuuste toepassing. In Java wordt het testen van eenheden bijna uitsluitend uitgevoerd met behulp van externe bibliotheken en frameworks, waarvan de meeste een eigen documentatietag hebben. Deze stub dient als middel om de lezer kennis te laten maken met de beschikbare hulpmiddelen en hun respectieve documentatie.

Opmerkingen

Unit Test Frameworks

Er zijn talloze frameworks beschikbaar voor het testen van eenheden binnen Java. De meest populaire optie is veruit JUnit. Het is gedocumenteerd als volgt:

JUnit

JUnit4 - Voorgestelde tag voor JUnit4-functies; nog niet geïmplementeerd .

Er bestaan nog andere testkaders voor eenheden en er is documentatie beschikbaar:

TestNG

Hulpmiddelen voor het testen van eenheden

Er zijn verschillende andere tools die worden gebruikt voor het testen van eenheden:

Mockito - Mocking framework; Hiermee kunnen objecten worden nagebootst. Handig voor het nabootsen van het verwachte gedrag van een externe eenheid binnen de test van een bepaalde eenheid, om het gedrag van de externe eenheid niet te koppelen aan de tests van de gegeven eenheid.

JBehave - BDD Framework. Hiermee kunnen tests worden gekoppeld aan gebruikersgedrag (waardoor validatie van vereisten / scenario's mogelijk is). Geen documententag beschikbaar op het moment van schrijven; hier is een externe link .

Wat is testen van eenheden?

Dit is een beetje een inleiding. Het wordt er meestal in gestopt omdat documentatie gedwongen is een voorbeeld te hebben, zelfs als het bedoeld is als een stubartikel. Als u de basisprincipes van het testen van eenheden al kent, kunt u doorgaan naar de opmerkingen, waar specifieke kaders worden genoemd.

Eenheidstesten zorgen ervoor dat een bepaalde module zich gedraagt zoals verwacht. In grootschalige toepassingen is het zorgen voor de juiste uitvoering van modules in een vacuüm een integraal onderdeel van het waarborgen van de betrouwbaarheid van de toepassing.

Beschouw het volgende (triviale) pseudo-voorbeeld:

public class Example {
  public static void main (String args[]) {
    new Example();
  }

  // Application-level test.
  public Example() {
    Consumer c = new Consumer();
    System.out.println("VALUE = " + c.getVal());
  }

  // Your Module.
  class Consumer {
    private Capitalizer c;
  
    public Consumer() {
      c = new Capitalizer();
    }

    public String getVal() {
      return c.getVal();
    }
  }

  // Another team's module.
  class Capitalizer {
    private DataReader dr;
  
    public Capitalizer() {
      dr = new DataReader();
    }

    public String getVal() {
      return dr.readVal().toUpperCase();
    }
  }

  // Another team's module.
  class DataReader {
    public String readVal() {
      // Refers to a file somewhere in your application deployment, or
      // perhaps retrieved over a deployment-specific network.
      File f; 
      String s = "data";
      // ... Read data from f into s ...
      return s;
    }
  }
}

Dus dit voorbeeld is triviaal; DataReader haalt de gegevens uit een bestand op en geeft deze door aan de Capitalizer , die alle tekens omzet in hoofdletters, die vervolgens worden doorgegeven aan de Consumer . Maar de DataReader is sterk gekoppeld aan onze applicatieomgeving, dus we stellen het testen van deze keten uit totdat we klaar zijn om een testrelease te implementeren.

Neem nu aan dat ergens in een release, om onbekende redenen, de methode getVal() in Capitalizer gewijzigd van het retourneren van een toUpperCase() naar een String toLowerCase() :

  // Another team's module.
  class Capitalizer {
    ...

    public String getVal() {
      return dr.readVal().toLowerCase();
    }
  }

Dit breekt duidelijk het verwachte gedrag. Maar vanwege de moeizame processen die betrokken zijn bij de uitvoering van de DataReader , zullen we dit pas merken bij onze volgende testimplementatie. Dus dagen / weken / maanden gaan voorbij met deze bug in ons systeem, en dan ziet de productmanager dit, en wendt zich onmiddellijk tot u, de teamleider die geassocieerd is met de Consumer . "Waarom gebeurt dit? Wat hebben jullie veranderd?" Uiteraard heb je geen idee. Je hebt geen idee wat er aan de hand is. Je hebt geen code gewijzigd die dit zou moeten raken; waarom is het plotseling kapot?

Uiteindelijk, na discussie tussen de teams en samenwerking, wordt het probleem opgespoord en het probleem opgelost. Maar het roept de vraag op; hoe kon dit voorkomen worden?

Er zijn twee voor de hand liggende dingen:

Tests moeten worden geautomatiseerd

Onze afhankelijkheid van handmatig testen liet deze bug veel te lang onopgemerkt voorbijgaan. We hebben een manier nodig om het proces te automatiseren waarmee bugs direct worden geïntroduceerd. Nog geen 5 weken. Nog geen 5 dagen. Nog geen 5 minuten. Nu.

Je moet begrijpen dat ik in dit voorbeeld een heel triviale bug heb geuit die werd geïntroduceerd en onopgemerkt. In een industriële toepassing, waarbij tientallen modules voortdurend worden bijgewerkt, kunnen deze overal kruipen. Je repareert iets met één module, alleen om te beseffen dat juist het gedrag waarop je "repareerde" ergens anders op vertrouwde (intern of extern).

Zonder rigoureuze validatie zullen dingen het systeem binnensluipen. Het is mogelijk dat, als het ver genoeg wordt genegeerd, dit zoveel extra werk oplevert bij het proberen om wijzigingen te repareren (en vervolgens die fixes, enz. Te repareren), dat een product daadwerkelijk zal toenemen in het resterende werk als er moeite in wordt gedaan. U wilt niet in deze situatie zijn.

Tests moeten fijnmazig zijn

Het tweede probleem dat in ons bovenstaande voorbeeld wordt opgemerkt, is de hoeveelheid tijd die nodig was om de bug op te sporen. De productmanager pingelde je toen de testers het opmerkten, je onderzocht en ontdekte dat de Capitalizer ogenschijnlijk slechte gegevens retourneerde, je pingelde het Capitalizer team met je bevindingen, ze hebben onderzocht, enz. Enz. Enz.

Hetzelfde punt dat ik hierboven heb gemaakt over de hoeveelheid en moeilijkheid van dit triviale voorbeeld. Het is duidelijk dat iedereen die redelijk vertrouwd is met Java het geïntroduceerde probleem snel zou kunnen vinden. Maar het is vaak veel, veel moeilijker om problemen op te sporen en te communiceren. Misschien heeft het Capitalizer team je een JAR zonder bron verstrekt. Misschien bevinden ze zich aan de andere kant van de wereld en zijn de communicatie-uren erg beperkt (misschien tot e-mails die eenmaal per dag worden verzonden). Het kan ertoe leiden dat het weken of langer duurt voordat bugs zijn getraceerd (en nogmaals, er kunnen er meerdere zijn voor een bepaalde release).

Om dit tegen te gaan, willen we rigoureuze testen op een zo fijn mogelijk niveau (u wilt ook grofkorrelige testen om ervoor te zorgen dat modules correct samenwerken, maar dat is hier niet ons focuspunt). We willen rigoureus specificeren hoe alle naar buiten gerichte functionaliteit (minimaal) werkt en testen op die functionaliteit.

Voer unit-testing uit

Stel je voor dat we een test hadden die er specifiek voor zorgde dat de methode getVal() van Capitalizer een hoofdletter voor een gegeven invoerreeks retourneerde. Stel je bovendien voor dat de test werd uitgevoerd voordat we zelfs maar een code hadden vastgelegd. De bug die in het systeem is geïntroduceerd (dat wil zeggen toUpperCase() wordt vervangen door toLowerCase() ) zou geen problemen veroorzaken omdat de bug nooit in het systeem zou worden geïntroduceerd. We zouden het in een test vangen, de ontwikkelaar zou (hopelijk) hun fout beseffen en er zou een alternatieve oplossing worden gevonden om het beoogde effect te introduceren.

Er zijn hier enkele omissies gemaakt over hoe deze tests te implementeren, maar die worden behandeld in de kader-specifieke documentatie (gekoppeld in de opmerkingen). Hopelijk dient dit als een voorbeeld van waarom unit testing belangrijk is.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow