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:

  1. Arrange: Skapar en parkeringsautomat.

  2. Act: Sätter in pengar i automaten, först 30 kr sedan 60 kr.

  3. 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. ...

Ladda ner koden från GitHub.

OBS I Visual Studio ändra från ARM till x86, se bilderna nedan.

Förslag på arbetsordning

  1. Ladda ner kod
  2. Packa upp på lämplig plats
  3. Öppna i Visual Studio
  4. Kompilera (Build) båda projekten
  5. 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.
  6. Kör alla test. Klicka Test > Run All Tests eller Test > Test Explorer > Run All Tests
  7. 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.
  8. 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

eller hela projektet.

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:

  1. I en automat som skriver ut en biljett.
  2. 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.

Kod för hela projektet.

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:

  1. Transfer
  2. BankAccount
  3. AccountNrGenerator
  4. Bank
  5. 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.

Transfer

  • -fromAccountNr : string
  • -toAccountNr : string
  • -amount: int
  1. +Transfer(fromAccountNr : string, toAccountNr : string, amount : int)
  2. +FromAccontNr : string (readonly)
  3. +ToAccountNr : string (readonly)
  4. +Amount : int (readonly)
Klassdiagram för Transfer.

Klassen BankAccount

Ett bankkonto har kontonummer, pinkod, saldo och en lista med alla genomförda transaktioner.

BankAccount

  • -accountNumber : string
  • -pin : string
  • -balance : int
  • -successfullTransfers : List<Transfer>
  1. +BankAccount(accountNumber : string, pin : string)
  2. +BankAccount(accountNumber : string, pin : string, balance : int)
  3. +AccountNumber : string
  4. +Balance : int
  5. +Transfer(transfer: Transfer) : bool
  6. +ValidatePin(pin : string) : bool
  7. +GetTransfers() : List<Transfer>
Klassdiagram för BankAccount.

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.

AccountNrGenerator

  • -currentAccountNr : int
  1. +AccountNrGenerator()
  2. +GetUniqueAccountNr() : string
Klassdiagram för AccountNrGenerator.

Klassen Bank

Banken innehåller konton. Kort om funktionaliteten:

  1. Det går att lägga till ett konto.
  2. Det går att göra överföringar mellan konton.
  3. Det går att få reda på saldot för ett konto.
  4. 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.

Bank

  • -accounts : Dictionary<String, BankAccount>
  • -accountNrGenerator : AccountNrGenerator
  • -failingTransfers : List<Transfer>
  1. +Bank()
  2. +AddAccount(pin : string) : string
  3. +AddAccount(pin : string, balance : int) : string
  4. +GetBalance(accountNr : string, pin : string) : int
  5. +Transfer(transfer : Transfer, pin : string) : bool
  6. +GetTransfers(accountNr : string, pin : string) : List<Transfer>
Klassdiagram för Bank.

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.

CardParkingMachine

  • -bank : Bank
  • -accountNr : string
  • -customerAccountNr : string
  • -customerPin : string
  1. +CardParkingMachine(costPerHour : int, bank : Bank, accountNr : string)
  2. +SetAccountNrAndPin(accountNr : string, pin : string) : void
  3. +IsSetAccountNrAndPin() : bool
  4. +BuyTicket() : Ticket
Klassdiagram för CardParkingMachine.

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 - 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 basics, Microsoft

Unit test best practices with .NET Core and .NET Standard, Microsoft

C# unit test tutorial, Rhyous