Merge pull request #5 from tdt4100/add-oving4

Add oving 4
This commit is contained in:
Andreas Omholt Olsen
2026-02-02 11:00:40 +01:00
committed by GitHub Enterprise
41 changed files with 1702 additions and 0 deletions

View File

@@ -8,3 +8,4 @@ Denne mappen inneholder øvingstekster for TDT4100 - Objektorientert programmeri
| [Øving 1](./oppgavetekster/oving1/README.md) | Java-syntaks og objektorientert tankegang |
| [Øving 2](./oppgavetekster/oving2/README.md) | Innkapsling og validering |
| [Øving 3](./oppgavetekster/oving3/README.md) | Klasser og testing |
| [Øving 4](./oppgavetekster/oving4/README.md) | Objektstrukturer |

View File

@@ -0,0 +1,26 @@
# Objektstrukturer - Card-oppgave del 2
Denne oppgaven handler om klasser for kortspill: `Card` (kort), `CardDeck` (kortstokk) og `CardHand` (korthånd), hvorav de to siste inneholder én eller flere `Card`-objekter. Oppgaven bygger på `Card` og `CardDeck` i [Innkapsling - Card-oppgave](../oving3/Card.md).
Filene i denne oppgaven skal lagres i [oving4/card](../../src/main/java/oving4/card).
**Merk**: Om du ikke har gjort `Card`-oppgaven allerede, bør du gjøre dette først. Hvis du ikke har gjort det, kan du kopiere koden fra [løsningsforslaget](https://git.ntnu.no/tdt4100/tdt4100-lf-25/blob/main/src/main/java/oving3/card/Card.java), som kommer til å være tilgjengelig etter siste demonstrasjonsfrist for øving 3.
I mange sammenhenger vil objekter av en klasse inneholde eller "eie" objekter av andre klasser, og de underordnede objektene vil kunne flyttes/overføres mellom de overordnede. Når en klasse er assosiert med én instans av en (annen) klasse er dette en [1-1-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-1-assosiasjoner) og når en klasse er assosiert med flere instanser av en annen klasse er dette en [1-n-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner). Et eksempel er kortspill, hvor kortene starter i kortstokken, fordeles på korthender og til slutt ender i en kortbunke. Et kort kan bare være ett sted om gangen, og må overføres fra ett sted til et annet, f.eks. fra kortstokk til korthender i utdelingsfasen. I [Innkapsling - Card-oppgave](../oving3/Card.md) ble det lagd logikk for kortstokk og enkeltkort. I denne oppgaven skal du implementere logikk for korthender, og utvide kortstokkens logikk litt.
`Card`-klassen har du allerede implementert, men du må sannsynligvis kopiere koden over fra `oving3` til `oving4`. Her er det enklest å lage en ny `Card`-klasse i `oving4` og så lime inn innholdet fra den gamle. Husk å ha riktig `package`.
`CardDeck`-klassen har du også implementert, og denne må også flyttes på samme måte som `Card`. Denne klassen skal utvides:
- `void deal(CardHand cardHand, int n)` - flytter `n` kort fra kortstokken (`CardDeck`-objektet) til korthånda (`CardHand`-objektet, som er første argument), ved å ta ett og ett kort med høyeste gyldige indeks, fjerne det fra `CardDeck`-objektet og legge det til `CardHand`-objektet. Kast unntak av typen `IllegalArgumentException` dersom `cardHand` er `null`.
`CardHand` er en ny klasse som skal implementeres. `CardHand`-objekter inneholder initielt ingen kort, og klassen inneholder de samme standardmetodene som `CardDeck`, altså `getCardCount()` og `getCard(int)`, for å lese hvor mange og hvilke kort den inneholder. I tillegg har den to metoder for å endre tilstand:
- `void addCard(Card)` - legger argumentet til dette `CardHand`-objektet. Kast unntak av typen `IllegalArgumentException` dersom argumentet er `null`.
- `Card play(int n)` - returnerer og fjerner kort nr. `n` (første kort har nr. $0$) fra dette `CardHand`-objektet (som om det ble spilt ut).
## Java-kode
Utvid `CardDeck` og lag `CardHand` som beskrevet over. Test klassene med selvlagde main-metoder og ved å kjøre JUnit-testene.
Testkode for denne oppgaven finner du her: [oving4/card/CardTest.java](../../src/test/java/oving4/card/CardTest.java), [oving4/card/CardDeckTest.java](../../src/test/java/oving4/card/CardDeckTest.java), [oving4/card/CardHandTest.java](../../src/test/java/oving4/card/CardHandTest.java).

View File

@@ -0,0 +1,62 @@
# Objektstrukturer - Partner-oppgave
Denne oppgaven handler om en `Partner`-klasse med en [1-1-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-1-assosiasjoner) kalt partner tilbake til samme klasse (altså kjønnsnøytralt partnerskap) og det å sikre konsistens, slik at Partner-objekter er parvis knyttet sammen.
En viktig del av det å implementere assosiasjoner er å sikre konsistens, dvs. at objekter i hver ende av en kobling refererer korrekt til hverandre. Et eksempel på dette for [1-1-assosiasjoner](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-1-assosiasjoner) er (kjønnsnøytralt) partnerskap, hvor to partnere er koblet til hverandre når partnerskap inngås og kobles fra hverandre ved en evt. skillsmisse. I denne oppgaven skal en `Partner`-klasse implementeres og ulike situasjoner håndteres korrekt, som illustrert nedenfor.
`Partner`-klassen skal inneholde informasjon om _navn_ (en `String` ulik `null`), som bare skal kunne settes i konstruktøren, og _partneren_, som er et annet `Partner`-objekt. Navnet er ikke viktig for oppførselen, men er grei å ha med i en `toString()`-metode, for å skille `Partner`-objektene fra hverandre. `Partner`-klassen skal ha følgende metoder for å lese tilstanden:
- `String getName()` - returnerer navnet knyttet til dette `Partner`-objektet.
- `Partner getPartner()` - returnerer `Partner`-objektet som er knyttet til dette `Partner`-objektet, evt. `null`, hvis partnerskap ikke er inngått.
`Partner`-klassen har kun én endringsmetode, `void setPartner(Partner)`, som brukes både for å inngå partnerskap, når argumentet er et `Partner`-objekt, og oppløse det, når argumentet er `null`. Figurene under illustrerer de tre tilfellene som må kunne håndteres, og som `JUnit`-testene sjekker.
## Eksempler på kall
### 1. Inngåelse av partnerskap
**Kall**: `p1.setPartner(p2)`
**Beskrivelse**: `Partner`-objektene `p1` og `p2` kobles sammen med ett kall til `setPartner`. Før kallet er `p1` og `p2` ikke koblet sammen, og etter kallet er det koblet sammen.
**Før kall**:
![partner1](./img/partner1.png)
**Etter kall**:
![partner2](./img/partner2.png)
### 2. Oppløsning av partnerskap
**Kall**: `p1.setPartner(null)`
**Beskrivelse**: `Partner`-objektene `p1` og `p2` kobles fra hverandre med ett kall til `setPartner` med `null` som argument. Før kallet er `p1` og `p2` koblet sammen, og etter kallet er det ikke lenger koblet sammen.
**Før kall**:
![partner2](./img/partner2.png)
**Etter kall**:
![partner1](./img/partner1.png)
### 3. Oppløsning og inngåelse av partnerskap i ett
**Kall**: `p1.setPartner(p3)`
**Beskrivelse**: `Partner`-objektene `p1`, `p2`, `p3` og `p4` er parvis koblet sammen, før ett kall til `setPartner` kobler sammen `p1` og `p3`, mens `p2` og `p4` kobles fra deres tidligere partnere.
**Før kall**:
![partner3](./img/partner3.png)
**Etter kall**:
![partner4](./img/partner4.png)
## Gjøremål
Oppgaven er (enkelt og greit) å implementere `Partner`-klassen og sjekke (f.eks. med en `main()`-metode) at `Partner`-objektene oppfører seg som de skal.
Testkode for denne oppgaven finner du her: [oving4/PartnerTest.java](../../src/test/java/oving4/PartnerTest.java).

View File

@@ -0,0 +1,134 @@
# Objektstrukturer - Person-oppgave
Denne oppgaven handler om en `Person`-klasse med en [1-n-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner) med rollene _children_ og _mother_/_father_ til samme klasse (altså barn-mor/far-forhold) og det å sikre konsistens, slik at foreldre og barn er korrekt knyttet sammen.
En viktig del av det å implementere assosiasjoner er å sikre konsistens, dvs. at objekter i hver ende av en kobling refererer korrekt til hverandre. Et eksempel på dette for [1-n-assosiasjoner](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner) er foreldreskap, hvor foreldre og barn er koblet til samme i et slektstre. I denne oppgaven skal en `Person`-klasse implementeres og det å legge til (og fjerne) barn håndteres korrekt, som illustrert nedenfor.
`Person`-klassen skal inneholde informasjon om _navn_ (en `String` ulik `null`) og _kjønn_ (en `char`, `'F'` eller `'M'`), som bare skal kunne settes i konstruktøren, og _mor_, _far_ og _barn_, som er andre `Person`-objekter. Navnet er ikke viktig for oppførselen, men er grei å ha med i en `toString()`-metode, for å skille `Person`-objektene fra hverandre. `Person`-klassen skal ha følgende metoder for å lese tilstanden:
- `String getName()` - returnerer navnet knyttet til dette `Person`-objektet.
- `char getGender()` - returnerer tegnet som representerer kjønnet, enten `'F'` eller `'M'`.
- `Person getMother()` - returnerer `Person`-objektet som er moren, evt. `null`.
- `Person getFather()` - returnerer `Person`-objektet som er faren, evt. `null`.
- `int getChildCount()` - returnerer antall barn dette `Person`-objektet har.
- `Person getChild(int n)` - returnerer barn nr. `n` (altså et `Person`-objekt), evt. utløser (et passende) unntak om `n` er for stor (eller liten).
`Person`-klassen har to sett med endringsmetoder, knyttet til de to rollene i hver ende av _children_-_mother_/-_father_-assosiasjonene.
Fra _children_-perspektivet har vi følgende to metoder:
- `void addChild(Person)` - oppretter en kobling til et barn (et annet `Person`-objekt). Dersom `Person`-objektet som metoden kalles på, er en _kvinne_, så skal denne bli barnets _mor_, og motsatt, dersom `Person`-objektet som metoden kalles på, er en _mann_, så skal denne bli barnets _far_. Argumentet kan ikke være `null`.
- `void removeChild(Person)` - fjerner en kobling til et barn (et annet `Person`-objekt). Dersom `Person`-objektet som metoden kalles på, er _moren_ til argumentet, så skal _mother_-koblingen fjernes, og motsatt, dersom `Person`-objektet som metoden kalles på, er argumentets _far_, så skal _father_-koblingen fjernes.
Fra _mother_/_father_-perspektivet har vi følgende to metoder:
- `void setMother(Person)` - setter argumentet (en kvinne) som _moren_ til `Person`-objektet som metoden kalles på. Argumentet får samtidig registrert `Person`-objektet som metoden kalles på, som sitt _barn_.
- `void setFather(Person)` - setter argumentet (en mann) som _faren_ til `Person`-objektet som metoden kalles på. Argumentet får samtidig registrert `Person`-objektet som metoden kalles på, som sitt _barn_.
Det som er verd å merke seg er at begge sett med metoder, `addChild`/`removeChild` og `setMother`/`setFather`, må ha logikk som håndterer koblingen den andre veien, så `addChild`/`removeChild` må kalle `setMother`/`setFather` og omvendt, eller ha kode med tilsvarende effekt. Dette kan være nokså fiklete, fordi en både må sikre konsistens og unngå uendelig nøstede kall (inntil du får `StackOverflowException`).
Listen og figurene under illustrerer de fem tilfellene som må kunne håndteres, og som testes av testene det er lenket til.
## Eksempler på kall
### 1. Opprettelse av koblinger med `addChild`
**Kall**:
`marit.addChild(jens)`
`hallvard.addChild(jens)`
(Dette har samme effekt som kallene under punkt [2](#2-opprettelse-av-koblinger-med-setmother-og-setfather).)
**Før kall**:
![person1](./img/person1.png)
**Etter kall**:
![person2](./img/person2.png)
### 2. Opprettelse av koblinger med `setMother` og `setFather`
**Kall**:
`jens.setMother(marit)`
`jens.setFather(hallvard)`
(Dette har samme effekt som kallene under punkt [1](#1-opprettelse-av-koblinger-med-addchild).)
**Før kall**:
![person1](./img/person1.png)
**Etter kall**:
![person2](./img/person2.png)
### 3. Fjerning av koblinger med `removeChild`
**Kall**:
`marit.removeChild(jens)`
`hallvard.removeChild(jens)`
(Dette har samme effekt som kallene under punkt [4](#4-fjerning-av-koblinger-med-setmother-og-setfather).)
**Før kall**:
![person2](./img/person2.png)
**Etter kall**:
![person1](./img/person1.png)
### 4. Fjerning av koblinger med `setMother` og `setFather`
**Kall**:
`jens.setMother(null)`
`jens.setFather(null)`
(Dette har samme effekt som kallene under punkt [3](#3-fjerning-av-koblinger-med-removechild).)
**Før kall**:
![person2](./img/person2.png)
**Etter kall**:
![person1](./img/person1.png)
### 5. Fjerning og oppretting av kobling med `setMother` og `setFather`, en slags "adoption"
**Kall**:
`jens.setFather(torkel)`
`jens.setMother(jorunn)`
**Før kall**:
![person3](./img/person3.png)
**Etter kall**:
![person4](./img/person4.png)
## Oppgaven
Oppgaven er delt i to trinn, den første håndterer _children_- og _mother_/_father_-rollen isolert og uten krav om konsistens,
mens det andre skal sikre konsistens.
### Del 1
- Implementer `addChild`- og `removeChild`-metodene slik at `getChildCount`- og `getChild`-metodene virker som forventet. Disse metodene håndterer altså kun _children_-rollen.
- Implementer `setMother`- og `setFather`-metodene slik at `getMother`- og `getFather`-metodene virker som forventet. Disse metodene håndteres altså kun _mother_/_father_-rollen.
Test metodene ved å koble opp `Person`-objekter tilsvarende din egen familie. Du blir nødt til å bruke de tre metodene `addChild`, `setMother` og `setFather`. Prøv å få med minst tre generasjoner.
### Del 2
Utvid metodene til å sikre konsistens. Test at det fortsatt virker å koble opp din egen familie, denne gangen ved å bare bruke
`addChild` og ved å bare bruke `setMother` og `setFather`. Kjør `JUnit`-testene som hører til oppgaven.
Testkode for denne oppgaven finner du her: [oving4/PersonTest.java](../../src/test/java/oving4/PersonTest.java).

View File

@@ -0,0 +1,61 @@
# Øving 4: Objektstrukturer
## Øvingsmål
- Lære hva assosiasjoner er og hvordan dette brukes i OO
- Lære hvordan man sikrer konsistent oppførsel mellom assosierte objekter
## Øvingskrav
- Kunne implementere klasser som har assosiasjoner til én eller flere andre klasser
- Kunne sikre at disse assosiasjon er konsistente i enkle objektstrukturer
- Kunne implementere metoder som oppretter, oppdaterer og fjerner slike assosiasjoner
## Dette må du gjøre
### Del 1: Programmering
I denne øvingen skal du velge og gjennomføre ENTEN både Partner- og Card del 2-oppgavene ELLER minst én av Twitter-, Stopwatch- og Person-oppgavene. Merk at **noen av oppgavene i neste øving (øving 5), bygger videre på noen av oppgavene under**, disse er uthevet med **fet skrift**. Det er ikke et krav at man gjør de uthevede oppgavene, men de gir flere oppgaver å velge mellom i øving 6.
**Gjør enten _begge_ disse:**
- [Partner](./Partner.md) (lett)
- **[Card del 2](./Card.md)** (lett)
**Eller _minst én_ av følgende oppgaver:**
- **[Twitter](./Twitter.md)** (medium, men lang)
- [Stopwatch](./StopWatch.md) (medium)
- [Person](./Person.md) (medium/vanskelig)
Oppgavene for denne øvingen skal du lagre i [`src/main/java/oving4`](../../src/main/java/oving4). Test-filene ligger i [`src/test/java/oving4`](../../src/test/java/oving4).
Alle oppgavene ovenfor er høyst eksamensrelevante og det anbefales å ta en titt på alle sammen.
### Del 2: Klassediagram
- Lag et [klassediagram](https://www.ntnu.no/wiki/display/tdt4100/Klassediagrammer) for en av oppgavene du velger. Husk å få med relasjonene mellom klassene.
Diagrammet kan for eksempel skrives på papir eller tegnes/lages i et valgfritt program. Du trenger ikke levere inn diagrammene, men de skal vises til studass under godkjenning av øvingen.
### Del 3: Testing
Skriv kode som tester oppførselen til `CoffeeCup`-klassen, dvs. at du skal teste om metodene i listen under har rett oppførsel og returnerer det de skal, i tillegg til at du skal teste konstruktørene. Det er ikke nødvendig å teste absolutt alle mulige tilfeller, men det kreves at du tester den grunnleggende funksjonaliteten. Basert på resultatene dine, vurder om klassen er implementert på en logisk måte.
- `double getCapacity()`
- `double getCurrentVolume()`
- `void increaseCupSize(double)`
- `void drinkCoffee(double)`
- `void fillCoffee(double)`
Du finner `CoffeeCup`-klassen under [`src/main/java/oving4/testing`](../../src/main/java/oving4/testing).
Her er det anbefalt å bruke [JUnit](https://www.ntnu.no/wiki/display/tdt4100/Enhetstesting+med+JUnit), men det er lov å teste vha. `main()`-metoden også. Dersom du bruker JUnit må du opprette testen under navnet `CoffeeCupTest` i [`src/test/java/oving4/testing`](../../src/test/java/oving4/testing).
### Hjelp / mistanke om bugs
Ved spørsmål eller behov for hjelp konsulter studassen din i saltiden hans/hennes. Du kan også oppsøke andre studasser på sal eller legge ut et innlegg på [Piazza](https://piazza.com/ntnu.no/spring2025/tdt4100).
### Godkjenning
Last opp kildekode på Blackboard innen den angitte innleveringsfristen. Innlevert kode skal demonstreres for en læringsassistent innen én uke etter innleveringsfrist. Se for øvrig Blackboard-sidene for informasjon rundt organisering av øvingsopplegget og det tilhørende øvingsreglementet.

View File

@@ -0,0 +1,34 @@
# Objektstrukturer - StopWatchManager-oppgave
Denne oppgaven handler om en `StopWatchManager`-klasse som inneholder flere `StopWatch`-objekter. Oppgaven bygger på klassen lagd i [StopWatch-oppgaven](../oving1/Stopwatch.md) fra "tilstand og oppførsel".
Dersom du ikke har gjort [`StopWatch`-oppgaven](../oving1/Stopwatch.md) allerede, bør du gjøre denne først. Hvis du ikke har gjort det, kan du kopiere koden fra [løsningsforslaget](https://git.ntnu.no/tdt4100/tdt4100-lf-25/blob/main/src/main/java/oving1/StopWatch.java), som kommer til å være tilgjengelig etter siste demonstrasjonsfrist for øving 1. Merk at validering i `StopWatch`-klassen ble gjort i [Innkapsling](../oving2/Encapsulation.md).
Filene i denne oppgaven skal lagres i [oving4/stopwatch](../../src/main/java/oving4/stopwatch).
I mange sammenhenger vil objekter av en klasse inneholde eller "eie" objekter av andre klasser. Når en klasse er assosiert med én instans av en (annen) klasse er dette en [1-1-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-1-assosiasjoner) og når en klasse er assosiert med flere instanser av en annen klasse er dette en [1-n-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner).
I denne oppgaven skal du implementere en `StopWatchManager`-klasse som kan holde styr på flere stoppeklokker. Ved hjelp av `StopWatchManager` skal man enkelt kunne holde styr på flere stoppeklokker og sørge for at alle stoppeklokker får beskjed om tiden som går. Dette kan være nyttig hvis man f.eks. ønsker å holde styr på flere løpere i et skirenn der ikke alle starter og fullfører samtidig, men hvor allikevel klokken må gå for alle.
Det skal være mulig å opprette nye stoppeklokker med et tilhørende navn (streng). Navnet skal man senere kunne bruke til å hente stoppeklokken igjen eller fjerne stoppeklokken fra `StopWatchManager`. For å få til dette kan det være lurt å se litt på `Map` fra [Collection-rammeverket](https://www.ntnu.no/wiki/display/tdt4100/Collection-rammeverket).
`StopWatchManager` skal ha følgende endringsmetoder:
- `StopWatch newStopWatch(String name)` - Oppretter en ny stoppeklokke knyttet til navnet `name`. Returnerer den nye stoppeklokken. Argumentet kan ikke være `null` eller allerede være knyttet til en stoppeklokke.
- `void removeStopWatch(String name)` - Fjerner stoppeklokken tilknyttet navnet `name`.
- `void tick(int ticks)` - Informerer alle stoppeklokkene om at ticks tikk har gått.
`StopWatchManager` skal ha følgende lesemetoder:
- `StopWatch getStopWatch(String name)` - returnerer stoppeklokken tilknyttet navnet `name`.
- `Collection<StopWatch> getAllWatches()` - returnerer alle stoppeklokkene.
- `Collection<StopWatch> getStartedWatches()` - returnerer alle stoppeklokkene som er startet.
- `Collection<StopWatch> getStoppedWatches()` - returnerer alle stoppeklokkene som er stoppet.
**Merk**: Det er viktig at de metodene som returnerer en samling av stoppeklokker returnerer nye samlinger. De som får en samling må kunne endre på den (f.eks. fjerne elementer) uten at dette forstyrrer `StopWatchManager` eller andre som har fått samlinger tidligere.
## Java-kode
Kopier `StopWatch` fra `oving2`-pakken og lag `StopWatchManager` som beskrevet over. Test klassen med selvlagde `main()`-metoder og ved å kjøre `JUnit`-testene.
Testkode for denne oppgaven finner du her: [oving4/stopwatch/StopWatchTest.java](../../src/test/java/oving4/stopwatch/StopWatchTest.java) og [oving4/stopwatch/StopWatchManagerTest.java](../../src/test/java/oving4/stopwatch/StopWatchManagerTest.java).

View File

@@ -0,0 +1,67 @@
# Objektstrukturer - Twitter-oppgave
Denne oppgaven handler om en begrenset klone av `Twitter`, med to klasser, `TwitterAccount` og `Tweet`.
Filene i denne oppgaven skal lagres i [oving4/twitter](../../src/main/java/oving4/twitter).
En `Twitter`-konto kan følge andre `Twitter`-kontoer og motsatt: en `Twitter`-konto kan bli fulgt av andre `Twitter`-kontoer.
Dette er altså en gjensidig kobling: Hvis konto A følger konto B, så er konto B fulgt av konto A. En kan selvsagt ikke følge seg selv.
I tillegg har hver Twitter-konto en mengde _tweets_, som er små, korte tekster. En tweet hører til den kontoen den ble sendt fra. Hvis noen finner en annen sin tweet interessant har man muligheten til å retweete denne. Da lager man en tweet som refererer til originalen, og (implisitt) få original-tweeten sin tekst. Merk at i en kjede av retweets, så vil alle referere til samme original-tweet. Med andre ord, hvis tweet B er en retweet av A og tweet C er en retweet av B, vil både tweet B og C ha A som original-tweet, slik det er vist under.
**Riktig objektstrutur**, når B er en retweet av A og C er en retweet av B:
![twitter1](./img/twitter1.png)
**Feil objektstrutur**, når B er en retweet av A og C er en retweet av B:
![twitter2](./img/twitter2.png)
## Tweet-klassen
`Tweet` skal ha to konstruktører, en for hver måte å tweete på:
- `Tweet(TwitterAccount, String)` - En ny original-tweet. Ingen av argumentene kan være `null`.
- `Tweet(TwitterAccount, Tweet)` - En retweet av `Tweet`-argumentet. Utløser et passende unntak hvis original-tweeten er fra samme konto. Ingen av argumentene kan være `null`.
`Tweet` skal ha metodene:
- `String getText()` - returnerer teksten til en tweet.
- `TwitterAccount getOwner()` - returnerer kontoen som tweeten kom fra.
- `Tweet getOriginalTweet()` - returnerer original-tweeten, hvis den er en retweet, ellers `null`.
- `int getRetweetCount()` - returnerer antall ganger denne tweeten har blitt retweetet.
## TwitterAccount-klassen
`TwitterAccount` skal ha konstruktøren:
- `TwitterAccount(String)` - som tar inn brukernavnet.
`TwitterAccount` skal ha metodene:
- `String getUserName()` - returnerer brukernavnet.
- `void follow(TwitterAccount account)` - denne (`this`) kontoen starter å følge account. Hvis account allerede følges, skal metoden ikke gjøre noe. Dersom `account` er seg selv eller `null`, skal metoden kaste et passende unntak.
- `void unfollow(TwitterAccount account)` - slutt å følge account.
- `boolean isFollowing(TwitterAccount account)` - returnerer om denne kontoen følger account.
- `boolean isFollowedBy(TwitterAccount account)` - returnerer om account følger denne kontoen.
- `void tweet(String)` - lager en ny tweet for denne kontoen.
- `void retweet(Tweet tweet)` - retweeter tweet fra denne kontoen.
- `Tweet getTweet(int i)` - returner tweet nummer `i`, der $1$ er den nyeste, $2$ den nest nyeste, osv. (Merk rekkefølgen!)
- `int getTweetCount()` - returner antall tweets til kontoen.
- `int getRetweetCount()` - returner antall retweets av tweets fra denne kontoen.
## Del 1
- Implementer `Tweet`-klassen.
- For å teste klassen må du sende inn TwitterAccount-objekter i konstruktøren. Lag en forenklet versjon av `TwitterAccount`-klassen for dette formålet, der du kun implementerer konstruktøren og evt. en passende `toString()`-metode. Dette gjør det mulig å teste `Tweet`-klassen din uten at du må implementere hele `TwitterAccount`-klassen først.
## Del 2
- Implementer `TwitterAccount`-klassen.
- Test klassen og dens samspill med `Tweet`-klassen ved å lage Twitter-konto for deg selv og noen av vennene dine. La noen av kontoene følge hverandre, tweete og retweete.
Testkode for denne oppgaven finner du her: [oving4/twitter/TweetTest.java](../../src/test/java/oving4/twitter/TweetTest.java) og [oving4/twitter/TwitterAccountTest.java](../../src/test/java/oving4/twitter/TwitterAccountTest.java).
## Frivillig utvidelse
På Twitter kan man markere en annen sin tweet som en favoritt. Implementer passende metoder for å kunne gjøre dette. En konto må ha oversikt over hvilke tweets den har markert som favoritter, og en tweet må vite hvem og hvor mange som har markert den som favoritt. Hva synes du burde skje hvis man markerer en retweet som en favoritt?

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,8 @@
@startuml partner1
skinparam dpi 400
object "~#p1:Partner" as p1
object "~#p2:Partner" as p2
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,11 @@
@startuml partner2
skinparam dpi 400
object "~#p1:Partner" as p1
object "~#p2:Partner" as p2
p1 --> p2 : partner
p2 --> p1 : partner
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@@ -0,0 +1,15 @@
@startuml partner3
skinparam dpi 400
object "~#p1:Partner" as p1
object "~#p2:Partner" as p2
object "~#p3:Partner" as p3
object "~#p4:Partner" as p4
p1 --> p2 : partner
p2 --> p1 : partner
p3 --> p4 : partner
p4 --> p3 : partner
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,13 @@
@startuml partner4
skinparam dpi 400
object "~#p1:Partner" as p1
object "~#p2:Partner" as p2
object "~#p3:Partner" as p3
object "~#p4:Partner" as p4
p1 --> p3 : partner
p3 --> p1 : partner
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,9 @@
@startuml person1
skinparam dpi 400
object "~#hallvard:Person" as hallvard
object "~#marit:Person" as marit
object "~#jens:Person" as jens
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -0,0 +1,14 @@
@startuml person2
skinparam dpi 400
object "~#hallvard:Person" as hallvard
object "~#marit:Person" as marit
object "~#jens:Person" as jens
marit --> jens : children
jens --> marit : mother
jens --> hallvard : father
hallvard --> jens : children
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -0,0 +1,16 @@
@startuml person3
skinparam dpi 400
object "~#hallvard:Person" as hallvard
object "~#marit:Person" as marit
object "~#jens:Person" as jens
object "~#torkel:Person" as torkel
object "~#jorunn:Person" as jorunn
marit --> jens : children
jens -u-> marit : mother
jens --> hallvard : father
hallvard --> jens : children
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -0,0 +1,16 @@
@startuml person4
skinparam dpi 400
object "~#hallvard:Person" as hallvard
object "~#marit:Person" as marit
object "~#jens:Person" as jens
object "~#torkel:Person" as torkel
object "~#jorunn:Person" as jorunn
jorunn --> jens : children
jens -u-> jorunn : mother
jens --> torkel : father
torkel --> jens : children
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@@ -0,0 +1,20 @@
@startuml twitter1
skinparam dpi 400
object "~#a:Tweet" as a {
text = "Kvitre-kvitre"
}
object "~#b:Tweet" as b {
text = "Kvitre-kvitre"
}
object "~#c:Tweet" as c {
text = "Kvitre-kvitre"
}
b --> a : originalTweet
c --> a : originalTweet
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,20 @@
@startuml twitter2
skinparam dpi 400
object "~#a:Tweet" as a {
text = "Kvitre-kvitre"
}
object "~#b:Tweet" as b {
text = "Kvitre-kvitre"
}
object "~#c:Tweet" as c {
text = "Kvitre-kvitre"
}
b --> a : originalTweet
c --> b : originalTweet
@enduml

View File

View File

View File

@@ -0,0 +1,73 @@
package oving4.testing;
public class CoffeeCup {
private double capacity;
private double currentVolume;
public CoffeeCup() {
this.capacity = 0.0;
this.currentVolume = 0.0;
}
public CoffeeCup(double capacity, double currentVolume) {
if (!CoffeeCup.isValidCapacity(capacity)) {
throw new IllegalArgumentException("Illegal capacity given.");
}
this.capacity = capacity;
if (!this.isValidVolume(currentVolume)) {
throw new IllegalArgumentException("Illegal volume given.");
}
this.currentVolume = currentVolume;
}
public double getCapacity() {
return this.capacity;
}
public double getCurrentVolume() {
return this.currentVolume;
}
private static boolean isValidCapacity(double capacity) {
return capacity >= 0.0;
}
public void increaseCupSize(double biggerCapacity) {
if (CoffeeCup.isValidCapacity(biggerCapacity)) {
this.capacity += biggerCapacity;
}
}
private boolean isValidVolume(double volume) {
return volume <= this.capacity && volume >= 0.0;
}
private boolean canDrink(double volume) {
return volume <= this.currentVolume;
}
public void drinkCoffee(double volume) {
if (!this.isValidVolume(volume) || !this.canDrink(volume)) {
throw new IllegalArgumentException("You cannot drink that much coffee!");
}
this.currentVolume -= volume;
}
public void fillCoffee(double volume) {
if (!this.isValidVolume(this.currentVolume + volume)) {
throw new IllegalArgumentException(
"You just poured coffee all over the table. Good job.");
}
this.currentVolume += volume;
}
@Override
public String toString() {
return String.format("[CoffeeCup capacity=%.2f, currentVolume=%.2f]", this.capacity,
this.currentVolume);
}
}

View File

View File

@@ -0,0 +1,74 @@
package oving4;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class PartnerTest {
private Partner p1;
private Partner p2;
@BeforeEach
public void setUp() {
p1 = new Partner("P1");
p2 = new Partner("P2");
}
@Test
@DisplayName("Check that the constructor initializes correctly")
public void testConstructor() {
assertNull(p1.getPartner());
assertNull(p2.getPartner());
assertThrows(IllegalArgumentException.class, () -> {
new Partner(null);
}, "Name cannot be null");
}
@Test
@DisplayName("Check that P1 and P2 are partners after p1.setPartner(p2)")
public void simplePartnership() {
assertNull(p1.getPartner());
assertNull(p2.getPartner());
p1.setPartner(p2);
assertEquals(p1.getPartner(), p2, "P1 should be partner to P2");
assertEquals(p2.getPartner(), p1, "P2 should be partner to P1");
}
@Test
@DisplayName("Check that one can split up a partnership")
public void partnershipWithDivorce() {
p1.setPartner(p2);
assertEquals(p1.getPartner(), p2, "P1 should be partner to P2");
assertEquals(p2.getPartner(), p1, "P2 should be partner to P1");
p1.setPartner(null);
assertNull(p1.getPartner());
assertNull(p2.getPartner());
}
@Test
@DisplayName("Check that combined breakup followed by the creation of a new partnership works")
public void swinger() {
Partner p3 = new Partner("P3");
Partner p4 = new Partner("P4");
p1.setPartner(p2);
p3.setPartner(p4);
assertEquals(p1.getPartner(), p2, "P1 should be the partner of P2");
assertEquals(p2.getPartner(), p1, "P2 should be the partner of P1");
assertEquals(p3.getPartner(), p4, "P3 should be the partner of P4");
assertEquals(p4.getPartner(), p3, "P4 should be the partner of P3");
p1.setPartner(p4);
assertEquals(p1.getPartner(), p4, "P4 should be the partner of P1");
assertEquals(p4.getPartner(), p1, "P1 should be the partner of P4");
assertNull(p2.getPartner());
assertNull(p3.getPartner());
}
}

View File

@@ -0,0 +1,333 @@
package oving4;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collection;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class PersonTest {
private Person anne;
private Person hallvard;
private Person jens;
private Person marit;
private static void hasChildren(Person person, Collection<Person> children) {
assertEquals(children.size(), person.getChildCount());
for (Person child : children) {
boolean found = false;
int i = 0;
while (i < person.getChildCount()) {
if (child == person.getChild(i)) {
found = true;
break;
}
i++;
}
assertTrue(found);
}
}
@BeforeEach
public void setUp() {
anne = new Person("Anne", 'F');
hallvard = new Person("Hallvard", 'M');
jens = new Person("Jens", 'M');
marit = new Person("Marit", 'F');
}
@Test
@DisplayName("Constructor")
public void testConstructor() {
assertEquals("Anne", anne.getName());
assertEquals('F', anne.getGender());
assertEquals(0, anne.getChildCount());
assertThrows(IllegalArgumentException.class, () -> {
new Person(null, 'M');
}, "Name cannot be null");
assertThrows(IllegalArgumentException.class, () -> {
new Person("Anne", 'X');
}, "X is not a valid gender");
}
@Test
@DisplayName("Child cannot be null")
public void testAddChildException() {
assertThrows(IllegalArgumentException.class, () -> {
anne.addChild(null);
});
}
@Test
@DisplayName("Woman cannot be father")
public void testFatherException() {
assertThrows(IllegalArgumentException.class, () -> {
jens.setFather(marit);
});
assertThrows(IllegalArgumentException.class, () -> {
anne.setFather(marit);
});
}
@Test
@DisplayName("Man cannot be mother")
public void testMotherException() {
assertThrows(IllegalArgumentException.class, () -> {
jens.setMother(hallvard);
});
assertThrows(IllegalArgumentException.class, () -> {
anne.setMother(hallvard);
});
}
@Test
@DisplayName("Man cannot be his own father")
public void testSelfFatherException() {
assertThrows(IllegalArgumentException.class, () -> {
jens.setFather(jens);
});
}
@Test
@DisplayName("Woman cannot be her own mother")
public void testSelfMotherException() {
assertThrows(IllegalArgumentException.class, () -> {
anne.setMother(anne);
});
}
@Test
@DisplayName("Setting father with setFather")
public void testSetFather() {
jens.setFather(hallvard);
// Check state of Hallvard
assertEquals(null, hallvard.getFather());
assertEquals(null, hallvard.getMother());
PersonTest.hasChildren(hallvard, List.of(jens));
// Check state of Jens
assertEquals(hallvard, jens.getFather());
assertEquals(null, jens.getMother());
assertEquals(0, jens.getChildCount());
anne.setFather(hallvard);
// Check state of Hallvard
assertEquals(null, hallvard.getFather());
assertEquals(null, hallvard.getMother());
PersonTest.hasChildren(hallvard, List.of(jens, anne));
// Check state of Jens
assertEquals(hallvard, jens.getFather());
assertEquals(null, jens.getMother());
assertEquals(0, jens.getChildCount());
// Check state of Anne
assertEquals(hallvard, anne.getFather());
assertEquals(null, anne.getMother());
assertEquals(0, anne.getChildCount());
}
@Test
@DisplayName("Setting father with addChild")
public void testFatherAddChild() {
hallvard.addChild(jens);
// Check state of Hallvard
assertEquals(null, hallvard.getFather());
assertEquals(null, hallvard.getMother());
PersonTest.hasChildren(hallvard, List.of(jens));
// Check state of Jens
assertEquals(hallvard, jens.getFather());
assertEquals(null, jens.getMother());
assertEquals(0, jens.getChildCount());
hallvard.addChild(anne);
// Check state of Hallvard
assertEquals(null, hallvard.getFather());
assertEquals(null, hallvard.getMother());
PersonTest.hasChildren(hallvard, List.of(jens, anne));
// Check state of Jens
assertEquals(hallvard, jens.getFather());
assertEquals(null, jens.getMother());
assertEquals(0, jens.getChildCount());
// Check state of Anne
assertEquals(hallvard, anne.getFather());
assertEquals(null, anne.getMother());
assertEquals(0, anne.getChildCount());
}
@Test
@DisplayName("Setting mother with setMother")
public void testSetMother() {
jens.setMother(marit);
// Check state of Marit
assertEquals(null, marit.getFather());
assertEquals(null, marit.getMother());
PersonTest.hasChildren(marit, List.of(jens));
// Check state of Jens
assertEquals(null, jens.getFather());
assertEquals(marit, jens.getMother());
assertEquals(0, jens.getChildCount());
anne.setMother(marit);
// Check state of Marit
assertEquals(null, marit.getFather());
assertEquals(null, marit.getMother());
PersonTest.hasChildren(marit, List.of(jens, anne));
// Check state of Jens
assertEquals(null, jens.getFather());
assertEquals(marit, jens.getMother());
assertEquals(0, jens.getChildCount());
// Check state of Anne
assertEquals(null, anne.getFather());
assertEquals(marit, anne.getMother());
assertEquals(0, anne.getChildCount());
}
@Test
@DisplayName("Setting mother with addChild")
public void testMotherAddChild() {
marit.addChild(jens);
// Check state of Marit
assertEquals(null, marit.getFather());
assertEquals(null, marit.getMother());
PersonTest.hasChildren(marit, List.of(jens));
// Check state of Jens
assertEquals(null, jens.getFather());
assertEquals(marit, jens.getMother());
assertEquals(0, jens.getChildCount());
marit.addChild(anne);
// Check state of Marit
assertEquals(null, marit.getFather());
assertEquals(null, marit.getMother());
PersonTest.hasChildren(marit, List.of(jens, anne));
// Check state of Jens
assertEquals(null, jens.getFather());
assertEquals(marit, jens.getMother());
assertEquals(0, jens.getChildCount());
// Check state of Anne
assertEquals(null, anne.getFather());
assertEquals(marit, anne.getMother());
assertEquals(0, anne.getChildCount());
}
@Test
@DisplayName("Change father with setFather")
public void testChangeFatherSetFather() {
anne.setFather(jens);
// Check state of Anne
assertEquals(jens, anne.getFather());
// Check state of Jens
PersonTest.hasChildren(jens, List.of(anne));
anne.setFather(hallvard);
// Check state of Anne
assertEquals(hallvard, anne.getFather());
// Check state of Jens
assertEquals(0, jens.getChildCount());
// Check state of Hallvard
PersonTest.hasChildren(hallvard, List.of(anne));
}
@Test
@DisplayName("Change father with addChild")
public void testChangeFatherAddChild() {
jens.addChild(anne);
// Check state of anne
assertEquals(jens, anne.getFather());
// Check state of jens
PersonTest.hasChildren(jens, List.of(anne));
hallvard.addChild(anne);
// Check state of anne
assertEquals(hallvard, anne.getFather());
// Check state of jens
assertEquals(0, jens.getChildCount());
// Check state of hallvard
PersonTest.hasChildren(hallvard, List.of(anne));
}
@Test
@DisplayName("Change mother with setMother")
public void testChangeMotherSetMother() {
jens.setMother(anne);
// Check state of jens
assertEquals(anne, jens.getMother());
// Check state of anne
PersonTest.hasChildren(anne, List.of(jens));
jens.setMother(marit);
// Check state of jens
assertEquals(marit, jens.getMother());
// Check state of anne
assertEquals(0, anne.getChildCount());
// Check state of marit
PersonTest.hasChildren(marit, List.of(jens));
}
@Test
@DisplayName("Change mother with addChild")
public void testChangeMotherAddChild() {
anne.addChild(jens);
// Check state of jens
assertEquals(anne, jens.getMother());
// Check state of anne
PersonTest.hasChildren(anne, List.of(jens));
marit.addChild(jens);
// Check state of jens
assertEquals(marit, jens.getMother());
// Check state of anne
assertEquals(0, anne.getChildCount());
// Check state of marit
PersonTest.hasChildren(marit, List.of(jens));
}
}

View File

@@ -0,0 +1,59 @@
package oving4.card;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class CardDeckTest {
private CardDeck cardDeck;
private static void checkDeck(CardDeck deck, String deckAsString) {
String[] toStrings = deckAsString.split(",");
assertEquals(toStrings.length, deck.getCardCount(),
"CardDeck does not have the correct size");
int i = 0;
for (String toString : toStrings) {
Card card = deck.getCard(i);
String cardString = String.valueOf(card.getSuit()) + card.getFace();
assertEquals(toString, cardString,
String.format("Card at position %d was incorrect. CardDeck should have been %s",
i + 1, toStrings));
i++;
}
}
@BeforeEach
public void setUp() {
cardDeck = new CardDeck(2);
}
@Test
@DisplayName("Check that CardDeck is initialized to S1,S2,H1,H2,D1,D2,C1,C2")
public void testConstructor() {
CardDeckTest.checkDeck(cardDeck, "S1,S2,H1,H2,D1,D2,C1,C2");
}
@Test
@DisplayName("Check that CardDeck is shuffled to S1,D1,S2,D2,H1,C1,H2,C2")
public void testShufflePerfectly() {
cardDeck.shufflePerfectly();
CardDeckTest.checkDeck(cardDeck, "S1,D1,S2,D2,H1,C1,H2,C2");
}
@Test
@DisplayName("Check that deal gives out the last three cards")
public void testDeal() {
CardHand hand = new CardHand();
cardDeck.deal(hand, 3);
CardDeckTest.checkDeck(cardDeck, "S1,S2,H1,H2,D1");
assertThrows(IllegalArgumentException.class, () -> {
cardDeck.deal(null, 1);
}, "Cannot deal to null");
}
}

View File

@@ -0,0 +1,75 @@
package oving4.card;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class CardHandTest {
private CardHand cardHand;
private static void checkHand(CardHand hand, String deckAsString) {
String[] toStrings = deckAsString.split(",");
assertEquals(toStrings.length, hand.getCardCount(),
"The number of cards in the hand was incorrect");
int i = 0;
for (String toString : toStrings) {
Card card = hand.getCard(i);
String cardString = String.valueOf(card.getSuit()) + card.getFace();
assertEquals(toString, cardString, String.format(
"The card at position %d was incorrect. The hand should have contained %s",
i + 1, toStrings));
i++;
}
}
@BeforeEach
public void setUp() {
cardHand = new CardHand();
}
@Test
@DisplayName("Check that CardHand is initialized to empty")
public void testConstructor() {
CardDeck deck = new CardDeck(2);
deck.deal(cardHand, 3);
CardHandTest.checkHand(cardHand, "C2,C1,D2");
}
@Test
@DisplayName("Test the addCard method")
public void testAddCard() {
CardDeck deck = new CardDeck(2);
deck.deal(cardHand, 3);
CardHandTest.checkHand(cardHand, "C2,C1,D2");
cardHand.addCard(new Card('H', 1));
CardHandTest.checkHand(cardHand, "C2,C1,D2,H1");
assertThrows(IllegalArgumentException.class, () -> {
cardHand.addCard(null);
}, "Cannot add null card");
}
@Test
@DisplayName("Test the deal and play methods")
public void testDealPlay() {
CardDeck deck = new CardDeck(2);
deck.deal(cardHand, 3);
CardHandTest.checkHand(cardHand, "C2,C1,D2");
cardHand.play(1);
CardHandTest.checkHand(cardHand, "C2,D2");
cardHand.play(0);
CardHandTest.checkHand(cardHand, "D2");
cardHand.play(0);
assertEquals(cardHand.getCardCount(), 0);
}
}

View File

@@ -0,0 +1,46 @@
package oving4.card;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class CardTest {
private static void checkCard(Card card, char suit, int face) {
assertEquals(card.getSuit(), suit);
assertEquals(card.getFace(), face);
}
@Test
@DisplayName("Check that the constructor creates Card objects with correct values")
public void testConstructor() {
CardTest.checkCard(new Card('S', 1), 'S', 1);
CardTest.checkCard(new Card('S', 13), 'S', 13);
CardTest.checkCard(new Card('H', 1), 'H', 1);
CardTest.checkCard(new Card('H', 13), 'H', 13);
CardTest.checkCard(new Card('D', 1), 'D', 1);
CardTest.checkCard(new Card('D', 13), 'D', 13);
CardTest.checkCard(new Card('C', 1), 'C', 1);
CardTest.checkCard(new Card('C', 13), 'C', 13);
assertThrows(IllegalArgumentException.class, () -> {
new Card('X', 1);
}, "Should not be able to create a card of type X");
assertThrows(IllegalArgumentException.class, () -> {
new Card('S', 0);
}, "Should not be able to create a card with value 0");
assertThrows(IllegalArgumentException.class, () -> {
new Card('C', 14);
}, "Should not be able to create a card with value 14");
}
@Test
@DisplayName("Check that toString works as expected")
public void testToString() {
assertEquals("S1", new Card('S', 1).toString());
assertEquals("H13", new Card('H', 13).toString());
}
}

View File

@@ -0,0 +1,111 @@
package oving4.stopwatch;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class StopWatchManagerTest {
private StopWatchManager manager;
@BeforeEach
public void setUp() {
manager = new StopWatchManager();
}
@Test
@DisplayName("Create new StopWatch")
public void testNewStopWatch() {
StopWatch sw1 = manager.newStopWatch("SW1");
StopWatch sw2 = manager.newStopWatch("SW2");
assertEquals(sw1, manager.getStopWatch("SW1"));
assertEquals(sw2, manager.getStopWatch("SW2"));
assertThrows(IllegalArgumentException.class, () -> {
manager.newStopWatch(null);
}, "Name cannot be null");
assertThrows(IllegalArgumentException.class, () -> {
manager.newStopWatch("SW1");
}, "Name already exists");
}
@Test
@DisplayName("Ticker")
public void testTicks() {
StopWatch sw1 = manager.newStopWatch("SW1");
StopWatch sw2 = manager.newStopWatch("SW2");
manager.tick(1);
assertEquals(1, sw1.getTicks());
assertEquals(1, sw2.getTicks());
manager.tick(4);
assertEquals(5, sw1.getTicks());
assertEquals(5, sw2.getTicks());
}
@Test
@DisplayName("Remove StopWatches")
public void testRemoveStopWatches() {
assertEquals(0, manager.getAllWatches().size());
StopWatch sw1 = manager.newStopWatch("SW1");
assertEquals(1, manager.getAllWatches().size());
assertEquals(sw1, manager.getStopWatch("SW1"));
StopWatch sw2 = manager.newStopWatch("SW2");
assertEquals(2, manager.getAllWatches().size());
assertEquals(sw1, manager.getStopWatch("SW1"));
assertEquals(sw2, manager.getStopWatch("SW2"));
manager.removeStopWatch("SW1");
assertEquals(1, manager.getAllWatches().size());
assertEquals(null, manager.getStopWatch("SW1"));
manager.removeStopWatch("SW2");
assertEquals(0, manager.getAllWatches().size());
assertEquals(null, manager.getStopWatch("SW1"));
assertEquals(null, manager.getStopWatch("SW2"));
}
@Test
@DisplayName("Starting and stopping StopWatches")
public void testStartedStoppedWatches() {
assertEquals(0, manager.getStartedWatches().size());
manager.newStopWatch("SW1").start();
assertEquals(1, manager.getStartedWatches().size());
assertEquals(0, manager.getStoppedWatches().size());
assertTrue(manager.getStartedWatches().contains(manager.getStopWatch("SW1")));
assertTrue(manager.getStopWatch("SW1").isStarted());
manager.newStopWatch("SW2").start();
assertEquals(2, manager.getStartedWatches().size());
assertEquals(0, manager.getStoppedWatches().size());
assertTrue(manager.getStartedWatches().contains(manager.getStopWatch("SW1")));
assertTrue(manager.getStopWatch("SW1").isStarted());
assertFalse(manager.getStopWatch("SW1").isStopped());
assertTrue(manager.getStartedWatches().contains(manager.getStopWatch("SW2")));
assertTrue(manager.getStopWatch("SW2").isStarted());
assertFalse(manager.getStopWatch("SW2").isStopped());
manager.getStopWatch("SW2").stop();
assertEquals(1, manager.getStoppedWatches().size());
assertFalse(manager.getStoppedWatches().contains(manager.getStopWatch("SW1")));
assertFalse(manager.getStopWatch("SW1").isStopped());
assertTrue(manager.getStoppedWatches().contains(manager.getStopWatch("SW2")));
assertTrue(manager.getStopWatch("SW2").isStopped());
manager.getStopWatch("SW1").stop();
assertEquals(2, manager.getStoppedWatches().size());
assertTrue(manager.getStoppedWatches().contains(manager.getStopWatch("SW1")));
assertTrue(manager.getStopWatch("SW1").isStopped());
assertTrue(manager.getStoppedWatches().contains(manager.getStopWatch("SW2")));
assertTrue(manager.getStopWatch("SW2").isStopped());
}
}

View File

@@ -0,0 +1,159 @@
package oving4.stopwatch;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class StopWatchTest {
private StopWatch stopWatch;
@BeforeEach
public void beforeEach() {
stopWatch = new StopWatch();
}
@Test
@DisplayName("Check that a newly created StopWatch object has correct values")
public void testConstructor() {
assertFalse(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
assertEquals(0, stopWatch.getTicks());
assertEquals(-1, stopWatch.getTime());
assertEquals(-1, stopWatch.getLapTime());
assertEquals(-1, stopWatch.getLastLapTime());
}
@Test
@DisplayName("Check that tick() without start does not change the time")
public void testTicksWithoutStart() {
stopWatch.tick(1);
assertEquals(-1, stopWatch.getTime());
assertEquals(1, stopWatch.getTicks());
stopWatch.tick(4);
assertEquals(-1, stopWatch.getTime());
assertEquals(5, stopWatch.getTicks());
}
@Test
@DisplayName("Start and stop the StopWatch and check that the time is correct")
public void testStartTickStop() {
stopWatch.start();
assertEquals(0, stopWatch.getTime());
assertEquals(0, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
assertThrows(IllegalStateException.class, () -> {
stopWatch.start();
}, "Cannot start an already running StopWatch");
stopWatch.tick(3);
assertEquals(3, stopWatch.getTime());
assertEquals(3, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
stopWatch.tick(5);
assertEquals(8, stopWatch.getTime());
assertEquals(8, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
stopWatch.stop();
assertEquals(8, stopWatch.getTime());
assertEquals(8, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertTrue(stopWatch.isStopped());
assertThrows(IllegalStateException.class, () -> {
stopWatch.stop();
}, "Cannot stop a StopWatch that is already stopped");
}
@Test
@DisplayName("Start and stop the StopWatch, and call tick() while it is not started")
public void testTickStartTickStopTick() {
stopWatch.tick(2);
assertEquals(-1, stopWatch.getTime());
assertEquals(2, stopWatch.getTicks());
assertFalse(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
stopWatch.start();
assertEquals(0, stopWatch.getTime());
assertEquals(2, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
stopWatch.tick(3);
assertEquals(3, stopWatch.getTime());
assertEquals(5, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
stopWatch.tick(5);
assertEquals(8, stopWatch.getTime());
assertEquals(10, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertFalse(stopWatch.isStopped());
stopWatch.stop();
assertEquals(8, stopWatch.getTime());
assertEquals(10, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertTrue(stopWatch.isStopped());
stopWatch.tick(3);
assertEquals(8, stopWatch.getTime());
assertEquals(13, stopWatch.getTicks());
assertTrue(stopWatch.isStarted());
assertTrue(stopWatch.isStopped());
assertThrows(IllegalArgumentException.class, () -> {
stopWatch.tick(-1);
}, "Time should not be able to go backward");
}
@Test
@DisplayName("Check that laps work as expected")
public void testLaps() {
assertThrows(IllegalStateException.class, () -> {
stopWatch.lap();
}, "Should not be able to start a new lap without starting the StopWatch");
stopWatch.start();
assertEquals(0, stopWatch.getTime());
assertEquals(0, stopWatch.getLapTime());
assertEquals(-1, stopWatch.getLastLapTime());
stopWatch.tick(3);
assertEquals(3, stopWatch.getTime());
assertEquals(3, stopWatch.getLapTime());
assertEquals(-1, stopWatch.getLastLapTime());
stopWatch.lap();
assertEquals(3, stopWatch.getTime());
assertEquals(0, stopWatch.getLapTime());
assertEquals(3, stopWatch.getLastLapTime());
stopWatch.tick(5);
assertEquals(8, stopWatch.getTime());
assertEquals(5, stopWatch.getLapTime());
assertEquals(3, stopWatch.getLastLapTime());
stopWatch.stop();
assertEquals(8, stopWatch.getTime());
assertEquals(0, stopWatch.getLapTime());
assertEquals(5, stopWatch.getLastLapTime());
assertThrows(IllegalStateException.class, () -> {
stopWatch.lap();
}, "Should not be able to start a new lap with a stopped StopWatch");
}
}

View File

View File

@@ -0,0 +1,93 @@
package oving4.twitter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class TweetTest {
private Tweet retweet1;
private Tweet tweet;
private TwitterAccount kari;
private TwitterAccount nils;
private TwitterAccount ole;
@BeforeEach
public void setUp() {
nils = new TwitterAccount("Nils");
ole = new TwitterAccount("Ole");
kari = new TwitterAccount("Kari");
tweet = new Tweet(nils, "Kvitre!");
retweet1 = null;
}
@Test
@DisplayName("Check that the constructor initializes correctly")
public void testNullInConstructors() {
assertEquals("Kvitre!", tweet.getText());
assertEquals(nils, tweet.getOwner());
assertThrows(IllegalArgumentException.class, () -> {
new Tweet(null, "Kvitre!");
}, "The tweet must have an owner");
assertThrows(IllegalArgumentException.class, () -> {
new Tweet(nils, (String) null);
}, "The tweet must have text");
assertThrows(IllegalArgumentException.class, () -> {
new Tweet(null, tweet);
}, "The tweet must have an owner");
assertThrows(IllegalArgumentException.class, () -> {
new Tweet(nils, (Tweet) null);
}, "The tweet must have an original tweet");
}
@Test
public void constructorNewTweet() {
assertEquals("Kvitre!", tweet.getText(),
"The constructor initialized the tweet with incorrect text");
assertEquals(nils, tweet.getOwner(),
"The constructor initialized the tweet with the wrong owner");
}
@Test
@DisplayName("Check that retweet has the same text but a different owner")
public void constructorRetweet() {
retweet1 = new Tweet(ole, tweet);
assertEquals("Kvitre!", retweet1.getText());
assertEquals(ole, retweet1.getOwner());
assertThrows(RuntimeException.class, () -> {
new Tweet(nils, tweet);
}, "A person should not be able to retweet themselves");
}
@Test
@DisplayName("Check that the original tweet is always correct")
public void getOriginalTweet() {
assertNull(tweet.getOriginalTweet());
retweet1 = new Tweet(ole, tweet);
assertEquals(tweet, retweet1.getOriginalTweet());
assertEquals(retweet1.getOriginalTweet().getText(), retweet1.getText());
assertEquals(tweet, retweet1.getOriginalTweet());
assertEquals(retweet1.getOriginalTweet().getText(), retweet1.getText());
}
@Test
@DisplayName("Check that retweet count increases when a tweet is retweeted")
public void getRetweetCount() {
assertEquals(0, tweet.getRetweetCount());
new Tweet(ole, tweet);
assertEquals(1, tweet.getRetweetCount());
new Tweet(kari, tweet);
assertEquals(2, tweet.getRetweetCount());
}
}

View File

@@ -0,0 +1,152 @@
package oving4.twitter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class TwitterAccountTest {
private TwitterAccount nils;
private TwitterAccount ole;
private static void checkFollow(TwitterAccount accountA, TwitterAccount accountB,
boolean AfollowsB, boolean BfollowsA) {
if (AfollowsB) {
assertTrue(accountA.isFollowing(accountB), String.format("%s should have followed %s",
accountA.getUserName(), accountB.getUserName()));
assertTrue(accountB.isFollowedBy(accountA),
String.format("%s should have been followed by %s", accountB.getUserName(),
accountA.getUserName()));
} else {
assertFalse(accountA.isFollowing(accountB),
String.format("%s should not have followed %s", accountA.getUserName(),
accountB.getUserName()));
assertFalse(accountB.isFollowedBy(accountA),
String.format("%s should not have been followed by %s", accountB.getUserName(),
accountA.getUserName()));
}
if (BfollowsA) {
assertTrue(accountB.isFollowing(accountA), String.format("%s should have followed %s",
accountB.getUserName(), accountA.getUserName()));
assertTrue(accountA.isFollowedBy(accountB),
String.format("%s should have been followed by %s", accountA.getUserName(),
accountB.getUserName()));
} else {
assertFalse(accountB.isFollowing(accountA),
String.format("%s should not have followed %s", accountB.getUserName(),
accountA.getUserName()));
assertFalse(accountA.isFollowedBy(accountB),
String.format("%s should not have been followed by %s", accountA.getUserName(),
accountB.getUserName()));
}
}
@BeforeEach
public void setUp() {
nils = new TwitterAccount("Nils");
ole = new TwitterAccount("Ole");
}
@Test
@DisplayName("Check that the constructor initializes correctly")
public void testConstructor() {
assertEquals("Nils", nils.getUserName());
assertEquals(0, nils.getTweetCount());
assertEquals("Ole", ole.getUserName());
assertEquals(0, ole.getTweetCount());
}
@Test
@DisplayName("Test that follow is implemented correctly")
public void testFollow() {
nils.follow(ole);
TwitterAccountTest.checkFollow(nils, ole, true, false);
ole.follow(nils);
TwitterAccountTest.checkFollow(nils, ole, true, true);
assertThrows(IllegalStateException.class, () -> {
nils.follow(nils);
}, "Should not be able to follow oneself");
assertThrows(IllegalArgumentException.class, () -> {
nils.follow(null);
}, "Should not be able to follow null");
}
@Test
public void testUnfollow() {
TwitterAccountTest.checkFollow(nils, ole, false, false);
nils.follow(ole);
TwitterAccountTest.checkFollow(nils, ole, true, false);
nils.unfollow(ole);
TwitterAccountTest.checkFollow(nils, ole, false, false);
}
@Test
public void testNewTweet() {
nils.tweet("Kvitre!");
assertEquals(1, nils.getTweetCount(), "The tweet count of Nils should be 1");
assertEquals("Kvitre!", nils.getTweet(1).getText(), "The text should have been 'Kvitre'");
nils.tweet("Kvitre igjen!");
assertEquals(2, nils.getTweetCount());
assertEquals("Kvitre igjen!", nils.getTweet(1).getText());
assertEquals("Kvitre!", nils.getTweet(2).getText());
}
@Test
public void testIllegalTweet() {
assertThrows(RuntimeException.class, () -> {
nils.getTweet(1);
}, "Should not be able to get a tweet that does not exist");
assertThrows(RuntimeException.class, () -> {
nils.getTweet(-1);
}, "Should not be able to get a tweet that does not exist");
nils.tweet("Kvitre!");
assertThrows(RuntimeException.class, () -> {
nils.getTweet(2);
}, "Should not be able to get a tweet that does not exist");
assertThrows(RuntimeException.class, () -> {
nils.getTweet(-1);
}, "Should not be able to get a tweet that does not exist");
}
@Test
@DisplayName("Check that the retweet count is correct, also when retweeting a retweet")
public void testRetweet() {
TwitterAccount kari = new TwitterAccount("Kari");
nils.tweet("Kvitre!");
assertEquals(1, nils.getTweetCount());
assertEquals("Kvitre!", nils.getTweet(1).getText());
ole.retweet(nils.getTweet(1));
assertEquals(1, nils.getTweetCount());
assertEquals(1, nils.getRetweetCount());
assertEquals(1, ole.getTweetCount());
assertEquals(0, ole.getRetweetCount());
assertEquals("Kvitre!", ole.getTweet(1).getText());
assertEquals(nils.getTweet(1), ole.getTweet(1).getOriginalTweet());
kari.retweet(ole.getTweet(1));
assertEquals(1, nils.getTweetCount());
assertEquals(2, nils.getRetweetCount());
assertEquals(1, ole.getTweetCount());
assertEquals(0, ole.getRetweetCount());
assertEquals(1, kari.getTweetCount());
assertEquals(0, kari.getRetweetCount());
assertEquals("Kvitre!", kari.getTweet(1).getText());
assertEquals(nils.getTweet(1), kari.getTweet(1).getOriginalTweet());
}
}