- Leren
- Informatica & ICT
- Programmeren
- Java programmeren: klassen, overerving, collecties en streams
- Klassendiagrammen en unittests in Java
Pakketten, klassendiagrammen en unittests in Java
BeginnerLes 10 van 11 in Cursus Java programmeren: klassen, overerving, collecties en streams
Wat je leert
- Groepeer verwante klassen in pakketten, sla elk bronbestand op waar de compiler en buildtools het verwachten en gebruik klassen uit andere pakketten met imports en het juiste toegangsniveau.
- Declareer pakketten en laat de mappenstructuur daarop aansluitenSchrijf een pakketdeclaratie, kies een gebruikelijke pakketnaam, sla het bestand op in de bijbehorende map en verwijs naar klassen met hun volledig gekwalificeerde naam.
- Gebruik klassen uit andere pakketten met importsImporteer afzonderlijke klassen of hele pakketten, weet voor welke klassen geen import nodig is, los conflicten tussen klassen met dezelfde naam op en gebruik statische imports.
- Regel de toegang tussen pakkettenKies tussen public, protected, pakkettoegang en private, zodat elk pakket alleen beschikbaar maakt wat andere pakketten nodig hebben.
- Declareer pakketten en laat de mappenstructuur daarop aansluiten
- Lees en teken klassendiagrammen die klassen, hun leden en de relaties ertussen tonen en vertaal in beide richtingen tussen een diagram en Java-code.
- Teken een afzonderlijke klasse en lees de weergave ervanToon de naam, attributen en operaties van een klasse met typen en zichtbaarheid en markeer statische leden, abstracte klassen en interfaces.
- Geef associaties en multipliciteit weerVerbind klassen met associaties, lees en schrijf multipliciteiten en navigeerbaarheid en onderscheid compositie, aggregatie en afhankelijkheid.
- Modelleer overerving en vertaal diagrammen naar code en omgekeerdToon overerving en implementatie van interfaces, schrijf Java-code op basis van een diagram en teken een diagram op basis van bestaande code.
- Teken een afzonderlijke klasse en lees de weergave ervan
- Verdeel een programma zo dat de gebruikersinterface, de applicatielogica en de gegevensopslag in afzonderlijke klassen en pakketten staan, met afhankelijkheden die in één richting wijzen.
- Scheid de gebruikersinterface van de programmalogicaHoud het lezen van invoer en weergeven van uitvoer in een gebruikersinterfaceklasse, bewaar de regels van het programma in logicaklassen en verbind beide in de main-methode.
- Orden lagen, pakketten en afhankelijkhedenDeel klassen in een presentatielaag, een logicalaag en een gegevenstoegangslaag in, laat afhankelijkheden naar beneden wijzen en verbind de lagen via interfaces.
- Scheid de gebruikersinterface van de programmalogica
- Schrijf automatische unittests met JUnit, kies testgevallen die fouten aan het licht brengen, houd tests onafhankelijk en leesbaar en schrijf tests voordat je nieuwe code ontwikkelt.
- Schrijf en voer je eerste JUnit-test uitMaak een testklasse, schrijf testmethoden met asserties, voer ze uit en lees het resultaat van een geslaagde en een mislukte test.
- Kies testgevallen die fouten opsporenKies gebruikelijke invoer, grenswaarden en ongeldige invoer, test verwachte excepties en geef elke test één duidelijk doel en een duidelijke naam.
- Houd tests onafhankelijk en onderhoudbaarMaak vóór elke test nieuwe testobjecten, vermijd gedeelde toestand, voer één test uit met meerdere invoerwaarden en test de logica afzonderlijk van invoer en uitvoer.
- Ontwikkel code met TDD door eerst tests te schrijvenVolg vanuit een testlijst de cyclus rood–groen–refactoren, neem kleine stappen en weet wat testgestuurde ontwikkeling wel en niet belooft.
- Schrijf en voer je eerste JUnit-test uit
- Gebruik een buildtool om bibliotheken te beheren, volg de standaardprojectindeling en compileer, test en verpak een programma met één opdracht.
- Zet een Maven-project op en bouw hetBeschrijf een project in pom.xml met coördinaten en afhankelijkheden, volg de standaardmapindeling en doorloop de buildlevenscyclus.
- Zet een Gradle-project op en herstel buildfoutenSchrijf het equivalente Gradle-buildscript, voer JUnit-tests uit op het JUnit Platform, vergelijk de twee tools en zoek de oorzaak van een mislukte build.
- Zet een Maven-project op en bouw het
Klassendiagrammen en unittests in Java: uitleg
1Deel een Java-programma in pakketten in
Naarmate een programma groeit, hebben klassenamen en mappen een structuur nodig die zowel mensen als Java kunnen volgen. Pakketten groeperen verwante typen en bepalen toegangsgrenzen. Imports maken die typen gemakkelijk te gebruiken zonder hun interne werking open te stellen. Je verbindt een declaratie met het bronpad, lost namen tussen pakketten op en bepaalt welke klassen een ander deel van het programma mag gebruiken en welke constructoren en methoden het mag aanroepen.
1.1 Pakketnamen en bronmappen
Een pakket groepeert verwante klassen en interfaces onder één naam. Het scheidt ook namen: twee klassen met de naam Parcel kunnen naast elkaar bestaan als hun pakketnamen verschillen. Een pakket biedt Java zowel een toegangsgrens als een manier om bestanden te ordenen.
Declareer het pakket eerst
Een gewoon bronbestand begint met de pakketdeclaratie, gevolgd door imports en daarna de klasse. Vóór de declaratie mag commentaar staan. Bijvoorbeeld:
package com.example.parcels.domain;
public class Parcel {
private int weightGrams;
public Parcel(int weightGrams) {
this.weightGrams = weightGrams;
}
}
Elk type op het hoogste niveau in dat bestand hoort bij com.example.parcels.domain. Een bestand heeft hoogstens één pakketdeclaratie. De publieke klasse Parcel hoort in Parcel.java; alleen de map wijzigen verandert het gedeclareerde pakket niet.
Zet punten om in mappen
De broncodemap is de map waar het pakketpad begint. In een standaard Maven- of Gradle-project begint de Java-hoofdcode bij src/main/java. Vervang elke punt in de pakketnaam door een schuine streep en voeg daarna de klassenaam toe:
src/main/java/
com/example/parcels/domain/Parcel.java
Het pakket is com.example.parcels.domain, niet src.main.java.com.example.parcels.domain. De eerste drie mappen beschrijven de buildindeling; ze maken geen deel uit van de Java-naam. Als een IDE meldt dat een pakket niet overeenkomt, vergelijk je de declaratie met het pad onder de broncodemap. Verplaats het bestand of corrigeer de declaratie zodat ze overeenkomen.
Pakketnamen gebruiken kleine letters. Organisaties draaien vaak hun domein om: example.com wordt com.example, gevolgd door het project en het onderdeel ervan. Een project zonder domein kan parcels.domain gebruiken. Als een omgekeerd domeindeel een koppelteken bevat, vervang je dat door een liggend streepje, zoals in org.example.hyphenated_name. Vermijd de naamruimte java en javax van het platform.
Gebruik de volledige klassenaam
Een volledig gekwalificeerde naam voegt de pakketnaam en de eenvoudige klassenaam samen: com.example.parcels.domain.Parcel. Die identificeert de klasse ook als een ander pakket eveneens een Parcel heeft. Code kan een toegankelijke klasse zonder import voluit noemen:
java.util.ArrayList<String> names = new java.util.ArrayList<>();
Om een main-klasse in een pakket uit te voeren, geef je de volledige naam en een klassenpad dat naar de hoofdmap van de gecompileerde pakketboom wijst. Vanuit de projectmap:
javac -d out src/main/java/com/example/parcels/Main.java src/main/java/com/example/parcels/domain/Parcel.java
java -cp out com.example.parcels.Main
Hier maakt javac -d out de uitvoerboom en zoekt java -cp out vanaf de hoofdmap van die boom. Als je java Main vanuit een pakketmap start, ontbreekt het pakketdeel van de identiteit van de klasse. Buildtools en IDE’s beheren deze opdrachten normaal gesproken.
Onderscheid naburige pakketten
com.example.parcels en com.example.parcels.domain zijn afzonderlijke pakketten. Een gedeeld begin van de naam geeft geen extra toegangsrechten. Een bestand zonder declaratie hoort bij het naamloze pakket. Benoemde pakketten kunnen de klassen daaruit niet importeren, dus gebruik benoemde pakketten in een programma met meerdere onderdelen.
Belangrijkste punten
- Plaats de pakketdeclaratie vóór imports en de klasse.
- Laat het pakketpad onder de broncodemap overeenkomen met de pakketnaam; neem
src/main/javaniet op in de pakketnaam. - Gebruik kleine letters voor pakketnamen en gebruik waar nodig de volledig gekwalificeerde klassenaam.
- Een gedeeld begin van pakketnamen geeft geen gedeelde pakkettoegang.
- Voer klassen in pakketten uit vanaf de hoofdmap van de gecompileerde boom met hun volledig gekwalificeerde namen.
1.2 Imports en conflicten tussen klassenamen
Met een import kan één bronbestand de eenvoudige naam van een toegankelijk type gebruiken. Een import vertelt de compiler wat de naam betekent. Een import kopieert de klasse niet, downloadt geen bibliotheek en maakt een verborgen klasse niet public.
Lees het begin van een bronbestand
De gebruikersinterface hoort bij één pakket en gebruikt klassen uit twee andere:
package com.example.parcels.ui;
import com.example.parcels.domain.Parcel;
import com.example.parcels.domain.ShippingCalculator;
import java.util.Scanner;
public class TextUI {
private Scanner scanner;
private ShippingCalculator calculator;
}
Parcel en ShippingCalculator komen uit het domeinpakket van het programma; Scanner komt uit de Java-bibliotheek. Elke import geldt alleen voor dit bestand. Een ander bestand dat Scanner gebruikt, heeft een eigen import of de volledige naam java.util.Scanner nodig.
Kies één type of een joker
import java.util.Scanner; is een import van één type. De exacte klasse is zichtbaar voor de lezer. import java.util.*; is een jokerimport: toegankelijke typen in dat pakket kunnen bij gebruik met hun eenvoudige naam worden aangeduid. De joker laadt geen ongebruikte klassen in het programma en vertraagt het niet.
De joker stopt bij dat pakket. Hij omvat niet java.util.function.Function of java.util.stream.Collectors, die in afzonderlijke pakketten staan. Dezelfde regel geldt voor je eigen code: import com.example.parcels.*; bereikt com.example.parcels.domain.Parcel niet.
In drie gevallen is geen import nodig: voor typen in hetzelfde pakket, automatisch beschikbare typen in java.lang en typen die voluit worden genoemd. Daarom verschijnen String, System, Math, Integer en Object vaak zonder import.
Los een gedeelde eenvoudige naam op
Zowel java.util als java.awt heeft een klasse met de naam List. Twee imports van deze verschillende klassen als afzonderlijke typen veroorzaken een compilatiefout. Twee jokerimports kunnen naast elkaar bestaan, maar de eenvoudige naam List wordt bij gebruik dubbelzinnig. Een import van één type heeft voorrang op een jokerimport.
Als beide klassen nodig zijn, importeer je de ene en schrijf je de andere voluit:
import java.util.List;
class NameDisplay {
private List<String> names;
private java.awt.List widget;
}
Java-imports bieden geen aliassyntaxis zoals import ... as .... Met de volledig gekwalificeerde naam kun je het tweede type onderscheiden.
Importeer een statisch lid
Een statische import maakt de naam van de bijbehorende klasse overbodig bij het aanroepen van een statisch lid:
import static java.lang.Math.sqrt;
Het bestand kan nu sqrt(16.0) aanroepen. Testcode gebruikt vaak import static org.junit.jupiter.api.Assertions.assertEquals;, zodat asserties als directe controles lezen. Gebruik statische imports in gewone programmacode selectief, zodat lezers kunnen blijven zien waar operaties vandaan komen.
Volg de foutmelding
“Cannot find symbol” kan wijzen op een ontbrekende import of een verkeerd gespeld type. “Package ... does not exist” wijst op een probleem met de spelling van een pakket, de broncodemap of een afhankelijkheid. “Is not public ... cannot be accessed from outside package” betekent dat Java het type heeft gevonden, maar dat de toegang is geblokkeerd. Een extra import kan die toegangsfout niet oplossen; bekijk de klassedeclaratie en de leden die je nodig hebt.
Belangrijkste punten
- Een import lost namen op in één bestand en verleent geen toegangsrechten.
- Een joker omvat typen in één pakket, zonder de onderliggende pakketten.
- Typen in hetzelfde pakket en typen uit
java.langhebben geen import nodig. - Gebruik één import van een type en één volledig gekwalificeerde naam als twee benodigde klassen dezelfde naam hebben.
- Een statische import maakt een statisch lid aanroepbaar zonder de klassenaam.
- Onderscheid een fout bij het oplossen van een naam van een toegangsfout.
1.3 Een kleine publieke interface
Een pakket moet de klassen en methoden beschikbaar maken die andere onderdelen nodig hebben en tegelijk implementatiedetails verborgen houden. Toegangsmodifiers maken die grens afdwingbaar. Een import kan een naam verkorten, maar kan niet voorbij een grens die de toegangsregels van Java afsluiten.
Kies de toegang voor een klasselid
| Toegang | Wie kan het lid gebruiken? |
|---|---|
public | Code die toegang heeft tot het declarerende type |
protected | Hetzelfde pakket, plus toegang vanuit subklassen in andere pakketten |
| Geen modifier | Precies hetzelfde pakket |
private | De declarerende klasse en haar geneste typen |
Geen modifier betekent pakkettoegang voor gewone klasseleden en constructoren. Er is geen sleutelwoord default voor. Een klasse op het hoogste niveau heeft maar twee mogelijkheden: public of pakkettoegang. Een publieke klasse op het hoogste niveau wordt opgeslagen in een bestand met haar naam, zoals ShippingCalculator.java.
Controleer het type en de operatie
Stel dat de gebruikersinterface een pakket moet construeren. Een publieke klasse alleen is niet genoeg:
package com.example.parcels.domain;
public class Parcel {
private int weightGrams;
Parcel(int weightGrams) {
this.weightGrams = weightGrams;
}
}
De constructor heeft pakkettoegang. Code in com.example.parcels.ui kan Parcel noemen, maar kan new Parcel(1200) niet aanroepen. Maak die constructor public als het maken van pakketten deel uitmaakt van de publieke diensten van het pakket. Houd weightGrams private en bied een geschikte operatie aan in plaats van het veld open te stellen.
Bij pakkettoegang wordt de volledige pakketnaam vergeleken. Een pakket dat een bovenliggend pakket lijkt en een pakket dat een onderliggend pakket lijkt, zijn geen van beide hetzelfde pakket. Code in com.example.parcels krijgt geen speciale toegang tot hulpklassen in com.example.parcels.domain.
Begrijp beschermde toegang
protected geeft toegang vanuit niet-verwante klassen in het declarerende pakket en vanuit subklassen. Tussen pakketten verloopt de toegang tot een instantielid via de overervingscontext van de subklasse. Een subklasse kan haar eigen overgeërfde veld gebruiken, maar kan niet via een willekeurig superklasseobject de pakketgrens omzeilen.
Als Item bijvoorbeeld protected int weightGrams declareert, kan een Letter extends Item in een ander pakket this.weightGrams gebruiken. Binnen Letter staat een parameter met alleen het type Item geen item.weightGrams toe; een referentie met het type Letter staat die overgeërfde toegang wel toe. Deze beperking voorkomt dat de rechten van een subklasse algemene toegang tot elke Item worden.
Verberg hulpklassen bewust
Maak ShippingCalculator en feeInCents public als de gebruikersinterface die aanroept. Een ondersteunende klasse WeightBands kan pakkettoegang houden. Code buiten het domeinpakket is dan afhankelijk van het gedrag van de calculator en niet van de manier waarop die de tabel opslaat. Als je die hulpklasse vervangt, zijn elders minder wijzigingen nodig.
Tests kunnen dezelfde pakketnaam gebruiken als de klasse die ze testen, terwijl ze onder src/test/java staan. Dat geeft ze pakkettoegang zonder testcode tussen productiebestanden te plaatsen. Het geeft geen toegang tot private velden. Als je elk lid public maakt om een test te laten slagen, ontstaan op meer plaatsen afhankelijkheden van interne details; test in plaats daarvan publiek gedrag of een nuttige operatie op pakketniveau.
Belangrijkste punten
- Klasseleden met pakkettoegang zijn alleen toegankelijk in precies hetzelfde pakket.
- Een aanroeper heeft toegang nodig tot zowel een klasse als de constructor of methode die hij gebruikt.
- Beschermde toegang omvat toegang binnen hetzelfde pakket en beperkte toegang vanuit subklassen in andere pakketten.
- Houd velden private en maak alleen de operaties beschikbaar die een ander pakket nodig heeft.
2Schets de programmastructuur met UML-klassendiagrammen
Een klassendiagram geeft je een compact overzicht van een programma voordat je de methodebody’s leest. Kaders beschrijven typen en operaties; lijnen beschrijven verbindingen, aantallen en subtyperelaties. Je leert die aanduidingen nauwkeurig lezen en vertalen naar Java-declaraties. Het pakketprogramma biedt voorbeelden van afzonderlijke klassen, collecties van geassocieerde objecten en onderling uitwisselbare repository-implementaties.
2.1 Wat een UML-klassekader vertelt
Een UML-klassendiagram toont de statische structuur: klassen, hun leden en relaties. Het helpt een lezer te zien welke soorten objecten bestaan en wat ze bieden. Het volgt niet de volgorde van methodeaanroepen tijdens de uitvoering.
Lees de drie vakken
Een klassekader heeft de klassenaam bovenaan, attributen in het midden en operaties onderaan. Een attribuut stelt meestal een Java-veld voor; een operatie stelt de zichtbare signatuur van een methode voor. Een schets kan vakken weglaten die niet bijdragen aan het doel.
Voor een pakket is een tekstuele weergave van het kader:
Parcel
-----------------------------
- weightGrams: int
- recipient: String
-----------------------------
+ Parcel(weightGrams: int, recipient: String)
+ getWeightGrams(): int
+ isHeavierThan(limitGrams: int): boolean
De constructor heeft de naam van de klasse en geen retourtype. De methode isHeavierThan accepteert een geheel getal met de naam limitGrams en geeft een booleaanse waarde terug.
Vertaal de volgorde van namen en typen
UML zet de naam vóór het type: weightGrams: int. Java zet het type eerst: int weightGrams. Een operatie volgt voor parameters hetzelfde patroon met eerst de naam en zet daarna het retourtype achter het sluithaakje.
private int weightGrams;
public boolean isHeavierThan(int limitGrams) {
return weightGrams > limitGrams;
}
Deze code komt overeen met - weightGrams: int en + isHeavierThan(limitGrams: int): boolean. Een methodebody ontbreekt meestal in het diagram; de operatie vermeldt wat kan worden aangeroepen, niet het algoritme.
Behoud zichtbaarheid en speciale aanduidingen
De zichtbaarheidssymbolen zijn + public, - private, # protected en ~ pakkettoegang. Zet het symbool vóór de naam van het lid. Als een gegeven diagram een symbool weglaat, verzin dan geen zichtbaarheid; voeg het bedoelde symbool toe wanneer je code documenteert.
Een statisch lid is onderstreept. Het hoort bij de klasse en niet bij elk object. Een abstracte klasse of operatie wordt cursief geschreven of gemarkeerd met {abstract} wanneer cursief onhandig is. De abstracte markering betekent dat een subklasse ontbrekend gedrag moet leveren voordat een object van een concrete klasse kan worden gemaakt.
Een interfacekader draagt «interface» boven de naam. ParcelRepository kan bijvoorbeeld + save(parcel: Parcel): void en + findAll(): List<Parcel> aanbieden. Het kader vermeldt een contract; het kiest geen opslagimplementatie.
Kies de details die de lezer nodig heeft
Een planningsschets toont misschien alleen Parcel, Shipment en hun belangrijkste operaties. Een diagram voor het implementeren van een klasse heeft typen en zichtbaarheid nodig. Als je elke gebruikelijke getter vermeldt, kun je de relaties verbergen die het diagram moest tonen. Kies nuttige leden en houd ze in overeenstemming met de code: een privéveld in Java mag niet met een public +-symbool verschijnen en een statische methode moet worden onderstreept.
Belangrijkste punten
- Een klassekader scheidt de naam, attributen en operaties.
- UML gebruikt
name: Type; Java gebruiktType name. - Gebruik
+,-,#en~voor public, private, protected en pakkettoegang. - Onderstreep statische leden; markeer abstracte elementen met cursief of
{abstract}. - Een interfacekader toont
«interface»en de operaties in het contract.
2.2 Lees de uiteinden van relatielijnen
Een associatie toont een structurele verbinding tussen objecten. In Java bewaart een object vaak een referentie naar een ander object in een veld. De lijn kan ook tonen hoeveel objecten zijn toegestaan en in welke richting de referentie kan worden gevolgd.
Tel vanaf het tegenoverliggende uiteinde
De multipliciteit staat naast de klasse waarvan die de objecten telt. Als 1..* naast Parcel staat en 1 naast Shipment, heeft één zending één of meer pakketten en hoort elk pakket bij precies één zending. Lees elk uiteinde afzonderlijk, beginnend bij één object aan het tegenoverliggende uiteinde.
| Multipliciteit | Betekenis |
|---|---|
1 | Precies één |
0..1 | Nul of één |
* of 0..* | Een willekeurig aantal, inclusief nul |
1..* | Minstens één |
2..4 | Twee tot en met vier |
Eén referentie kan 1 of 0..1 voorstellen; een collectie stelt een uiteinde met meerdere waarden voor. Een Java-List alleen dwingt geen ondergrens af. Een Shipment met de pakketregel 1..* moet een lege beginlijst afwijzen en voorkomen dat latere operaties de lijst leeg achterlaten. Evenzo heeft een verplichte enkele referentie een controle op null nodig.
Volg de referentie
Een open pijlpunt bij Parcel betekent dat een referentie van Shipment naar Parcel kan worden gevolgd. Een klassendiagram kan de navigeerbaarheid ongespecificeerd laten. Volgens de hier gebruikte onderwijsconventie markeren pijlen eenrichtingsreferenties; een lijn zonder aanduiding kan toegang in beide richtingen of een ongespecificeerde richting betekenen. Leid niet uitsluitend uit een pijl naar Parcel af dat er een veld terug bestaat.
Een rolnaam zoals parcels geeft aan wat de verbinding vanuit de zending betekent. Toon die referentie als associatie of als - parcels: List<Parcel> in het kader. Beide zonder reden tonen kan één veld op twee verschillende delen van de toestand laten lijken.
Kies de betekenis van de geheel-deelrelatie
Compositie gebruikt een gevulde ruit bij het geheel. Een deel hoort tegelijk bij hoogstens één samengesteld geheel en wordt normaal gesproken met dat geheel verwijderd, zoals een bestelling en de bijbehorende bestelregels. Aggregatie gebruikt een open ruit voor een lossere groepering waarvan de delen zelfstandig kunnen bestaan. Een team kan spelers groeperen zonder hun levensduur te bepalen.
Java heeft geen sleutelwoord voor een ruit en dwingt UML-eigenaarschap niet af alleen omdat er een lijst bestaat. Modelleer compositie alleen wanneer het ontwerp die eigenaarschapsregel bevat. Als het ontwerp alleen zegt dat het ene object het andere kent, is een gewone associatie duidelijker dan een ruit waarvoor geen onderbouwing bestaat.
Toon gebruik zonder eigenaarschap te beweren
Een afhankelijkheid is een onderbroken lijn met een open pijlpunt naar het type dat wordt gebruikt. Een calculator met feeInCents(Parcel parcel) is afhankelijk van Parcel, omdat de methodesignatuur dat type gebruikt. Een afhankelijkheid is een algemene gebruiksrelatie; die is niet beperkt tot code zonder velden. Een associatie geeft specifiekere structurele informatie wanneer een veld belangrijk is voor het model.
Vraag jezelf vóór het tekenen van een lijn af wat de code of eis vastlegt: een referentie, een toegestaan aantal, gebruik of eigenaarschap. Een collectie legt opslag van meerdere waarden vast, maar op zichzelf geen exclusief eigenaarschap of minimumaantal.
Belangrijkste punten
- De multipliciteit naast een klasse telt haar objecten voor één object aan het andere uiteinde.
- Een collectietype dwingt geen minimale of maximale multipliciteit af.
- Een associatiepijl wijst naar het type dat vanuit de bron kan worden bereikt.
- Plaats een compositie- of aggregatieruit aan de kant van het geheel.
- Een afhankelijkheid is een onderbroken pijl voor gebruik; een associatie beschrijft een structurele verbinding.
- Toon een referentie één keer, tenzij het diagram bewust beide weergaven nodig heeft.
2.3 Van een diagram naar Java-declaraties
Zowel overerving als de implementatie van een interface verbindt een specifiek type met een algemener type. UML gebruikt een open driehoek bij het algemene type, maar de lijnstijl maakt onderscheid tussen de twee relaties. Dat onderscheid vertelt je welke Java-declaratie je moet schrijven.
Lees de driehoek en de lijn
Een doorgetrokken lijn met een open driehoek bij Item betekent dat een subklasse zoals Letter de klasse Item uitbreidt. Dit is generalisatie. Een onderbroken lijn met een open driehoek bij ParcelRepository betekent dat een klasse zoals InMemoryParcelRepository die interface implementeert. Dit is realisatie.
De driehoek raakt altijd de superklasse of interface. Een onderbroken lijn met een open pijlpunt, zonder driehoek, betekent daarentegen een afhankelijkheid. De lijnstijl en de pijlpunt dragen samen de betekenis; een onderbroken lijn alleen is niet genoeg.
Bouw het skelet in een handige volgorde
- Maak elke klasse of interface in het bijbehorende pakket.
- Zet attributen om in velden en behoud namen, typen en zichtbaarheid.
- Zet navigeerbare associaties om in referentie- of collectievelden.
- Voeg
extendsofimplementstoe op basis van de generalisatie- of realisatielijnen. - Zet operaties om in methodekoppen en vul daarna de body’s in.
Stel dat een interface save en findAll aanbiedt. Een implementatie heeft een associatie met meerdere Parcel-objecten met de rol parcels:
public class InMemoryParcelRepository implements ParcelRepository {
private List<Parcel> parcels = new ArrayList<>();
@Override
public void save(Parcel parcel) {
parcels.add(parcel);
}
@Override
public List<Parcel> findAll() {
return new ArrayList<>(parcels);
}
}
De onderbroken lijn met driehoek levert implements ParcelRepository op. De associatie levert het lijstveld op. De operatiesignaturen leveren de twee publieke methoden op. De methodebody’s zijn een implementatiekeuze: hier voegt save een object achteraan toe en geeft findAll een nieuwe lijst terug, zodat aanroepers niet rechtstreeks elementen aan de opgeslagen lijst kunnen toevoegen of eruit kunnen verwijderen. De pakketobjecten zelf worden nog steeds gedeeld.
Teken in de andere richting
Begin met één kader voor elke klasse of interface. Een veld met een eenvoudige waarde wordt een attribuut. Een referentie naar een klasse in het programma wordt een associatie; een collectie duidt op een uiteinde met meerdere waarden. Bekijk controles in constructoren en methoden voordat je een ondergrens van één vastlegt. Vertaal extends naar een doorgetrokken lijn met driehoek en implements naar een onderbroken lijn met driehoek.
Overgeërfde velden en operaties blijven meestal in het kader van de superklasse. Zet nieuwe of overschreven leden in de subklasse. Voeg een afhankelijkheid toe voor een parameter, retourtype of lokaal gebruik wanneer die relatie helpt om het ontwerp uit te leggen.
Pakketten kunnen hun klassen omvatten in mapvormige kaders met een klein tabblad. Deze groepen helpen zichtbaar te maken welke onderdelen van welke andere afhankelijk zijn. Houd een documentatiediagram bij wanneer de code verandert; geef een vroege planningsschets een label zodat lezers het doel begrijpen.
Een Shipment die pakketten bevat, is geen soort Parcel. De lijst geeft een “heeft een”-relatie aan, dus teken een associatie. Gebruik overerving voor een “is een soort”-relatie die het Java-typesysteem en het gedrag daadwerkelijk ondersteunen.
Belangrijkste punten
- Generalisatie gebruikt een doorgetrokken lijn en een open driehoek bij de superklasse.
- Realisatie gebruikt een onderbroken lijn en een open driehoek bij de interface.
- Vertaal velden, associaties, overerving en methodesignaturen voordat je body’s schrijft.
- Behoud bij het vertalen de typen en zichtbaarheid uit het diagram.
- Alleen het gedeclareerde type van een collectie legt geen minimumaantal vast.
- Gebruik associaties voor opgenomen objecten en overerving voor subtyperelaties.
- Houd documentatiediagrammen in overeenstemming met de code die ze beschrijven.
3Deel een programma in lagen in
Een programma wordt gemakkelijker te testen en te wijzigen als de dialoog, regels en opslag afzonderlijke verantwoordelijkheden hebben. Je volgt een pakketopdracht door die onderdelen, verplaatst berekeningen uit consolecode en geeft samenwerkende objecten door via constructoren. Interfaces laten de logica werken met echte opslag of een vervanging in het geheugen. Door imports nauwkeurig te lezen zie je van welke typen elke laag afhankelijk is.
3.1 Houd de dialoog buiten berekeningen
De gebruikersinterface leest opdrachten en geeft resultaten weer. De applicatielogica bevat de regels en gegevens waarop die opdrachten werken. Door ze te scheiden kan een test een berekening rechtstreeks aanroepen en kan een andere interface die hergebruiken zonder de regel te kopiëren.
Geef elk onderdeel een duidelijke taak
TextUI verzorgt de dialoog: vraag om een gewicht, lees een regel met Scanner, zet de tekst om in een getal, roep de logica aan en geef het resultaat weer. ShippingCalculator accepteert een gewicht in gram en geeft een tarief in centen terug. De calculator vraagt de gebruiker niets en geeft geen antwoord weer.
Een methode met de naam printFee mengt die taken als de methode zowel berekent als uitvoer weergeeft. Verander de logicaoperatie in feeInCents(int weightGrams). De gebruikersinterface kan dan "Fee: " + calculator.feeInCents(weightGrams) + " cents" weergeven. Een test kan de teruggegeven gehele waarde met een assertie controleren zonder console-uitvoer op te vangen.
Schrijf de regel als retourwaarde
De pakketregel rekent 500 cent voor positieve gewichten tot en met 2.000 g, 900 cent tot en met 10.000 g en daarna 100 cent extra voor elke begonnen 1.000 g boven 10.000 g.
public int feeInCents(int weightGrams) {
if (weightGrams <= 0) {
throw new IllegalArgumentException("weight must be positive");
}
if (weightGrams <= 2000) {
return 500;
}
if (weightGrams <= 10000) {
return 900;
}
long excess = (long) weightGrams - 10000;
long startedBlocks = (excess + 999) / 1000;
return (int) (900 + 100 * startedBlocks);
}
De eerste twee takken die een geldig tarief teruggeven, omvatten hun bovengrens. Bij 12.500 g is het overschot 2.500 g. Geheeltallige deling na het optellen van 999 telt drie begonnen blokken en levert 1.200 cent op. De tussenberekening met long verwerkt ook grote positieve int-gewichten zonder overloop bij het optellen van 999.
Een ongeldig gewicht leidt tot een IllegalArgumentException: de methode wijst een argument af dat de regel schendt. De gebruikersinterface kan die exceptie opvangen en een leesbaar bericht kiezen. De regel hoort bij de logica; de formulering van het bericht hoort bij de interface.
Verbind de objecten in main
Geef de invoerlezer en calculator door aan de constructor van de gebruikersinterface:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ShippingCalculator calculator = new ShippingCalculator();
TextUI ui = new TextUI(scanner, calculator);
ui.start();
}
Maak één scanner voor de invoerstroom en deel die met de gebruikersinterface. Meerdere scanners op System.in kunnen elkaars gebufferde invoer verstoren. De main-methode stelt het programma samen; start voert de dialooglus uit.
Kijk bij het beoordelen van een klasse naar de verantwoordelijkheden. Een Scanner of System.out binnen een tariefcalculator laat zien dat invoer en uitvoer met regels zijn gemengd. Een tariefformule binnen de gebruikersinterface laat hetzelfde probleem van de andere kant zien. Verplaats de berekening naar een methode met expliciete invoer en een resultaat; houd het parsen van opdrachten en de weergavetekst bij elkaar in de gebruikersinterface.
Belangrijkste punten
- De gebruikersinterface leest en geeft uitvoer weer; logicamethoden accepteren waarden en geven resultaten terug.
- Houd tariefregels en validatie in de logica, zodat tests die rechtstreeks kunnen aanroepen.
- Laat de gebruikersinterface bepalen hoe een resultaat of afgewezen invoer wordt weergegeven.
- Maak samenwerkende objecten in
mainen geef ze door aan de constructor van de gebruikersinterface. - Tel een gedeeltelijk gebruikt gewichtsblok als een begonnen blok wanneer de regel dat vereist.
3.2 Verbind lagen via een repository
Een programma in lagen groepeert klassen op basis van hun verantwoordelijkheid. De presentatielaag verzorgt de dialoog, de logicalaag past regels toe en de gegevenstoegangslaag slaat informatie op en haalt die op. Een klasse gebruikt een andere laag via een duidelijke operatie in plaats van rechtstreeks bij de interne velden te komen.
Plaats de pakketklassen
Het gebruikersinterfacepakket bevat TextUI. Het domeinpakket bevat Parcel, ShippingCalculator en ParcelService. Het gegevenspakket bevat ParcelRepository en de opslagimplementaties daarvan. Main stelt de objecten boven deze lagen samen.
Een opdracht volgt deze route: de gebruiker voert “add 1200 Alex” in; de gebruikersinterface parst de opdracht en roept service.register(1200, "Alex") aan; de service valideert het gewicht, maakt een pakket, slaat het via de repository op en geeft 500 cent terug; de gebruikersinterface geeft het tarief weer. Tekst lezen, bepalen wat is toegestaan en het resultaat opslaan hebben elk een eigen plaats.
Wees afhankelijk van een contract
De service heeft opslaggedrag nodig, geen specifiek bestandsformaat. Druk dat gedrag uit via een interface:
public interface ParcelRepository {
void save(Parcel parcel);
List<Parcel> findAll();
}
De service bewaart een referentie van het type ParcelRepository en ontvangt die in de constructor:
public class ParcelService {
private final ParcelRepository repository;
public ParcelService(ParcelRepository repository) {
this.repository = repository;
}
}
Dit is constructorinjectie, een vorm van afhankelijkheidsinjectie: de aanroeper levert een samenwerkend object. Een service die binnen zijn eigen methoden new FileParcelRepository(...) maakt, kiest zelf voor bestandsopslag en maakt afzonderlijke tests moeilijker.
Wijzig de implementatie bij het samenstellen
Het draaiende programma kan new ParcelService(new FileParcelRepository("parcels.csv")) maken. Een test kan new InMemoryParcelRepository() leveren. Beide implementaties voldoen aan dezelfde interface. Aanroepen via de interface gebruiken polymorfisme om de methoden van het werkelijke object te bereiken.
Als het repositorycontract gelijk blijft, vereist een overstap naar andere opslag een nieuwe implementatie en een wijziging bij het samenstellen. Tariefregels en dialoogcode hoeven niet te weten hoe de gegevens worden opgeslagen.
Controleer de richting van afhankelijkheden nauwkeurig
Het neerwaartse pad waarlangs diensten worden gebruikt, loopt van gebruikersinterface naar logica naar opslag. Het domein mag geen gebruikersinterfaceklassen importeren of aanroepen. Als de logica de gebruiker een resultaat moet laten zien, geef je dat terug aan de gebruikersinterface. Als de gebruikersinterface rechtstreeks via de repository opslaat, kan ze de validatie van de service omzeilen.
Ook Java-typeverwijzingen zijn van belang: een repositorysignatuur die Parcel noemt, is afhankelijk van dat domeintype. Met ParcelRepository in het gegevenspakket en de service in het domeinpakket verwijzen die pakketten naar elkaar. Om die cyclische pakketafhankelijkheid op te heffen, plaats je de repository-interface naast de domeintypen en laat je de gegevensimplementaties die importeren. De service gebruikt nog steeds hetzelfde contract; alleen de plaats ervan verandert. Bekijk bij het controleren van het ontwerp zowel de route van methodeaanroepen als de imports.
Grotere programma’s kunnen pakketten eerst per functionaliteit groeperen, zoals bestellingen of klanten, en daarna de verantwoordelijkheden binnen elke functionaliteit scheiden. Pakketnamen helpen lezers verantwoordelijkheden te vinden; constructorparameters en methodesignaturen tonen de werkelijke afhankelijkheden.
Belangrijkste punten
- Scheid de verantwoordelijkheden voor presentatie, logica en gegevenstoegang.
- Geef een repository-interface door aan de constructor van de service.
- Kies een implementatie met bestanden of met opslag in het geheugen waar het programma wordt samengesteld.
- Laat acties van de gebruikersinterface via de logica lopen, zodat validatie wordt toegepast.
- Geef resultaten terug aan de gebruikersinterface in plaats van vanuit de logica gebruikersinterfacecode aan te roepen.
- Onderscheid bij het controleren van cyclische pakketafhankelijkheden de richting waarin diensten worden gebruikt van Java-typeafhankelijkheden.
4Controleer code met JUnit-tests
Automatische tests zetten verwacht gedrag om in controles die je na elke wijziging opnieuw kunt uitvoeren. Een controle schrijven is pas het begin: nuttige invoer brengt grenswaarden aan het licht, nieuwe testopstellingen houden tests onafhankelijk en leesbare foutmeldingen helpen je de oorzaak te vinden. Je bouwt Jupiter-tests voor de pakketlogica, beheerst hun gegevens en samenwerkende objecten en gebruikt een korte cyclus waarin je eerst een test schrijft om een regel geval voor geval uit te breiden.
4.1 Een test die een uitkomst controleert
Een unittest roept een kleine eenheid code aan met gekozen invoer en controleert het resultaat automatisch. Zo wordt een eis uitvoerbaar. Door tests na een wijziging opnieuw uit te voeren, kun je een regressie vinden: een fout in gedrag dat eerder werkte.
Maak een Jupiter-test
JUnit Jupiter is het programmeermodel dat JUnit 5 en 6 gebruiken. JUnit 6 heeft Java 17 of later nodig. Testannotaties en asserties gebruiken org.junit.jupiter.api.
Plaats ShippingCalculatorTest.java onder src/test/java/com/example/parcels/domain, met dezelfde pakketdeclaratie als de calculator. De afzonderlijke broncodemap houdt tests gescheiden van de meegeleverde programmacode, terwijl toegang binnen hetzelfde pakket behouden blijft.
package com.example.parcels.domain;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ShippingCalculatorTest {
@Test
void lightParcelCostsBaseFee() {
ShippingCalculator calculator = new ShippingCalculator();
int fee = calculator.feeInCents(1500);
assertEquals(500, fee);
}
}
De testklasse en -methode kunnen pakkettoegang hebben. Een testmethode geeft void terug en mag niet private zijn. Gebruik de @Test van Jupiter; org.junit.Test van JUnit 4 hoort bij een ander programmeermodel en wordt niet door de Jupiter-engine ontdekt. JUnit 4-asserties kunnen werken als hun bibliotheek beschikbaar is, maar consequent imports van Jupiter gebruiken houdt de opzet duidelijk.
Lees voorbereiden–uitvoeren–controleren
Het eerste deel bereidt een calculator voor. Het tweede deel voert uit door het tarief op te vragen. De laatste assertie vergelijkt de verwachte 500 cent met de teruggegeven waarde. Lege regels maken deze taken gemakkelijk herkenbaar. Alleen fee weergeven zou het aan een persoon overlaten om te bepalen of de waarde correct is; de assertie maakt die beslissing onderdeel van de test.
Bij assertEquals(expected, actual) staat de eis eerst en het waargenomen resultaat als tweede. Een optioneel bericht komt als laatste, zoals in assertEquals(500, fee, "fee for 1,500 g"). Als je de eerste twee argumenten omdraait, blijft voor deze waarden meestal gelijk of de test slaagt of mislukt, maar keert de betekenis van het foutrapport om.
Kies de juiste assertie
Gebruik assertTrue of assertFalse voor een booleaanse waarde en assertNull of assertNotNull wanneer de aan- of afwezigheid van een object de uitkomst is. Controles op objectgelijkheid gebruiken equals; zonder overschrijving kunnen twee verschillende objecten met overeenkomende velden toch als ongelijk worden beschouwd.
Resultaten met zwevendekommagetallen hebben een passende tolerantie nodig. De berekening 0.1 + 0.2 levert in een Java-double 0.30000000000000004 op:
assertEquals(0.3, 0.1 + 0.2, 1e-9);
Het derde argument staat een absoluut verschil tot 0.000000001 toe. Kies een tolerantie die bij de berekening past; een te grote tolerantie kan een betekenisvolle fout verbergen. Berekeningen met gehele aantallen centen hebben geen tolerantie voor kommagetallen nodig.
Lees een foutmelding
Voer een test uit vanuit de IDE of de buildtool. Een rapport zoals “expected: <500> but was: <900>” noemt het vereiste en het waargenomen resultaat; de stacktrace wijst naar de assertie. Een onverwachte exceptie laat een test ook mislukken. Groen betekent dat de gecontroleerde gevallen zijn geslaagd; het kiezen van die gevallen blijft een afzonderlijk onderdeel van testen.
Belangrijkste punten
- Gebruik Jupiter-annotaties en -asserties uit
org.junit.jupiter.api. - Plaats tests in
src/test/java, normaal gesproken in het pakket van de geteste klasse. - Bereid de objecten voor, voer de bewerking uit en controleer de uitkomst met een assertie.
- Zet de verwachte waarde eerst in
assertEquals. - Gebruik een onderbouwde tolerantie voor vergelijkingen van zwevendekommagetallen.
- Lees de naam van de mislukte test, de verwachte en werkelijke waarden en de locatie van de assertie.
4.2 Kies invoer aan de grenzen van regels
Een nuttige verzameling tests controleert hoe de specificatie invoer in gevallen verdeelt. Eén gewone invoerwaarde test zelden een hele regel. Grensgevallen op een grens en direct ernaast zijn vooral geschikt om een voorwaarde met de verkeerde vergelijkingsoperator te vinden.
Verdeel de mogelijke invoer
Een equivalentieklasse groepeert invoerwaarden die gelijk moeten worden behandeld. De verzendregel heeft ongeldige gewichten van nul of lager, een tariefgroep van 500 cent tot en met 2.000 g, een tariefgroep van 900 cent tot en met 10.000 g en daarboven een berekening met begonnen blokken. Kies representatieve invoerwaarden uit de klassen en bekijk daarna hun grenzen.
| Gewicht in gram | Verwachte uitkomst |
|---|---|
0, -5 | Afwijzen met IllegalArgumentException |
1, 2000 | 500 cent |
2001, 10000 | 900 cent |
10001, 11000 | 1.000 cent |
11001 | 1.100 cent |
Als je <= 2000 verandert in < 2000, heeft dat gevolgen voor precies 2.000 g. Een test bij 1.500 g slaagt nog steeds. Test boven 10.000 g zowel een heel blok als de eerste gram van het volgende blok om het naar boven afronden te controleren.
Verwachte waarden komen uit de eis en een berekening met de hand. Bij 12.500 g is het overschot 2.500 g. Daarvoor zijn drie begonnen blokken nodig, dus het verwachte resultaat is 1.200 cent. Als je de methode uitvoert en het antwoord naar de test kopieert, kopieer je ook eventuele fouten.
Test afgewezen invoer
Een exceptietest geeft JUnit de operatie die moet worden uitgevoerd:
assertThrows(IllegalArgumentException.class,
() -> calculator.feeInCents(0));
De expressie () -> ... is een lambda: een klein stuk code dat aan JUnit wordt doorgegeven. JUnit voert die binnen de controle uit. Als je de methode aanroept voordat je die aan de assertie doorgeeft, ontsnapt de exceptie voordat de controle die kan onderzoeken.
assertThrows accepteert het genoemde exceptietype of een subklasse daarvan. De assertie mislukt als de operatie normaal wordt voltooid of een onverenigbaar type opwerpt. De assertie geeft de exceptie terug, zodat een vereist bericht kan worden gecontroleerd:
IllegalArgumentException error = assertThrows(
IllegalArgumentException.class,
() -> calculator.feeInCents(0));
assertEquals("weight must be positive", error.getMessage());
Geef één scenario een leesbare naam
Namen zoals zeroWeightIsRejected en feeIsBaseFeeAtExactly2000Grams duiden het bedoelde gedrag aan. Een test kan meerdere asserties hebben als die dezelfde uitkomst controleren. Nadat pakketten van 1.200 g en 800 g aan een zending zijn toegevoegd, beschrijft de controle op een aantal van twee en een totaalgewicht van 2.000 g dat ene scenario. assertAll kan controles groeperen, zodat alle mislukkingen worden gemeld.
Controleer gewijzigde objecten via hun gedrag, zoals parcelCount() en totalWeightGrams(), in plaats van private velden beschikbaar te maken voor tests. Bij refactoring kunnen opslagdetails dan veranderen terwijl de controles behouden blijven. Volledige regeldekking betekent dat de tests de regels hebben uitgevoerd; toch kan een verkeerde grens of niet-geteste ongeldige invoer worden gemist.
Belangrijkste punten
- Verdeel invoer volgens de specificatie en test representatieve gevallen en grenswaarden.
- Controleer een grens zelf en nabijgelegen waarden om één-ernaastfouten te vinden.
- Bereken verwachte resultaten onafhankelijk van de implementatie.
- Geef de operatie als lambda door aan
assertThrows. - Geef per test één scenario een naam; meerdere samenhangende asserties kunnen dat scenario controleren.
- Test gewijzigde toestand via gedrag dat aanroepers kunnen waarnemen.
4.3 Tests die afzonderlijk en in elke volgorde werken
Elke test moet alles voorbereiden wat hij nodig heeft. Een test die alleen slaagt nadat een andere test gegevens heeft toegevoegd, is afhankelijk van een verborgen voorgeschiedenis. Zulke tests zijn moeilijk te vertrouwen wanneer een testuitvoerder de volgorde verandert of slechts één methode uitvoert.
Maak een nieuwe testopstelling
Een testopstelling is de verzameling objecten en gegevens die een test gebruikt. Plaats herhaalde voorbereiding per test in @BeforeEach:
class ShipmentTest {
private Shipment shipment;
@BeforeEach
void setUp() {
shipment = new Shipment();
}
@Test
void newShipmentIsEmpty() {
assertEquals(0, shipment.parcelCount());
}
@Test
void addingParcelIncreasesCount() {
shipment.add(new Parcel(1200, "Alex"));
assertEquals(1, shipment.parcelCount());
}
}
De voorbereiding wordt vóór elke methode uitgevoerd. De tweede test verandert de zending niet die de eerste gebruikt. Standaard maakt Jupiter ook voor elke testmethode een nieuwe instantie van de testklasse. Veranderlijke statische velden blijven gedeeld, dus een statische lijst kan nog steeds toestand tussen die instanties laten doorlekken.
@AfterEach ruimt na elke test op. @BeforeAll en @AfterAll worden één keer voor de klasse uitgevoerd en zijn onder de standaardlevenscyclus statisch. Gedeelde referentiegegevens die alleen worden gelezen, kunnen geschikt zijn voor een eenmalige voorbereiding per klasse; veranderlijke objecten moeten meestal voor elke test opnieuw worden gemaakt. Tests moeten afzonderlijk werken zonder afhankelijk te zijn van de volgorde in het bronbestand.
Gebruik één testbody voor een tabel
Een geparametriseerde test voert dezelfde gedragscontrole uit voor meerdere rijen:
@ParameterizedTest
@CsvSource({"1, 500", "2000, 500", "2001, 900",
"10000, 900", "10001, 1000"})
void feeMatchesTable(int grams, int expected) {
assertEquals(expected,
new ShippingCalculator().feeInCents(grams));
}
Elke rij is een afzonderlijke aanroep met een eigen rapportage. @ValueSource(ints = {0, -5}) levert één geheel getal per aanroep voor tests van ongeldige invoer. Annotaties voor geparametriseerde tests komen uit org.junit.jupiter.params; CsvSource en ValueSource staan in het pakket provider daarvan. De verzamelafhankelijkheid junit-jupiter bevat de module voor geparametriseerde tests.
@DisplayName voegt een leesbaar rapportlabel toe. @Disabled legt een overgeslagen test met een reden vast; een overgeslagen controle levert geen bewijs dat het te testen gedrag werkt.
Beheers de omgeving
Een servicetest kan een InMemoryParcelRepository ontvangen, een werkende vereenvoudigde testimplementatie die pakketten in het geheugen opslaat. Dit vermijdt echte bestandsinhoud en isoleert de logica. Geef bij het testen van de invoerverwerking een Scanner die uit een bekende string is opgebouwd door aan een tekstinterface. Een vaste datum of een Random met een vaste beginwaarde maakt externe invoer eveneens voorspelbaar.
Een instabiele test geeft bij verschillende uitvoeringen een ander resultaat zonder relevante codewijziging. Gedeelde veranderlijke gegevens, de huidige tijd, onbeheerde willekeurige waarden en achtergebleven bestanden zijn veelvoorkomende oorzaken. Geef de test controle over die invoer. Houd hulpmethoden klein en de voorbereiding leesbaar; een testopstelling die veel niet-verwante objecten maakt, kan laten zien dat de geteste klasse te veel taken heeft.
Belangrijkste punten
- Gebruik
@BeforeEachom tests nieuwe testopstellingen te geven. - Een nieuwe instantie van de testklasse zet veranderlijke statische toestand niet terug.
- Geparametriseerde tests voeren één methode uit met afzonderlijk gerapporteerde invoerrijen.
- Gebruik vereenvoudigde testimplementaties en aangeleverde invoer om logicatests onafhankelijk te houden van echte opslag en de klok.
- Zorg dat elke test afzonderlijk uitvoerbaar is, zonder vereiste methodevolgorde.
4.4 Bouw gedrag op via rood, groen en refactoren
Testgestuurde ontwikkeling (TDD) begint een gedragswijziging met een test. Een korte cyclus maakt het ontbrekende gedrag zichtbaar, implementeert het en verbetert daarna de structuur van de code. Een testlijst bevat gevallen die nog moeten worden onderzocht, zonder dat ze allemaal tegelijk hoeven te worden geschreven.
Kies één punt uit de testlijst
Noteer voor een controle op schrikkeljaren in de gregoriaanse kalender vier gedragingen: een gewoon jaar dat geen schrikkeljaar is, een jaar dat deelbaar is door vier, een eeuwjaar dat niet deelbaar is door 400 en een eeuwjaar dat deelbaar is door 400. Deze gevallen vertegenwoordigen verschillende delen van de regel.
Rood: schrijf één test en voer die uit. Een ontbrekende methode kan een compilatiefout veroorzaken. Controleer zodra de methode bestaat of de test mislukt vanwege het gedrag dat je wilt toevoegen. Een ontbrekende assertie, de verkeerde methodeaanroep of een niet-ontdekte test kan een schijnbaar groen resultaat misleidend maken. Een nieuw geval kan ook al door bestaande code worden afgehandeld; onderzoek dat resultaat voordat je de implementatie wijzigt.
Groen: voeg de eenvoudigste code toe waarmee de nieuwe test en alle eerdere tests slagen. Refactoren: verbeter de structuur zonder het gedrag te veranderen en voer de tests na elke kleine wijziging opnieuw uit. Kies daarna het volgende geval.
Laat gevallen de regel ontwikkelen
De eerste controle is assertFalse(LeapYears.isLeapYear(2023)). Als je de methode maakt met return false;, slaagt dit geval. Het is een bewuste eerste stap, geen voltooide kalenderregel.
De volgende controle, assertTrue(LeapYears.isLeapYear(2024)), mislukt. Verander de methode in return year % 4 == 0;. Beide gevallen slagen nu.
Daarna brengt assertFalse(LeapYears.isLeapYear(1900)) de uitzondering voor eeuwjaren aan het licht. Als je de expressie verandert in year % 4 == 0 && year % 100 != 0, slagen die drie tests.
Tot slot brengt assertTrue(LeapYears.isLeapYear(2000)) de uitzondering op de eeuwjaarregel aan het licht. De implementatie wordt:
public static boolean isLeapYear(int year) {
return year % 400 == 0
|| (year % 4 == 0 && year % 100 != 0);
}
Voer na de wijziging alle vier controles uit. Elke nieuwe mislukking wees op één ontbrekend deel van het gedrag, in plaats van je te laten zoeken naar fouten in een grote onvoltooide implementatie.
Verbeter de structuur terwijl de tests groen zijn
Refactoring verandert namen of indeling terwijl het gedrag behouden blijft. Een methode hernoemen, een hulpmethode afsplitsen, herhaalde logica verwijderen en een klasse naar een beter pakket verplaatsen zijn refactorings als het waarneembare gedrag gelijk blijft. Het tarief op een grenswaarde wijzigen is een gedragswijziging en heeft een test nodig die die wijziging beschrijft.
Als een opschoning een test laat mislukken, herstel dan het bedoelde gedrag voordat je een volgende functionaliteit toevoegt. Kleine wijzigingen helpen de oorzaak van een mislukking te vinden. Als je opschoning overslaat, blijft code over die wel werkt maar moeilijker uit te breiden wordt; als je een grote implementatie voor veel toekomstige gevallen schrijft, ontstaat meer code om te onderzoeken wanneer de volgende controle mislukt.
TDD hangt af van de kwaliteit van de eisen en de gekozen gevallen. Een verkeerd begrepen regel kan zowel in de test als in de implementatie voorkomen. Logica met duidelijke invoer en uitvoer is een nuttig startpunt, omdat het verwachte resultaat onafhankelijk kan worden vastgesteld en snel kan worden gecontroleerd.
Belangrijkste punten
- Houd een testlijst bij en implementeer steeds één gedraging.
- Rood maakt het ontbrekende gedrag zichtbaar; groen laat alle huidige tests slagen.
- Onderzoek een onverwacht geslaagde test voordat je aanneemt dat die de bedoelde wijziging heeft gecontroleerd.
- Refactor de structuur met behoud van gedrag en voer de tests opnieuw uit.
- Gebruik opeenvolgende gevallen om gewone regels en uitzonderingen aan het licht te brengen.
5Bouw en test projecten met Maven of Gradle
Een project heeft meer nodig dan bronbestanden: het heeft bibliotheken, compilatie-instellingen, een testuitvoerder en een manier om het resultaat te verpakken nodig. Maven en Gradle beschrijven die keuzes, zodat een opdracht de build kan herhalen. Je gebruikt hun standaardmappen, declareert Jupiter-afhankelijkheden, voert de meegeleverde wrapper uit en onderscheidt een codefout van een probleem met testdetectie of afhankelijkheden door de builduitvoer te lezen.
5.1 Bouw een Maven-project vanuit de beschrijving
Een buildtool zet bronbestanden om in een gecontroleerd, verpakt resultaat. Maven leest een projectbeschrijving, downloadt gedeclareerde bibliotheken, compileert code, voert tests uit en maakt een artefact. Dezelfde beschrijving geeft de opdrachtregel en IDE een gedeeld beeld van het project.
Gebruik de standaardindeling
Zet Java-productiecode in src/main/java, tests in src/test/java, productiebestanden zonder code in src/main/resources en testgegevens in src/test/resources. Pakketmappen beginnen onder elke Java-broncodemap. Maven schrijft gegenereerde uitvoer onder target; bewerk de bronbestanden, niet de gecompileerde kopieën in die map.
Het bestand pom.xml in de hoofdmap van het project, het Project Object Model, identificeert het project met artefactcoördinaten: groupId, artifactId en version. Zo identificeert com.example:parcels:1.0-SNAPSHOT een ontwikkelversie van het pakketprogramma. Een bibliotheekafhankelijkheid heeft ook coördinaten.
Declareer JUnit voor tests
Hieronder staat een volledige kleine Maven-configuratie. Die compileert voor Java 17 en kiest een testuitvoerder die Jupiter ondersteunt:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>parcels</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
</plugin>
</plugins>
</build>
</project>
Maven haalt afhankelijkheden uit repositories, normaal gesproken Maven Central, en bewaart ze in een cache in .m2/repository onder je persoonlijke map. Bibliotheken die een afhankelijkheid nodig heeft, zijn transitieve afhankelijkheden. Het testbereik maakt JUnit beschikbaar voor het compileren en uitvoeren van tests, niet voor het compileren van de hoofdcode of het klassenpad van het programma tijdens de uitvoering. Een gewoon JAR-bestand houdt de JAR-bestanden van afhankelijkheden afzonderlijk, tenzij de verpakking is ingesteld om ze te bundelen.
Vraag een levenscyclusfase aan
De hoofdlevenscyclus doorloopt onder andere de fasen compile, test, package en install. Als je package aanvraagt, worden ook eerdere fasen uitgevoerd. Met de wrapper compileert en test ./mvnw test; ./mvnw package maakt ook het JAR-bestand onder target; ./mvnw install plaatst het artefact bovendien in de lokale repository. De afzonderlijke levenscyclus voor opschonen, ./mvnw clean, verwijdert gegenereerde uitvoer.
Een mislukte test stopt de normale build voordat een nieuw artefact wordt verpakt. Een ouder JAR-bestand kan nog aanwezig zijn totdat je opschoont, dus de aanwezigheid ervan bewijst niet dat de laatste build is geslaagd.
Maak testdetectie zichtbaar
De standaardpatronen van Surefire omvatten namen die beginnen met Test of eindigen op Test, Tests of TestCase. ShippingChecks voldoet daar niet aan; gebruik ShippingCalculatorTest en bekijk het gerapporteerde aantal tests. Tests onder src/main/java kunnen geen afhankelijkheid met testbereik gebruiken.
De Maven Wrapper, mvnw of op Windows mvnw.cmd, voert de Maven-versie uit die het project opgeeft. Gebruik een project dat de wrapperbestanden bevat; een POM maakt de scripts niet zelf. Vanuit de hoofdmap biedt de wrapper een consistente buildopdracht zonder afzonderlijke Maven-installatie.
Belangrijkste punten
- Houd hoofdcode, tests en bestanden zonder code in hun standaardbronmappen.
- Identificeer projecten en bibliotheken met coördinaten voor groep, artefact en versie.
- Declareer JUnit met testbereik en gebruik een compatibele Surefire-testuitvoerder.
- Een aangevraagde Maven-fase voert de eerdere fasen in de levenscyclus uit.
- Controleer aantallen tests; een geslaagde build kan toch een test met een verkeerde naam hebben gemist.
- Gegenereerde uitvoer hoort in
targeten kan opnieuw worden opgebouwd. - De wrapper voert de Maven-versie uit die het project heeft gekozen.
5.2 Voer Gradle-tests uit en volg buildfouten
Gradle beschrijft een build met een script en voert taken met hun afhankelijkheden uit. Een Kotlin-script heet build.gradle.kts; een Groovy-script heet build.gradle. Plug-ins leveren taken en afhankelijkheidsconfiguraties geven aan waar bibliotheken nodig zijn.
Configureer een applicatie en Jupiter
Een settings.gradle.kts in de hoofdmap van het project kan rootProject.name = "parcels" bevatten. Een bijpassend Kotlin-buildscript is:
plugins {
application
}
repositories {
mavenCentral()
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
testImplementation(platform("org.junit:junit-bom:6.1.3"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
application {
mainClass = "com.example.parcels.Main"
}
tasks.test {
useJUnitPlatform()
}
De plug-in application levert Java-buildtaken en voegt run toe. mavenCentral() kiest een repository voor afhankelijkheden. De BOM stemt de versies van JUnit-modules op elkaar af, zodat de versie één keer voorkomt. testImplementation levert bibliotheken die nodig zijn om tests te compileren en uit te voeren. testRuntimeOnly levert de launcher alleen tijdens het uitvoeren ervan. Bibliotheken voor de hoofdcode gebruiken implementation, waardoor ze ook beschikbaar zijn voor testcode.
useJUnitPlatform() kiest het platform dat Jupiter-tests uitvoert. Als je Jupiter-bibliotheken toevoegt zonder de testuitvoerder te configureren, worden die tests mogelijk niet ontdekt. De huidige Gradle-versie meldt bij de standaardinstelling een fout als er testbroncode bestaat maar geen tests worden ontdekt; bekijk de uitvoer en configuratie van de testtaak in plaats van de controle te verwijderen.
Voer taken uit met de wrapper
Gebruik de wrapper die bij het project is meegeleverd: ./gradlew test voert tests uit; ./gradlew build compileert, test en verpakt; ./gradlew run start de geconfigureerde main-klasse; en ./gradlew clean verwijdert build. Windows gebruikt gradlew.bat. Gradle gebruikt dezelfde bronindeling voor hoofdcode en tests als Maven en schrijft gegenereerde uitvoer naar build.
Een taak met de markering UP-TO-DATE heeft ongewijzigde bijgehouden invoer en uitvoer, zodat Gradle het eerdere resultaat hergebruikt. Die melding verschilt van een nieuw uitgevoerde testrun. Het HTML-testrapport staat in build/reports/tests/test/index.html.
Zoek de fase die mislukte
Begin bij de eerste bruikbare fout. “Cannot find symbol” tijdens het compileren van de hoofdcode wijst op de genoemde code, import of afhankelijkheid. Een ontbrekend org.junit.jupiter.api tijdens het compileren van tests wijst op de instelling van testafhankelijkheden; dezelfde fout tijdens het compileren van de hoofdcode kan betekenen dat een test in de bronmap voor hoofdcode is geplaatst. Een fout bij het oplossen van een afhankelijkheid wijst op coördinaten, toegang tot de repository of netwerkbeschikbaarheid.
Een mislukking tijdens het uitvoeren van een test is anders: de test is gecompileerd en ontdekt, maar de controle is mislukt. Als “expected: <500> but was: <900>” verschijnt bij de tariefgrens van 2.000 g, bekijk dan de vergelijking die de tariefgroep kiest. Afhankelijkheden wijzigen herstelt die berekening niet.
Maven biedt een declaratieve XML-beschrijving met vaste levenscyclusfasen. Gradle biedt configureerbare scripts en taken, waaronder het overslaan van actueel werk. Beide automatiseren herhaalbare Java-builds; gebruik de tool die voor het project is gekozen en lees de eerste fout waarop je kunt reageren voordat je code wijzigt.
Belangrijkste punten
- Plug-ins voegen Gradle-taken toe; configuraties bepalen waar afhankelijkheden beschikbaar zijn.
- Stem JUnit-versies af met een BOM en voer Jupiter uit op het JUnit Platform.
- Gebruik de meegeleverde Gradle Wrapper voor de taken test, build, run en clean.
- Onderscheid taakresultaten uit de cache, compilatiefouten, afhankelijkheidsfouten en mislukte asserties.
- Lees de naam van de mislukte test en de verwachte en werkelijke waarden voordat je de implementatie wijzigt.
Klassendiagrammen en unittests in Java: voorbeelden
1Verplaats een klasse voor pakketten naar een Java-pakket
13 stappen
- 1IntroductieGeef de klasse een duidelijke plaats
Een pakketprogramma heeft een calculator en een tekstinterface. Als deze klassen naar afzonderlijke pakketten worden verplaatst, heeft Java namen, mappen en toegangsniveaus nodig die overeenkomen.
Volg de verplaatsing in drie stappen: plaats de klasse, laat een ander bestand de naam gebruiken en controleer daarna of die aanroeper de constructor en methoden kan gebruiken.
- 2VoorbeeldBegin onder de broncodemap
De broncodemap is
src/main/java. Zet voorParcelincom.example.parcels.domainde punten in de pakketnaam om in mappen en voeg de bestandsnaam toe:src/main/java/com/example/parcels/domain/Parcel.javaSchrijf
package com.example.parcels.domain;aan het begin van het bestand. De mappensrc/main/javavan de broncodemap beschrijven de buildindeling en blijven buiten de pakketdeclaratie. - 3UitlegHoud de identiteit van de klasse intact
De eenvoudige naam is
Parcel; de volledig gekwalificeerde naam iscom.example.parcels.domain.Parcel. Het verplaatsen van het bestand verandert die identiteit op zichzelf niet.Een publieke klasse met de naam
Parcelhoort inParcel.java. Voer een main-klasse in een pakket uit vanaf de hoofdmap van de gecompileerde boom met een opdracht zoalsjava -cp out com.example.parcels.Main, zodat Java de pakketmappen onderoutkan vinden. - 4UitdagingHerstel een niet-overeenkomende locatie
Een publieke klasse met de naam
Receiptdeclareertpackage com.example.parcels.domain;, maar is opgeslagen ondersrc/main/java/com/example/parcels/ui/. Het pakket moet ongewijzigd blijven. Waar moet het bestand naartoe?---reveal---
Verplaats het naar
src/main/java/com/example/parcels/domain/Receipt.java. Het pakketpad begint onder de broncodemap en de bestandsnaam komt overeen met de publieke klasse. Een import elders wijzigen zou de bronindeling niet herstellen. - 5VoorbeeldMaak de typenaam gemakkelijk te gebruiken
De gebruikersinterface heeft een eigen declaratie en importeert daarna het domeintype:
package com.example.parcels.ui; import com.example.parcels.domain.Parcel;De klassebody kan nu de eenvoudige naam
Parcelgebruiken. De import geldt alleen voor dit bestand. De import lost een naam op; die kopieert geen klasse en maakt geen afhankelijkheid beschikbaar voor de build. - 6KernpuntEen joker stopt bij één pakket
import com.example.parcels.*;omvat het domeinpakket niet. Het gedeelde begin van de naam lijkt op een stamboom, maar elke volledige pakketnaam duidt een afzonderlijk pakket aan.Dezelfde regel verklaart waarom
java.util.*geenjava.util.function.Functionlevert. Gebruik een import van één type, de jokerimport van het juiste pakket of een volledig gekwalificeerde naam voor dat type. - 7UitdagingHaal twee lijsten uit elkaar
Een bestand heeft zowel
java.util.Listalsjava.awt.Listnodig. Het heeft momenteel imports van beide afzonderlijke typen en de compilatie mislukt. Hoe kan het bestand beide typen zonder dubbelzinnigheid noemen?---reveal---
Behoud één import van een type, zoals
import java.util.List;, en schrijfjava.awt.Listvoluit waar dat type nodig is. Twee verschillende imports van één type kunnen in één bestand niet dezelfde eenvoudige naam twee betekenissen geven. - 8TipHerken namen die geen import nodig hebben
Een bestand kan typen in zijn eigen pakket gebruiken zonder ze te importeren. Toegankelijke typen uit
java.lang, waaronderString,MathenSystem, zijn ook automatisch beschikbaar.Een statische import heeft een andere taak: als je
Assertions.assertEqualsstatisch importeert, kun jeassertEquals(...)direct aanroepen. Die import verkort een verwijzing naar een statisch lid, geen pakketnaam. - 9VoorbeeldControleer ook de constructor
Het type is public, maar de constructor heeft geen modifier:
public class Parcel { Parcel(int grams, String recipient) { } }Die constructor heeft pakkettoegang. De gebruikersinterface kan
Parcelvia de import oplossen, maar kan vanuit het gebruikersinterfacepakket geen object ervan construeren. Markeer de constructor als public als het maken van pakketten tot de bedoelde publieke operaties van het domein behoort; houd de velden private. - 10UitdagingMaak de operatie toegankelijk en houd de hulpklasse verborgen
De gebruikersinterface moet de methode
feeInCentsvan een publieke calculator aanroepen. Die methode is momenteel zonder toegangsmodifier gedeclareerd. Alleen de calculator gebruikt de hulpklasseWeightBandsmet pakkettoegang. Welke toegang moet veranderen?---reveal---
Maak
feeInCentspublic en laatWeightBandspakkettoegang houden. De gebruikersinterface heeft de berekeningsoperatie nodig, niet de hulpklasse voor de tabel. Het importeren van een van beide klassen verandert de toegangsrechten niet. - 11UitlegEen subklasse heeft specifieke rechten
Protected leden zijn toegankelijk voor het hele declarerende pakket en voor subklassen volgens de regels voor overerving tussen pakketten. In een ander pakket kan een subklasse haar eigen overgeërfde instantielid gebruiken.
Ze kan geen referentie met het type van de superklasse gebruiken om dat lid op een willekeurig superklasseobject te benaderen. Protected toegang is dus ruimer dan toegang voor alleen subklassen, maar beperkter dan public toegang.
- 12UitdagingOnderzoek het hele toegangspad
TextUIimporteert een publiekeParcelcorrect. Bij de aanroepnew Parcel(800, "Jo")wordt gemeld dat de constructor niet van buiten het pakket toegankelijk is. De constructor heeft geen modifier. Wat herstelt deze bedoelde constructie vanuit een ander pakket?---reveal---
Maak de constructor public. Het pakket en de import lossen het type al op, dus het bestand verplaatsen of een jokerimport toevoegen verhelpt de gemelde toegangsfout niet. Private pakketvelden kunnen private blijven.
- 13SamenvattingGebruik drie afzonderlijke controles
Controleer de locatie door de declaratie te vergelijken met mappen onder de broncodemap. Controleer de naamgeving via imports of volledig gekwalificeerde namen. Controleer de toegang tot het type en de operatie die de aanroeper daadwerkelijk gebruikt.
Deze volgorde helpt een vaag probleem als “Java kan deze klasse niet gebruiken” om te zetten in een gerichte correctie van de bronindeling, naamoplossing of zichtbaarheid.
2Lees een pakketmodel en bouw het Java-skelet
13 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieLees de structuur voordat je body’s schrijft
- 2VoorbeeldVertaal één veld en één methode
- 3UitlegHerken een lid op klasseniveau
- 4UitdagingBehoud de symbolen
- 5VoorbeeldTel pakketten vanuit één zending
- 6VoorbeeldKies een representatie en dwing de regel af
- 7KernpuntVolg een verbinding in één richting
- 8UitdagingControleer een minimumaantal
- 9UitlegZet eigenaarschap aan het juiste uiteinde
- 10VoorbeeldLees twee lijnen met driehoeken
- 11TipBouw de declaraties op volgorde
- 12UitdagingCombineer een contract en een collectie
- 13SamenvattingHoud de taak van elk symbool apart
3Volg één opdracht door afzonderlijke verantwoordelijkheden
11 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVolg een opdracht door het programma
- 2VoorbeeldHaal de regel uit consolecode
- 3VoorbeeldBereken een begonnen blok
- 4KernpuntHoud validatie bij de regel
- 5UitdagingMaak een berekening herbruikbaar
- 6VoorbeeldGeef opslag door aan de service
- 7VerbandVervang bestanden door een werkende testimplementatie
- 8TipControleer aanroepen en imports afzonderlijk
- 9UitdagingHerstel een omweg buiten de service om
- 10UitdagingSluit een beheerste servicetest aan
- 11SamenvattingPlaats elke wijziging bij de bijbehorende taak
4Bouw een nuttige verzameling Jupiter-tests op
19 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieZet eisen om in uitvoerbare controles
- 2VoorbeeldBereid voor, voer uit en controleer
- 3UitlegLees verwacht en werkelijk correct
- 4TipSta bewust numerieke afronding toe
- 5UitdagingHerstel een test die alleen uitvoer weergeeft
- 6VoorbeeldZoek de tariefovergangen
- 7KernpuntHoud de verwachting onafhankelijk
- 8VoorbeeldPlaats de afgewezen aanroep binnen de controle
- 9UitdagingKies het geval dat de wijziging blootlegt
- 10VoorbeeldGeef elke test een nieuwe zending
- 11VoorbeeldVoer een tabel door één testbody
- 12VerbandBeheers het samenwerkende object, niet de testvolgorde
- 13UitdagingOnderzoek gegevens die een andere test achterliet
- 14VoorbeeldBegin de cyclus met een gewoon jaar
- 15VoorbeeldVoeg de algemene regel voor deelbaarheid door vier toe
- 16VoorbeeldVerfijn de voorwaarde met eeuwjaren
- 17UitdagingKies een volgende stap en behoud de controles
- 18UitdagingBreng gevalskeuze en isolatie samen
- 19SamenvattingVertrouw op specifiek, herhaalbaar bewijs
5Van buildbestand naar testrapport
13 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieHerhaal de build met één opdracht
- 2UitlegScheid bronbestanden van gegenereerde uitvoer
- 3VoorbeeldGeef het project en de testbibliotheek een naam
- 4VoorbeeldVraag de fase aan die je nodig hebt
- 5TipVerwar nul tests niet met gecontroleerd gedrag
- 6UitdagingZoek een verkeerd geplaatste test
- 7UitlegKies de Gradle-taken
- 8VoorbeeldHoud Jupiter-modules op elkaar afgestemd
- 9VoorbeeldGeef de testtaak aan hoe Jupiter moet worden uitgevoerd
- 10UitdagingHerstel testdetectie
- 11VoorbeeldVolg de mislukking van een uitgevoerde test
- 12UitdagingCombineer detectie met een nieuwe build
- 13SamenvattingLees de build als bewijs in fasen
Klassendiagrammen en unittests in Java: oefenvragen
Set 1: Namen, mappen en toegang in Java10 Oefenvragen
Vraag 1 van 10
Ticket declareert package org.example.events.model;. Welk pad moet die onder de Java-broncodemap voor de hoofdcode hebben?Toon antwoordVerberg antwoord
Ticket.java op. De broncodemap is al de beginlocatie, dus als je src/main/java opnieuw toevoegt, verdubbel je het deel van het pad dat bij de buildindeling hoort.Vraag 2 van 10
shop.ui heeft een hulpklasse nodig die momenteel geen pakketdeclaratie heeft. Hoe moet de hulpklasse worden ingedeeld zodat de gebruikersinterface die kan importeren?Toon antwoordVerberg antwoord
Vraag 3 van 10
import java.util.*; en gebruikt java.util.function.Predicate met de eenvoudige naam. Welke extra import van één type levert die naam?Toon antwoordVerberg antwoord
Predicate staat in het afzonderlijke pakket java.util.function, dus de volledige typenaam is nodig in de import van één type. De bestaande jokerimport java.util.* bereikt geen onderliggende pakketten en een statische import is voor statische leden.Vraag 4 van 10
java.util.* en java.awt.* en declareert daarna List<String> names. Hoe kan het List naar de collectie-interface laten verwijzen terwijl beide jokerimports behouden blijven?Toon antwoordVerberg antwoord
List zo verwijzen naar java.util.List. De twee jokerimports kunnen blijven staan; een statische import zou dit type op het hoogste niveau niet als statisch lid identificeren.Vraag 5 van 10
assertTrue(valid) aan zonder de klassenaam Assertions ervoor. Welke import maakt die directe aanroep mogelijk?Toon antwoordVerberg antwoord
assertTrue is een statisch lid van Assertions, dus een statische import maakt het lid direct aanroepbaar. Alleen de klasse importeren staat Assertions.assertTrue(...) toe, maar haalt de naam van de bijbehorende klasse niet uit de aanroep.Vraag 6 van 10
book.data.Record is public en heeft een constructor die is gedeclareerd als Record(int id). Een niet-verwante klasse in book.ui importeert de klasse, maar kan geen object ervan construeren. Welke toegangswijziging maakt die aanroep mogelijk?Toon antwoordVerberg antwoord
book.ui is een ander pakket. Public toegang maakt deze aanroep door een niet-verwante klasse mogelijk; protected toegang niet, omdat de aanroeper zich niet in het declarerende pakket bevindt en geen rechten als subklasse gebruikt.Vraag 7 van 10
class RateTable heeft geen toegangsmodifier. Waar kunnen andere klassen op het hoogste niveau die gebruiken?Toon antwoordVerberg antwoord
public heeft pakkettoegang, waardoor het gebruik beperkt is tot precies hetzelfde pakket. Imports lossen namen op zonder toegang te verlenen, dus een expliciete import vanuit een ander pakket kan de toegang tot deze hulpklasse niet openen.Vraag 8 van 10
# threshold: int en een onderstreepte + reset(): void. Hoe worden die leden naar Java vertaald?Toon antwoordVerberg antwoord
# markeert protected zichtbaarheid, terwijl onderstrepen een statisch lid markeert. Het plusteken maakt reset public en het retourtype is void; onderstrepen betekent niet abstract, waarvoor cursief of een abstracte markering wordt gebruikt.Vraag 9 van 10
public boolean accepts(int grams)?Toon antwoordVerberg antwoord
Vraag 10 van 10
base heeft de klasse Engine met pakkettoegang. De expliciet gedeclareerde constructor en de methode run() hebben beide geen toegangsmodifier. Een niet-verwante Screen in base.ui moet new Engine().run() aanroepen. Welke minimale zichtbaarheidswijzigingen maken die aanroep mogelijk?Toon antwoordVerberg antwoord
base.ui is een afzonderlijk pakket, dus alle drie hebben public toegang nodig voor deze niet-verwante aanroeper. Als je alleen het type en de methode toegankelijk maakt, blijft de constructie geblokkeerd.Set 2: Klasserelaties en verantwoordelijkheden in lagen10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
0..* naast Book. Met hoeveel boeken mag één bibliotheek verbonden zijn?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
List<Session> begint leeg en geen enkele methode controleert de grootte. Wat ontbreekt in de implementatie?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Printable. Welke Java-subtypedeclaratie stelt die lijn voor?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Basket bevat private List<Product> products; en breidt Product niet uit. Hoe moet de hoofdrelatie worden weergegeven?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
CatalogRepository. Hoe kan een test de echte database vermijden?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
rules.OrderService importeert storage.OrderRepository. Die interface noemt rules.Order in haar signaturen en opslagimplementaties implementeren de interface. Hoe kan het contract worden verplaatst om de cyclische afhankelijkheid tussen de pakketten rules en storage op te heffen en tegelijk uitwisselbare opslag te behouden?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 3: Asserties, gevalskeuze en onafhankelijke tests10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
actual terug en het vereiste resultaat is 42. Welke Jupiter-gelijkheidsassertie geeft de waarden de juiste labels?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
double-berekening wijkt ongeveer 0,0000000000000001 af van 0,6. De vereiste numerieke nauwkeurigheid is 0,000001. Hoe moet het verwachte resultaat worden gecontroleerd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
reading <= 800 in reading < 800. Welke geldige meetwaarde legt de fout bloot?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
IllegalArgumentException. Hoe moet een Jupiter-test de aanroep uitvoeren?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
250 + ((grams - 3000) / 500) * 50 boven de grens. Welke nieuwe test legt de afrondingsfout het directst bloot?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 4: Buildconfiguratie en foutrapporten10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
TotalsChecks.java importeert org.junit.Test. De opnamepatronen van Surefire zijn ongewijzigd. Welke combinatie van correcties laat de klasse compileren en als ontdekte Jupiter-test uitvoeren?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
platform(...). Waarom kunnen declaraties van bijbehorende JUnit-afhankelijkheden afzonderlijke versienummers weglaten?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
org.junit.jupiter.api ontbreekt. De enige Jupiter-declaratie is testRuntimeOnly("org.junit.jupiter:junit-jupiter") en de testtaak gebruikt al het JUnit Platform. Welke configuratiecorrectie verhelpt het probleem in deze fase?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 5: Onderzoek een volledige Java-wijziging10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
1 naast Department en 0..* naast Employee. Wat staat het model toe voor één afdeling en wat vereist het voor één werknemer?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
base.Item heeft een protected instantieveld weight. In een ander pakket heeft Special extends Item een instantiemethode met parameters Item a en Special b. Welke expressies kunnen binnen die methode rechtstreeks toegang krijgen tot het overgeërfde veld?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
MemoryArchive de interface Archive realiseert en via een navigeerbare associatie één of meer Entry-objecten bevat. Een gegenereerde klasse vermeldt momenteel extends Archive en maakt een lege lijst zonder controles. Welke combinatie van correcties behoudt het model?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
year % 4 == 0 terug en slaagt voor tests met 2031 en 2032. Een nieuwe test voor 2100 moet dat jaar afwijzen en een latere test voor 2400 moet het accepteren. Welke volgorde van gedragswijzigingen past bij de openstaande gevallen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
< 2500 voor de lagere tariefgroep gebruikt. Welk extra paar controles legt beide fouten bloot?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
testRuntimeOnly("org.junit.jupiter:junit-jupiter:6.1.3") en tasks.test { }. Een Jupiter-testklasse staat onder src/main/java en de annotatie compileert niet. Met welke wijzigingen kan de klasse compileren en onder Jupiter worden uitgevoerd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
target staan. Wat bewijst dat bestand over de broncode van vandaag?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
Afhankelijkheid
Parcel creëert een afhankelijkheid van de definitie van het type voor een verzendpakket.Afhankelijkheidsbereik
src/test/java zonder er een afhankelijkheid van de hoofdcode van te maken.Afhankelijkheidsconfiguratie
Afhankelijkheidsinjectie
Aggregatie
Applicatielogica
Artefactcoördinaten
Assertie
Associatie
parcels aan de kant van het pakket.Attribuut
- weightGrams: int stelt een privéveld voor een geheel getal voor dat het gewicht van het pakket bevat.B
Beschermde toegang
Bill of materials
Broncodemap
src/main/java voor productiecode in een standaardbuild.parcels.domain.Parcel is het pad onder de broncodemap parcels/domain/Parcel.java.Buildlevenscyclus
Buildtool
C
Compositie
Cyclische pakketafhankelijkheid
E
Equivalentieklasse
Exceptieassertie
assertThrows kan controleren of een gewicht van nul wordt afgewezen en de exceptie teruggeven om het bericht te controleren.G
Gebruikersinterface
Gegevenstoegangslaag
Generalisatie
Letter naar Item wordt vertaald naar een Java-declaratie waarin Letter de klasse Item uitbreidt.Geparametriseerde test
Gradle-taak
Grensgeval
I
Import van één type
import java.util.ArrayList; geeft precies aan welke ArrayList het bestand gebruikt.Importdeclaratie
Scanner importeert, kan de eenvoudige naam in de velddeclaratie gebruiken in plaats van java.util.Scanner.Instabiele test
Interfacecontract
save via het repositorycontract aanroepen, ongeacht of de opslag geheugen of een bestand gebruikt.J
Jokerimport
java.util.* heeft java.util.function.Function nog steeds een eigen import of volledige naam nodig.JUnit Jupiter
@Test van Jupiter te gebruiken, blijft de testannotatie in overeenstemming met de engine die de build heeft gekozen.JUnit Platform
useJUnitPlatform() wanneer de tests van het project Jupiter-annotaties gebruiken.L
Laag
Lambda-expressie
() -> calculator.feeInCents(0) kan JUnit de ongeldige aanroep binnen de assertie uitvoeren.Levenscyclusmethode
@BeforeEach bouwt veranderlijke objecten opnieuw op, terwijl @AfterEach tijdelijke testbronnen kan verwijderen.M
Multipliciteit
1..* naast Parcel staat, moet elke zending minstens één verbonden pakket behouden.N
Naamconflict
List nodig heeft, kan het ene importeren en voor het andere de volledig gekwalificeerde naam gebruiken.Naamloos pakket
Navigeerbaarheid
O
Operatie
+ totalWeightGrams(): int beschrijft een publieke methode zonder het algoritme te geven.P
Pakket
Pakketdeclaratie
package com.example.parcels.domain; schrijft, wordt dat pakket onderdeel van de volledige naam van de klasse.Pakketgroepering
Pakkettoegang
Plug-in
Presentatielaag
Publieke interface
R
Realisatie
Refactoring
Regressie
Repository
Rood–groen–refactoren
S
Samenstelling van objecten
Scheiding van verantwoordelijkheden
Statisch lid
Statische import
assertEquals vaak statisch, zodat elke controle leest als een directe assertieaanroep.Superklasse
Surefire
ShippingCalculatorTest voldoet aan een standaard opnamepatroon van Surefire.T
Testgestuurde ontwikkeling
Testlijst
Testonafhankelijkheid
Testopstelling
Toegangsmodifier
Tolerantie
Transitieve afhankelijkheid
U
UML-klassendiagram
Unittest
V
Vereenvoudigde testimplementatie
Verwachte uitkomst
Volledig gekwalificeerde naam
java.util.List onderscheid je de collectie-interface van een ander type dat ook List heet.Voorbereiden–uitvoeren–controleren
W
Wrapper
./mvnw test.Z
Zichtbaarheidssymbool
+ public, - private, # protected of ~ pakkettoegang.- wordt getoond, moet private zichtbaarheid behouden wanneer het diagram naar Java wordt vertaald.- 1Eerste Java-programma’s
- 2Voorwaarden en lussen in Java
- 3Java-methoden
- 4Lijsten, arrays en strings in Java
- 5Java-klassen en objecten
- 6Objecten, referenties en gelijkheid in Java
- 7HashMaps en collecties in Java
- 8Overerving in Java
- 9Excepties en bestanden in Java
- 10Klassendiagrammen en unittests in Java
- 11Lambda-expressies en streams in Java
Over deze les
Door StudeliFout melden
Maak je eigen Studiemateriaal
Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.