Dataczwartek, 13 sierpnia 2026 Czas17:58:40
← Testowanie i dokumentowanie aplikacji

Lekcja 2. MSTest — pierwszy projekt testowy w Visual Studio

Średni

Po 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:

  1. Utwórz aplikację konsolową (jak w dziale Programowanie konsolowe, lekcja 1): Utwórz nowy projekt → wyszukaj Console App → nazwij ją np. SitoApp → Utwórz.
  2. 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.
  3. Wyszukaj MSTest i wybierz szablon "MSTest Test Project" → Dalej → nazwij go SitoApp.Tests (konwencja: nazwa testowanego projektu + .Tests) → Utwórz.
  4. Dodaj odwołanie projektu testowego do projektu właściwego: kliknij prawym przyciskiem na SitoApp.TestsDodaj → Odwołanie do projektu… → zaznacz checkbox przy SitoApp → OK. Bez tego kroku klasy testowe nie "zobaczą" klas z aplikacji.
  5. Wklej kod klas Sito.cs/Program.cs do projektu SitoApp, a kod testów do SitoApp.Tests (zastępując domyślnie wygenerowany plik Test1.cs).
  6. 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 (metoda void, 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

  1. Dodaj do SitoTests nowy test sprawdzający, że dla n = 2 metoda zwraca listę zawierającą DOKŁADNIE jeden element: liczbę 2.

  2. Dopisz do klasy Sito metodę 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.

  3. Napisz osobną klasę Kalkulator z metodami Dodaj, Odejmij, Pomnoz i Podziel (ta ostatnia niech rzuca DivideByZeroException przy dzieleniu przez 0), a następnie komplet testów MSTest dla wszystkich czterech metod, z użyciem Assert.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.