Lekcja 2. MSTest — pierwszy projekt testowy w Visual Studio
ŚredniPo co się tego uczymy?
Teoria testowania (lekcja 1) staje się przydatna dopiero, gdy umiesz ją zastosować w praktyce. MSTest to wbudowane w Visual Studio narzędzie do pisania i uruchamiania testów jednostkowych dla C# — w tej lekcji napiszesz od zera aplikację konsolową (Sito Eratostenesa) razem z KOMPLETNYM zestawem testów jednostkowych, dokładnie w takim stylu, jaki może pojawić się na egzaminie praktycznym.
Teoria
Krok po kroku w Visual Studio — od zera do działających testów:
- Utwórz aplikację konsolową (jak w dziale Programowanie konsolowe, lekcja 1): Utwórz nowy projekt → wyszukaj
Console App→ nazwij ją np.SitoApp→ Utwórz. - Dodaj projekt testowy do TEGO SAMEGO rozwiązania: w Eksploratorze rozwiązań kliknij prawym przyciskiem na nazwę ROZWIĄZANIA (Solution, na samej górze drzewa) → Dodaj → Nowy projekt.
- Wyszukaj
MSTesti wybierz szablon "MSTest Test Project" → Dalej → nazwij goSitoApp.Tests(konwencja: nazwa testowanego projektu +.Tests) → Utwórz. - Dodaj odwołanie projektu testowego do projektu właściwego: kliknij prawym przyciskiem na
SitoApp.Tests→ Dodaj → Odwołanie do projektu… → zaznacz checkbox przySitoApp→ OK. Bez tego kroku klasy testowe nie "zobaczą" klas z aplikacji. - Wklej kod klas
Sito.cs/Program.csdo projektuSitoApp, a kod testów doSitoApp.Tests(zastępując domyślnie wygenerowany plikTest1.cs). - Uruchom testy: menu Test → Uruchom wszystkie testy (albo skrót
Ctrl+R, A) — otworzy się okno Eksplorator testów, gdzie każdy test pokazuje się z zielonym ✔ (przeszedł) albo czerwonym ✘ (nie przeszedł, z opisem błędu).
Zasada AAA (Arrange-Act-Assert) — każdy dobrze napisany test jednostkowy składa się z trzech kroków: Arrange (przygotuj dane wejściowe i obiekt do przetestowania), Act (wywołaj testowaną metodę), Assert (sprawdź, czy wynik jest zgodny z oczekiwaniem). Trzymanie się tej struktury sprawia, że testy są czytelne nawet miesiące po ich napisaniu.
Struktura projektu testowego w Visual Studio — testy trzyma się w OSOBNYM projekcie (np. SitoApp.Tests), który ma DODANE ODWOŁANIE do projektu z kodem właściwym (SitoApp). Dzięki temu klasy testowe "widzą" klasy produkcyjne i mogą je wywoływać.
Najważniejsze atrybuty MSTest:
[TestClass]— oznacza klasę zawierającą testy (rozpoznawana przez Eksplorator testów w VS).[TestMethod]— oznacza POJEDYNCZY test (metodavoid, bez parametrów).
Najważniejsze asercje (Assert) — "serce" każdego testu:
Assert.AreEqual(oczekiwany, wynik)— sprawdza, czy wartości są RÓWNE.Assert.AreNotEqual(a, b)— sprawdza, czy wartości są RÓŻNE.Assert.IsTrue(warunek)/Assert.IsFalse(warunek)— sprawdza wartość logiczną.Assert.IsNull(wartość)/Assert.IsNotNull(wartość)— sprawdza, czy coś jest (albo nie jest) puste.Assert.ThrowsException<WyjątekTyp>(() => metoda())— sprawdza, czy wywołanie metody RZUCA oczekiwany wyjątek.CollectionAssert.AreEqual(oczekiwanaLista, wynikowaLista)— porównuje CAŁE kolekcje element po elemencie.
Co warto testować: metody zwracające wartość (czy wynik jest poprawny), metody obliczeniowe, walidację danych (czy metoda poprawnie wykrywa błędne dane), funkcje rzucające wyjątki (czy rzucają go we właściwym momencie), logikę true/false. Czego NIE testujemy jednostkowo: elementów interfejsu użytkownika (przyciski, okna, kolory) — to nie jest logika, tylko wygląd (o testowaniu aplikacji z UI — patrz lekcje 3-4).
Przypadki brzegowe — dobry zestaw testów sprawdza nie tylko "typowy" przypadek, ale też granice: liczbę 0, liczby ujemne, pustą listę, największą możliwą wartość. To właśnie przypadki brzegowe najczęściej ujawniają błędy, które umykają przy zwykłym, pobieżnym sprawdzeniu.
Schemat
Rozwiązanie (Solution)
├── SitoApp/ (projekt właściwy)
│ ├── Program.cs
│ └── Sito.cs
└── SitoApp.Tests/ (projekt testowy, z odwołaniem do SitoApp)
└── SitoTests.cs
Jeden test = AAA:
Arrange → przygotuj obiekt Sito i dane wejściowe
Act → wywołaj sito.WyznaczPierwsze(10)
Assert → CollectionAssert.AreEqual({2,3,5,7}, wynik)
Przykład z życia
Sito Eratostenesa to klasyczny, ponad 2000-letni algorytm znajdowania wszystkich liczb pierwszych do zadanej granicy — zamiast sprawdzać podzielność każdej liczby osobno, "przekreśla" wielokrotności kolejnych liczb pierwszych, co jest znacznie szybsze. To dobry przykład na naukę testowania, bo ma jasno określony, łatwy do zweryfikowania wynik (dla 10 zawsze powinno wyjść {2, 3, 5, 7}) i kilka naturalnych przypadków brzegowych (0, 1, liczby ujemne).
Sito.cs + Program.cs (projekt SitoApp)
// Sito.cs — logika algorytmu Eratostenesa
using System.Collections.Generic;
public class Sito
{
public List<int> WyznaczPierwsze(int n)
{
var pierwsze = new List<int>();
if (n < 2) return pierwsze;
bool[] sito = new bool[n + 1]; // false = potencjalnie pierwsza
for (int i = 2; i <= n; i++)
{
if (!sito[i])
{
pierwsze.Add(i);
for (int j = i * 2; j <= n; j += i)
{
sito[j] = true; // oznacz jako niepierwsza
}
}
}
return pierwsze;
}
}
// Program.cs — uruchomienie aplikacji
using System;
class Program
{
static void Main()
{
Console.Write("Podaj górny zakres (np. 100): ");
if (int.TryParse(Console.ReadLine(), out int zakres))
{
var sito = new Sito();
var liczbyPierwsze = sito.WyznaczPierwsze(zakres);
Console.WriteLine("Liczby pierwsze:");
Console.WriteLine(string.Join(", ", liczbyPierwsze));
}
else
{
Console.WriteLine("Niepoprawna liczba.");
}
Console.ReadKey();
}
}
SitoTests.cs (projekt SitoApp.Tests)
// SitoTests.cs — projekt testowy SitoApp.Tests
// (dodaj odwołanie: prawy klik na SitoApp.Tests -> Dodaj -> Odwołanie projektu -> SitoApp)
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace SitoApp.Tests
{
[TestClass]
public class SitoTests
{
[TestMethod]
public void WyznaczPierwsze_Do10_ZwracaPoprawne()
{
// Arrange
var sito = new Sito();
var oczekiwane = new List<int> { 2, 3, 5, 7 };
// Act
var wynik = sito.WyznaczPierwsze(10);
// Assert
CollectionAssert.AreEqual(oczekiwane, wynik);
}
[TestMethod]
public void WyznaczPierwsze_DlaZera_ZwracaPustaListe()
{
var sito = new Sito();
var wynik = sito.WyznaczPierwsze();
Assert.AreEqual(, wynik.Count);
}
[TestMethod]
public void WyznaczPierwsze_DlaLiczbyMniejszejNiz2_ZwracaPustaListe()
{
var sito = new Sito();
var wynik = sito.WyznaczPierwsze(1);
Assert.AreEqual(, wynik.Count);
}
[TestMethod]
public void WyznaczPierwsze_DlaLiczbyUjemnej_ZwracaPustaListe()
{
var sito = new Sito();
var wynik = sito.WyznaczPierwsze(-5);
Assert.AreEqual(, wynik.Count);
}
}
}
Komentarz i wyjaśnienie kodu
WyznaczPierwsze_Do10_ZwracaPoprawne to test "typowego przypadku" — sprawdza, że dla znanej, łatwej do ręcznego zweryfikowania wartości (10) metoda zwraca dokładnie oczekiwaną listę liczb pierwszych. CollectionAssert.AreEqual porównuje CAŁĄ listę element po elemencie i w tej samej kolejności — sama liczba elementów by nie wystarczyła.
Pozostałe trzy testy to PRZYPADKI BRZEGOWE: 0, 1 i liczba ujemna — żaden z nich nie ma "typowej" liczby pierwszej do znalezienia, więc metoda powinna zwrócić PUSTĄ listę, a nie rzucić wyjątek czy zwrócić błędny wynik. Dobra nazwa testu (np. WyznaczPierwsze_DlaZera_ZwracaPustaListe) od razu mówi CO i w JAKIEJ sytuacji jest sprawdzane — to konwencja "Metoda_Scenariusz_OczekiwanyWynik", warta stosowania konsekwentnie.
Ćwiczenie samodzielne
Utwórz w Visual Studio dwa projekty jak w przykładzie: aplikację konsolową SitoApp i projekt testowy MSTest SitoApp.Tests z odwołaniem do pierwszego. Wklej kod, uruchom wszystkie testy (menu Test → Uruchom wszystkie testy) i sprawdź, że wszystkie świecą się na zielono. Następnie celowo popsuj Sito.cs (np. zmień j += i na j += i + 1) i zobacz, który test to wykryje.
Zadania do pracy własnej
Dodaj do
SitoTestsnowy test sprawdzający, że dlan = 2metoda zwraca listę zawierającą DOKŁADNIE jeden element: liczbę 2.Dopisz do klasy
SitometodęCzyPierwsza(int liczba)zwracającąbool, oraz komplet testów dla niej: liczba pierwsza (np. 7 → true), liczba złożona (np. 8 → false), 0 i 1 (obie → false, bo z definicji nie są pierwsze), liczba ujemna.Napisz osobną klasę
Kalkulatorz metodamiDodaj,Odejmij,PomnoziPodziel(ta ostatnia niech rzucaDivideByZeroExceptionprzy dzieleniu przez 0), a następnie komplet testów MSTest dla wszystkich czterech metod, z użyciemAssert.ThrowsException<DivideByZeroException>dla przypadku dzielenia przez zero.
Typowe błędy
Brak odwołania (Reference) projektu testowego do projektu produkcyjnego — bez tego kroku klasy testowe nie "widzą" klas z SitoApp i kod się nie skompiluje. To pierwsza rzecz do sprawdzenia, gdy Visual Studio zgłasza "nie znaleziono typu lub przestrzeni nazw".
Testowanie tylko "szczęśliwej ścieżki" (happy path), pomijanie przypadków brzegowych — test sprawdzający wyłącznie typowe dane (jak WyznaczPierwsze(10)) może przejść, mimo że metoda źle radzi sobie z 0, liczbami ujemnymi czy bardzo dużymi wartościami. Dobre testy CELOWO szukają granic działania kodu.
Zbyt ogólne nazwy testów (np. Test1, TestSito) — utrudniają zrozumienie, CO faktycznie jest sprawdzane, gdy test zawiedzie miesiące później. Konwencja Metoda_Scenariusz_OczekiwanyWynik (jak w przykładzie) rozwiązuje ten problem.
Nawiązanie do egzaminu zawodowego
To fundament praktyczny całego działu — dokładnie ten sam wzorzec (projekt testowy MSTest, atrybuty [TestClass]/[TestMethod], asercje) będzie wykorzystany w kolejnych lekcjach do testowania logiki aplikacji WPF (lekcja 3) i .NET MAUI (lekcja 4) — okaże się, że ten SAM zestaw testów działa identycznie niezależnie od interfejsu użytkownika, o ile logika jest od niego oddzielona.