4102 Unit Test ParkingMachine
Denna utmaning är en introduktion till Unit Testing (sv. enhetstester). Testerna är färdigskrivna för den första utmaningen. När testerna skrivs i förväg kallas det för testdriven programmering (eng. test driven programming). Mer om Unit Test kan läsas i länkarna i slutet av dokumentet.
Ett Unit Test besår av tre delar: arrange, act och assert. Ett kodexempel visas nedan.
[TestMethod]
public void ValidInsertMoney()
{
// Arrange
ParkingMachine machine = new ParkingMachine();
// Act
machine.InsertMoney(30);
machine.InsertMoney(60);
// Assert
Assert.AreEqual(90, machine.CurrentTotal);
}
I koden ovan så händer följande:
Arrange: Skapar en parkeringsautomat.
Act: Sätter in pengar i automaten, först 30 kr sedan 60 kr.
Assert: Kollar att det finns 90 kr i automaten.
Utmaning 1 - sätt in pengar och skriv ut biljett
Klassen ParkingMachine representerar en parkeringsautomat där man kan sätt in ett godtyckligt belopp med pengar en eller flera gånger innan man köper in biljett. Det går också att får pengarna tillbaka om man ångrar sig innan man har köpt en biljett. Pengarna från biljettköpen sparas i automaten.
Din uppgift är att komplettera klassen ParkingMachine med egenskaper (eng. properties) och metoder.
Klassen UnitTestTicketMachine innehåller unit-tester. Koden i klassen ska inte ändras. Se bara till att testerna går igenom.
Det går inte att köra applikationen, utan det är testen som avgör om din kod är korrekt.
Tips: debuggern kan vara användbar. Sätt en breakpoint på lämpligt ställe och kör sedan testet. ...
OBS I Visual Studio ändra från ARM till x86, se bilderna nedan.
Förslag på arbetsordning
- Ladda ner kod
- Packa upp på lämplig plats
- Öppna i Visual Studio
- Kompilera (Build) båda projekten
- Lägg till enklast möjliga kod i ParkingMachine så att ParkingMachine kompilerar utan fel. Skriv förslagsvis koden själv. Det finns verktyg för att lägga till metodstubbar och egenskaper (eng. properties). En varning dock för autogenererade egenskaper. De kopplas inte till objektvariablerna automatiskt.
- Kör alla test. Klicka Test > Run All Tests eller Test > Test Explorer > Run All Tests
- Skriv kod så att ett test blir godkänt. Ta dem i den ordning som de är implementerade i ParkingMachineTest. Välj lämpliga namn på parametrarna så att koden blir självdokumenterande.
- Fortsätt med vart och ett av de andra testen. Se till att både det nya testet godkänns och alla som tidigare godkänts.
Tips
I metoden BuyTicket är modulus användbart, t.ex. 11 % 3
blir 2
Reflektera
Vad har du lärt dig om Unit Test?
Utmaning 2 - valbart biljettpris
Klassen ParkingMachine
Ändra på konstruktorn så att den tar biljettpriset per timme som en parameter.
Klassen UnitTestParkingMachine
Ändra så att de olika testen görs med olika biljettpris per timme.
Utmaning 3 - biljett giltig till
Uppvärmning
Klasserna (egentligen eng. Struct) DateTime och TimeSpan är användbara för att hantera datum och tidsrymder. Kolla på nedanstående kod. Lek lite med den. Försäkra dig om att du förstår hur DateTime och TimeSpan kan användas.
Ladda ner koden från GitHub. Kör konsollaplikationen.
På riktigt
Nästa steg är att lägga till så att man kan se på biljetten vilken dag och vilken tid som parkeringen går ut.
Ny testkod finns. OBS den nya testkoden ersätter den gamla. Kopiera testkoden från GitHub
Se till att din kod passerar alla tester.
Utmaning 4 - klassen Ticket
Hur ska vi använda oss av vår parkeringautomat? Några möjliga användningar följer:
- I en automat som skriver ut en biljett.
- I en automat som ger en elektronisk biljett som kan kontrolleras av en kontrollant. En parkeringsbot ges vid ej betalad parkering.
För alternativ 2 ovan blir det enklare om det finns en klass som representerar en biljett. Det ska vi lösa nu.
Nästan samma testfall som tidigare kan användas. Den enda förändringen i ParkingMachineTest är att hänsyn måste tas till att metoden BuyTicket i ParkingMachine får ändrad returdatatyp från string till Ticket.
Ersätt koden i ParkingMachineTest med ny kod från GitHub.
OBS! Ursäkta, det kan vara så att namespace har bytt namn.
Dags att koda klassen Ticket
Skriv klassen Ticket först. En del av koden kan flyttas från ParkingMachine. Mer instruktioner följer nedan.
Lite kod för klassen Ticket på GitHub samt kommentarer till metoderna. Kommentar för metoden ToString saknas dock, men den ska returnera biljetten som en text.
Properties
- CostPerHour : int (bara läsa inte skriva)
- Price : int (bara läsa inte skriva)
Metoder
Uppräknade i samma ordning som kommentarerna i koden.
- GetValidTimeSpan() : TimeSpan
- GetValidTo() : DateTime
- ToString() : string
Klassen ParkingMachine
Skriv om koden i klassen ParkingMachine, så att den använder sig av klassen Ticket. Tänk till och använd Ticket så mycket som möjligt.
Metoder som finns kvar
- InsertMoney, oförändrad?!
- BuyTicket, oförändrad?!
- GetParkingTimeSpan, använd Ticket
- GetValidTo, använd Ticket
- Cancel, oförändrad?!
Övriga publika metoder bör vara överflödiga. Koden kan antagligen flyttas till Ticket. I vissa fall behövs bara små förändringar.
Utmaning 5 - bank
Det är dags att utöka med betalning från bankkonto. Denna utmaning fortsätter där föregående slutade.
Det finns två alternativa tillvägagångssätt.
Alternativ 1 - koda först och hämta därefter test
Lägg till ett projekt Banking (.NET Core Library) i din lösning (solution). Lägg sedan till klasser och implementera dem utifrån beskrivningen nedan.
När du har kodat en klass hämtar du dess test från GitHub och ser till att testen klaras.
Alternativ 2 - hämta test från GitHub direkt
Ladda ner kod från GitHub. Koden kompilerar men det saknas kod i konstruktorer och metoder. Klasserna ParkingMachine och Ticket är fördigskrivna. Det är dessa som du gjort i övningarna ovan.
Ersätt koden i ParkingMachine och Ticket med din egen kod. Vill du behålla den färdiga koden går det också bra. Valet är ditt. OBS, se upp med namespace, de kan ha bytt namn.
Gemensam fortsättning
Klasserna skrivs lämpligtvis i följande ordning:
- Transfer
- BankAccount
- AccountNrGenerator
- Bank
- CardParkingMachine
Varje klass har ett test som heter som klassen men med tillägget Test på slutet av namnet. Skriv koden för en klass i taget och se till att testen klaras. Det kan vara lämpligt att ta en metod eller konstruktor i taget innan dess test körs. Börja med att klara av de testmetoder som kommer först i testklassen.
Mer detaljer om klasserna följer nedan.
Klassen Transfer
Klassen Transfer innehåller information för en transaktion. När ett objekt väl har skapats kan dess objektvariabler INTE ändras.
Klassen BankAccount
Ett bankkonto har kontonummer, pinkod, saldo och en lista med alla genomförda transaktioner.
Mycket framgår av den korta beskrivningen och klassdiagrammet ovan. Nedan följer förtydliganden för några metoder.
Metoden Transfer
Metoden tar ett objekt av klassen Transfer som parameter. Detta objekt innehåller information om överföringen.
Ett av fromAccountNr
och toAccountNr
stämmer överens med det egna accountNumber
. Detta avgör om det är ett uttag eller en insättning. Vid uttag måste täckning finnas på kontot. Om överföringen lyckas läggs överföringen till i listan successfullTransfers
.
Metoden returnerar true
om den lyckas och annars false
.
Klassen AccountNrGenerator
Klassen tar hand om att generera unika kontonummer.
Följande kontonummer genereras: 1001, 1002, 1003, 1004, ...
Första anropet av GerUniqueAccountNr
ger 1001. Andra ger 1002 o.s.v.
Klassen Bank
Banken innehåller konton. Kort om funktionaliteten:
- Det går att lägga till ett konto.
- Det går att göra överföringar mellan konton.
- Det går att få reda på saldot för ett konto.
- Det går att få ut en lista med alla transaktioner som gjorts på ett konto.
Vid samtliga fall krävs pinkod som validerar behörigheten.
Vidare förklaring av objektvariabeln accounts
samt metoderna följer nedan.
Objektvariabeln accounts
Objektvariabeln accounts refererar till ett objekt av klassen Dictionary. Dictionary är en lista med par av nycklar (eng. key) och värden (eng. value). För en ordlista blir det uppslagsord (key) och förklaring (value).
Metoden AddAccount med pin som parameter
Metoden AddAccount lägger till ett konto med angiven pinkod. Metoden returnerar kontonumret vid framgång. Om parametern pin
är null
eller en tom sträng "" så returneras null.
Metoden AddAccount med pin och balance som parameter
Denna version av metoden tar två parametrar. Förutom pin
tar den kontobehållningen. Kontots balance
sätts till detta värde. Om parametern balance
är negativ blir kontots saldo 0.
Denna metod returnerar enligt samma mönster som ovanstående metod.
Metoden GetBalance
Metoden GetBalance returnerar saldot för angivet konto om pinkoden stämmer. Om pinkoden är felaktig eller kontonumret saknas i banken så returneras 0.
Metoden Transfer
Metoden Transfer genomför den transaktion som bifogats om pinkoden stämmer för det konto som överföringen görs från.
Metoden returnerar true
om den var framgångsrik och annars false
. Om den misslyckas sparas transaktionen i listan failingTransfers.
Metoden GetTransfers
Metoen GetTransfers returnerar en lista med transaktioner för givet kontonummer förutsatt att kontot finns och pinkoden stämmer.
Klassen CardParkingMachine
Sätt in pengar i automaten. Ange kontonummer och pin före köp. När köpet genomförs överförs pengar från kundens konto i banken till biljettautomatens konto i banken.
Klassen CardParkingMachine ärver klassen ParkingMachine. Metoder ärvs från ParkingMachine. Det är bara metoden BuyTicket
som måste ersättas.
Förklaring av objektvariabler och metoder följer. Metoder och variabler i
Objektvariabeln bank
Ett objekt av klassen Bank.
Objektvariabeln accountNr
Parkeringsautomatens kontonummer på banken.
Objektvariabeln customerAccountNr
Kontonumret för nuvarande kund. OBS måste sättas till null
efter köp.
Objektvariablen customerPin
Pinkoden för nuvarande kund. OBS måste stättas till null
efter köp.
Metoden SetAccountNrAndPin
Metoden sätter kontonummer och pinkod för nuvarande kund. Denna information behövs vid köp av biljett för att pengar ska kunna överföras mellan konton.
Metoden IsSetAccountNrAndPin
Metoden kontrollerar att kontonummer och pinkod har satts för nuvarande kund.
Metoden BuyTicket
Metoden BuyTicket returnerar en biljett om köpet lyckades och annars null.
Metodhuvudet för BuyTicket blir:
public new Ticket BuyTicket()
Nyckelordet new
anges för att berätta att den nya metoden ersätter metoden i klassen ParkingMachine
.
Alternativ kunde metoden deklarerats med virtual
i superklassen ParkingMachine
och med override
i den ärvande klassen CardParkingMachine
.
Utmaning 6 - kontrollera biljettens giltighet
Ny klass TicketChecker.
Metoder (UML)
- IsValid(ticket: Ticket) : boolean
MER KOMMER
Extrauppgift 1 - konsollapplikation
Extrauppgift 1 och 2 kan göras oberoende av varandra. Välj själv vilken du vill börja med.
Skriv en konsollaplikation där man kan:
- Köpa en biljett. Biljetten skrivs ut i konsollfönstret.
- Ångra ett ej genomfört köp och få pengarna tillbaka. Återbetalt belopp visas i konsollfönstret.
Extrauppgift 2 - UWP-app
Universal Windows Platform Application.
Extrauppgift 3 - WPF-app
Windows Presentation Foundation Application
Extrauppgift 4 - Coins
Gör en parkeringsapparat där man kan betala med mynt.
Klassen CoinParkingMachine
Låt klassen CoinParkingMachine ärva ParkingMachine.
Metoden InsertCoin
Lägg till metoden InsertCoin. Den tar en Coin som parameter. Använd en Enum för Coin, se nedan.
//kod i filen Coin.cs
public enum Coin : int
{
One = 1,
Two = 2,
Five = 5,
Ten = 10
}
// koden för att sätt in ett mynt blir
myParkingMachine.InsertCoin(Coin.Five);
// omvandla ett mynt till int
Coin myCoin = Coin.Five;
int value;
// typomvandling med (int)
value = (int)myCoin;
Metoden Refund
Lägg till en metod som returnerar en lista med alla mynt i valfri ordning.
Klassen UnitTest CoinParkingMachine
Skriv tester för metoderna InsertCoin och Refund. Förslag på testfall följer:
- Sätt in 10. Köp biljett. Sätt in 1, 2, 5, 10, 2 kr. Kolla att Refund lämnar tillbaka dessa mynt, samt att automatens övre fack är tömt på mynt.
- Sätt in 10, 5, 5, 2, 1, 1, 10. Köp biljett. Sätt in 5, 2, 1, 1, 2, 2, 2. Köp biljett. Kontrollera att biljetten har rätt giltighetstid. Kontrollera även att mynten från båda köpen finns kvar i automatens nedre fack.
Mer fakta om Unit Test
Unit test best practices with .NET Core and .NET Standard, Microsoft