Lekcja 6. Testy niefunkcjonalne — użyteczność, wydajność, obciążenie, bezpieczeństwo
ŚredniPo co się tego uczymy?
Aplikacja może przechodzić WSZYSTKIE testy jednostkowe i funkcjonalne, a mimo to być fatalna w użyciu — bo działa wolno, pada pod większym obciążeniem, jest niewygodna dla użytkownika albo ma dziury bezpieczeństwa. Testy niefunkcjonalne sprawdzają WŁAŚNIE te cechy — wymagane wprost w INF.04.8.3 i równie ważne jak poprawność samej logiki.
Teoria
Czym różnią się testy niefunkcjonalne od funkcjonalnych? Test funkcjonalny odpowiada na pytanie "CZY dana funkcja działa" (np. czy da się złożyć zamówienie). Test niefunkcjonalny odpowiada na pytanie "JAK DOBRZE" ta funkcja działa pod różnymi względami — nawet jeśli wynik jest poprawny, może być np. zbyt wolny albo niewygodny.
Testy użyteczności (usability) — sprawdzają, czy interfejs jest ZROZUMIAŁY i WYGODNY dla użytkownika: czy przyciski są czytelnie opisane, czy komunikaty o błędach są pomocne, czy da się wykonać typowe zadanie bez instrukcji obsługi. Zwykle wykonywane przez OBSERWACJĘ prawdziwych użytkowników próbujących wykonać zadanie, nie automatycznie.
Testy wydajności (performance) — mierzą CZAS odpowiedzi systemu w typowych warunkach: jak szybko ładuje się strona, jak szybko backend odpowiada na zapytanie API, ile trwa wygenerowanie raportu. Wynik wyrażany jest konkretną liczbą (np. "odpowiedź API poniżej 200 ms"), którą można obiektywnie zmierzyć i porównać z wymaganiem.
Testy obciążeniowe (load testing) — sprawdzają zachowanie systemu pod DUŻYM obciążeniem: co się stanie, gdy 1000 użytkowników jednocześnie złoży zamówienie? Czy serwer nadal odpowiada, czy zaczyna zwracać błędy, czy czas odpowiedzi drastycznie rośnie? Do symulacji dużego ruchu używa się narzędzi generujących sztuczne obciążenie (np. Apache JMeter, k6) — jeden komputer testowy symuluje setki/tysiące "wirtualnych użytkowników" naraz.
Testy zgodności (compatibility) — sprawdzają, czy aplikacja działa poprawnie w RÓŻNYCH środowiskach: różne przeglądarki (Chrome, Firefox, Edge), różne rozdzielczości ekranu, różne wersje systemu operacyjnego, różne urządzenia (desktop/tablet/telefon). Istotne zwłaszcza dla aplikacji webowych (dział Aplikacje webowe) i mobilnych (dział MAUI, lekcja o dostosowaniu do platformy).
Testy bezpieczeństwa (security) — sprawdzają odporność aplikacji na typowe ataki: SQL injection (poznane w dziale PHP/Bazy danych), nieautoryzowany dostęp do danych innych użytkowników, przesyłanie haseł jawnym tekstem, podatność na przechwycenie sesji. Podstawowa zasada: NIGDY nie ufaj danym przychodzącym od użytkownika bez walidacji, zarówno po stronie klienta, jak i (koniecznie) po stronie serwera.
Dlaczego "niefunkcjonalne" nie znaczy "mniej ważne" — sklep internetowy, który poprawnie oblicza sumę koszyka (funkcjonalność OK), ale ładuje się 15 sekund albo pada przy Black Friday, straci klientów mimo technicznie bezbłędnej logiki. W praktyce zawodowej testy niefunkcjonalne bywają decydujące dla sukcesu projektu.
Schemat
TESTY NIEFUNKCJONALNE — pięć głównych kategorii ┌─────────────┬──────────────────┬───────────────────────────┐ │ Kategoria │ Pytanie │ Przykładowe narzędzie │ ├─────────────┼──────────────────┼───────────────────────────┤ │ Użyteczność │ Czy jest wygodne? │ obserwacja użytkowników │ │ Wydajność │ Jak szybko? │ pomiar czasu odpowiedzi │ │ Obciążenie │ Co przy 1000 os.? │ JMeter, k6 (symulacja ruchu)│ │ Zgodność │ Wszędzie działa? │ testy na różnych przeglądarkach│ │ Bezpieczeństwo│ Czy odporne na atak?│ próby SQL injection, audyt│ └─────────────┴──────────────────┴───────────────────────────┘
Przykład z życia
Sklepy internetowe w Czarny Piątek (Black Friday) regularnie "padają" pod nawałem klientów — to klasyczny przykład zaniedbanego testu OBCIĄŻENIOWEGO: aplikacja działała bez zarzutu przy normalnym ruchu, ale nie sprawdzono, co się stanie przy dziesięciokrotnie większej liczbie jednoczesnych użytkowników. Podobnie strony rządowe udostępniające ważny formularz (np. zapisy na szczepienia) często padały w pierwszych minutach po starcie z powodu braku testów obciążeniowych na realistyczną skalę.
TestWydajnosci.cs — pomiar czasu wykonania (Stopwatch)
// Prosty przykład testu wydajności w C# - pomiar czasu wykonania
// (nie jest to "prawdziwe" narzędzie do testów obciążeniowych,
// ale pokazuje ideę mierzenia wydajności w kodzie)
using System;
using System.Diagnostics;
class TestWydajnosci
{
static void Main()
{
var sito = new Sito(); // klasa z lekcji 2
var stoper = Stopwatch.StartNew();
var wynik = sito.WyznaczPierwsze(1_000_000);
stoper.Stop();
Console.WriteLine($"Znaleziono {wynik.Count} liczb pierwszych");
Console.WriteLine($"Czas wykonania: {stoper.ElapsedMilliseconds} ms");
// prosty "test" wydajności - sprawdzenie, czy mieścimy się w budżecie czasowym
if (stoper.ElapsedMilliseconds > 500)
{
Console.WriteLine("UWAGA: przekroczono oczekiwany czas wykonania (500 ms)!");
}
}
}
Komentarz i wyjaśnienie kodu
Stopwatch to klasa z .NET do precyzyjnego mierzenia upływającego czasu — StartNew() uruchamia pomiar, Stop() go zatrzymuje, a ElapsedMilliseconds zwraca liczbę milisekund, jakie upłynęły. To najprostszy sposób na wprowadzenie ELEMENTU testu wydajnościowego do własnego kodu — profesjonalne narzędzia (JMeter, k6, BenchmarkDotNet) robią to samo, ale na dużo większą skalę i z bardziej precyzyjną metodologią (wielokrotne powtórzenia, statystyka, symulacja wielu użytkowników naraz).
Warunek if (stoper.ElapsedMilliseconds > 500) pokazuje ideę "budżetu czasowego" — ustalasz z góry akceptowalny czas wykonania (tu: 500 ms) i program sam sygnalizuje, gdy go przekroczy. W profesjonalnych zespołach takie progi bywają wpięte w automatyczne testy wydajnościowe uruchamiane regularnie.
Ćwiczenie samodzielne
Zmierz czas wykonania metody WyznaczPierwsze z lekcji 2 dla kilku różnych wartości n (1000, 100 000, 1 000 000) przy użyciu Stopwatch. Zaobserwuj, jak rośnie czas wykonania wraz ze wzrostem n, i porównaj to z pojęciem złożoności obliczeniowej poznanym w dziale Programowanie konsolowe.
Zadania do pracy własnej
Wypisz 5 konkretnych pytań testowych dla każdej z pięciu kategorii testów niefunkcjonalnych (użyteczność, wydajność, obciążenie, zgodność, bezpieczeństwo) w kontekście aplikacji "Serwis ogłoszeniowy" z działu Aplikacje webowe.
Zaprojektuj prosty scenariusz testu bezpieczeństwa dla formularza logowania: wypisz co najmniej 4 rzeczy, które powinieneś sprawdzić (np. czy błędne hasło NIE ujawnia, że sam login istnieje w systemie; czy da się zalogować przez wpisanie fragmentu SQL w polu hasła).
Napisz w C# prosty program mierzący czas wykonania TRZECH różnych algorytmów sortowania (bąbelkowe, przez wstawianie, szybkie — z działu Programowanie konsolowe) dla tej samej, dużej losowej tablicy liczb, i wypisz wyniki w formie porównawczej tabeli. To praktyczny przykład testu wydajnościowego wykorzystującego wiedzę o złożoności obliczeniowej.
Typowe błędy
Testowanie wydajności na małych, nierealistycznych danych (np. 10 rekordów zamiast realistycznych 100 000) — różnice w wydajności między dobrym a złym algorytmem często ujawniają się DOPIERO przy dużej skali danych.
Ignorowanie testów niefunkcjonalnych, bo "program przecież działa" — funkcjonalna poprawność i dobra jakość niefunkcjonalna to DWIE OSOBNE sprawy; aplikacja może być w 100% poprawna logicznie, a jednocześnie fatalna w użyciu, wolna albo niebezpieczna.
Testowanie bezpieczeństwa WYŁĄCZNIE po stronie klienta (JavaScript) — walidacja w przeglądarce jest wygodna dla użytkownika, ale NIE chroni przed atakującym, który wysyła żądania bezpośrednio do API, omijając interfejs. Bezpieczeństwo zawsze musi być wymuszane też (a przede wszystkim) po stronie serwera.
Nawiązanie do egzaminu zawodowego
To bezpośrednie pokrycie INF.04.8.3 ("testy funkcjonalne/niefunkcjonalne: użyteczność, wydajność, obciążenie, zgodność, bezpieczeństwo"). W kolejnej lekcji zobaczysz, jak w praktyce ORGANIZUJE się cały proces testowania — środowiska testowe, scenariusze i raportowanie znalezionych błędów.