Lekcja 10. Zadanie kontrolne — klasa z logiką i pełny zestaw testów jednostkowych
Trudny / egzaminacyjnyPo co się tego uczymy?
To podsumowanie całego działu: napiszesz od podstaw klasę z logiką biznesową i KOMPLETNY zestaw testów jednostkowych — dokładnie w takim stylu, w jakim może pojawić się zadanie na egzaminie praktycznym INF.04. Zadanie sprawdza, czy potrafisz samodzielnie zaprojektować przypadki testowe (typowe, brzegowe, błędne), a nie tylko odtworzyć gotowy przykład z lekcji.
Teoria
Specyfikacja zadania. Zaprojektuj i zaimplementuj klasę RezerwacjaSali obsługującą rezerwacje sal konferencyjnych w firmie, wraz z KOMPLETNYM zestawem testów jednostkowych (MSTest lub xUnit — do wyboru). Wymagania funkcjonalne:
- Metoda
CzyDostepna(DateTime poczatek, DateTime koniec, List<Rezerwacja> istniejaceRezerwacje)zwracającabool— sala jest DOSTĘPNA, jeśli nowy termin NIE nakłada się (w żadnym punkcie) z żadną z istniejących rezerwacji. - Metoda
DodajRezerwacje(DateTime poczatek, DateTime koniec, string osoba)— dodaje nową rezerwację, ale RZUCA wyjątekInvalidOperationException, jeśli termin koliduje z istniejącą rezerwacją. - Walidacja:
koniecmusi być PÓŹNIEJSZY niżpoczatek(w przeciwnym razieArgumentException); rezerwacja nie może trwać dłużej niż 8 godzin. - Metoda
LiczbaRezerwacjiWDniu(DateTime dzien)zwracająca liczbę rezerwacji przypadających na wskazany dzień.
Wymagania dotyczące testów (to jest właściwe zadanie kontrolne — ocena dotyczy GŁÓWNIE jakości i kompletności testów, nie samej implementacji):
- Zastosuj zasadę AAA (Arrange-Act-Assert) w KAŻDYM teście.
- Pokryj testami przypadek TYPOWY (poprawna rezerwacja, brak kolizji).
- Pokryj testami przypadki BRZEGOWE: rezerwacja kończąca się DOKŁADNIE w momencie rozpoczęcia innej (czy to kolizja?), rezerwacja trwająca dokładnie 8 godzin (graniczna, dozwolona) i 8 godzin + 1 minuta (niedozwolona).
- Pokryj testami przypadki BŁĘDNE:
koniecwcześniejszy niżpoczatek, próba dodania rezerwacji kolidującej z istniejącą. - Nazwij testy zgodnie z konwencją
Metoda_Scenariusz_OczekiwanyWynik(lekcja 2). - Napisz krótką dokumentację (komentarz XML doc, lekcja 8) dla metody
CzyDostepna.
Schemat
RezerwacjaSali ├── CzyDostepna(poczatek, koniec, istniejące) : bool ├── DodajRezerwacje(poczatek, koniec, osoba) : rzuca InvalidOperationException przy kolizji └── LiczbaRezerwacjiWDniu(dzien) : int PRZYPADKI DO PRZETESTOWANIA: ✓ typowy: 10:00-11:00 vs istniejąca 14:00-15:00 → dostępna ⚠ brzegowy: 10:00-11:00 vs istniejąca 11:00-12:00 → dostępna (styk, nie nakładanie) ⚠ brzegowy: rezerwacja dokładnie 8h → dozwolona ✗ błędny: koniec przed początkiem → ArgumentException ✗ błędny: 9:00-18:01 (8h 1min) → ArgumentException ✗ błędny: DodajRezerwacje z kolizją → InvalidOperationException
Przykład z życia
Systemy rezerwacji sal konferencyjnych (Outlook, Google Calendar Rooms) codziennie rozwiązują dokładnie ten problem: sprawdzają, czy proponowany termin NIE nakłada się z żadną istniejącą rezerwacją, uwzględniając subtelne przypadki brzegowe — czy spotkanie kończące się dokładnie o 11:00 koliduje z innym zaczynającym się o 11:00 (zwykle NIE — to "styk", nie nakładanie). Właśnie takie subtelności są source większości błędów w prawdziwych systemach rezerwacji i dlatego wymagają starannie zaprojektowanych testów.
RezerwacjaSali.cs (szkielet do samodzielnego uzupełnienia)
// Szkielet do uzupełnienia - to punkt wyjścia, nie gotowe rozwiązanie
using System;
using System.Collections.Generic;
using System.Linq;
public class Rezerwacja
{
public DateTime Poczatek { get; set; }
public DateTime Koniec { get; set; }
public string Osoba { get; set; }
}
public class RezerwacjaSali
{
private List<Rezerwacja> _rezerwacje = new List<Rezerwacja>();
public bool CzyDostepna(DateTime poczatek, DateTime koniec, List<Rezerwacja> istniejaceRezerwacje)
{
// TODO: zaimplementuj sprawdzenie nakładania się przedziałów czasowych.
// Wskazówka: dwa przedziały [A_start, A_end) i [B_start, B_end) NIE nakładają się,
// gdy A_end <= B_start LUB B_end <= A_start.
throw new NotImplementedException();
}
public void DodajRezerwacje(DateTime poczatek, DateTime koniec, string osoba)
{
// TODO: zwaliduj dane wejściowe (koniec > poczatek, maks. 8h),
// sprawdź kolizję przez CzyDostepna, dodaj do _rezerwacje albo rzuć wyjątek.
throw new NotImplementedException();
}
public int LiczbaRezerwacjiWDniu(DateTime dzien)
{
// TODO: policz, ile rezerwacji z _rezerwacje przypada na wskazany dzień.
throw new NotImplementedException();
}
}
Komentarz i wyjaśnienie kodu
Podpowiedź w komentarzu (A_end <= B_start LUB B_end <= A_start) to klasyczny wzór na sprawdzenie NIE-nakładania się dwóch przedziałów czasowych — łatwiej sprawdzić warunek BRAKU kolizji i go zanegować, niż próbować wypisać wszystkie warianty nakładania się wprost. To dobra ogólna technika przy projektowaniu logiki z wieloma warunkami: czasem prościej sformułować i zanegować przeciwieństwo.
Rzucanie NotImplementedException() w szkielecie to celowy sygnał "tu musisz dopisać kod" — to standardowa praktyka przy zadaniach kontrolnych i szkieletach projektów, żeby jasno oznaczyć, co jest do zrobienia.
Ćwiczenie samodzielne
Zaimplementuj metodę CzyDostepna zgodnie z podpowiedzią, a następnie napisz do niej co najmniej 3 testy: typowy przypadek bez kolizji, typowy przypadek Z kolizją, przypadek brzegowy "styku" (koniec jednej rezerwacji = początek drugiej).
Zadania do pracy własnej
(4 pkt) Zaimplementuj
LiczbaRezerwacjiWDniui napisz dla niej 3 testy: dzień bez żadnej rezerwacji (0), dzień z jedną rezerwacją (1), dzień z kilkoma rezerwacjami (poprawna suma). Nazwij testy zgodnie z konwencją z lekcji 2.(4 pkt) Zaimplementuj walidację w
DodajRezerwacje(koniec > poczatek, maksymalnie 8 godzin) i napisz testy sprawdzające oba warunki, w tym PRZYPADEK BRZEGOWY dokładnie 8-godzinnej rezerwacji (powinna być dozwolona) oraz 8 godzin i 1 minutę (powinna zostać odrzucona).(4 pkt) Dokończ pełną implementację
CzyDostepnaiDodajRezerwacje(z rzucaniemInvalidOperationExceptionprzy kolizji), a następnie napisz KOMPLETNY zestaw co najmniej 8 testów pokrywających: typowy brak kolizji, typową kolizję, "styk" na początku i na końcu (2 osobne testy), próbę dodania kolidującej rezerwacji (sprawdź wyjątek), oraz dodaj komentarz XML doc doCzyDostepna. Łącznie zadanie warte 12 punktów — analogicznie do zadań kontrolnych w innych działach tego kursu.
Typowe błędy
Sprawdzanie kolizji przez porównanie WYŁĄCZNIE godzin rozpoczęcia (pomijając czas trwania) — dwie rezerwacje mogą zaczynać się o różnych godzinach, a mimo to się nakładać (np. 9:00-12:00 i 10:00-11:00) — trzeba porównywać CAŁE przedziały czasowe, nie same momenty startu.
Traktowanie "styku" (koniec jednej = początek drugiej) jako kolizji — to częsty błąd projektowy; w większości systemów rezerwacji spotkanie kończące się o 11:00 NIE koliduje z innym zaczynającym się dokładnie o 11:00. Warto to jawnie przetestować, żeby uniknąć niejednoznaczności.
Pisanie testów DOPIERO po napisaniu całej implementacji, "na wyczucie co powinno działać" — lepszą praktyką (choć niewymaganą w tym zadaniu) jest myślenie o przypadkach testowych RÓWNOLEGLE z projektowaniem logiki, co pomaga wychwycić niejasności w specyfikacji (jak właśnie kwestia "styku") ZANIM napiszesz błędny kod.
Nawiązanie do egzaminu zawodowego
To zadanie kontrolne podsumowuje CAŁY dział INF.04.8: teorię testowania (lekcja 1), praktyczne MSTest (lekcja 2), zastosowanie w różnych typach aplikacji (lekcje 3-5), testy niefunkcjonalne (lekcja 6), organizację procesu (lekcja 7), dokumentację (lekcja 8) i automatyzację (lekcja 9). Umiejętność zaprojektowania KOMPLETNEGO zestawu testów (typowych, brzegowych, błędnych) dla danej specyfikacji to dokładnie to, czego oczekuje się na egzaminie praktycznym INF.04, gdzie temat testów jednostkowych pojawiał się już w poprzednich sesjach egzaminacyjnych.