Testy jednostkowe w rozwoju mobilnym: co to jest, metody i frameworki

Autor: IT Sectr Opublikowano: 2026-04-06 Czas czytania: 9 min

Testy jednostkowe to metoda weryfikacji oprogramowania, w której sprawdza się poprawność działania poszczególnych modułów lub funkcji kodu w izolacji od reszty systemu. Według Martina Fowlera, 2026, testy jednostkowe są fundamentem CI/CD i refaktoryzacji, zapewniając szybkie informacje zwrotne o działaniu kodu. Testy modułowe pomagają wykrywać błędy na wczesnych etapach rozwoju, zmniejszając koszt ich naprawy dziesięciokrotnie.

Najważniejsze

  • Test jednostkowy — sprawdzenie jednego modułu (funkcji, metody, klasy) w izolacji od zewnętrznych zależności
  • Zasady FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — podstawa wysokiej jakości testów
  • Maki i stuby — zamienniki zewnętrznych zależności (bazy danych, API, system plików), zapewniające izolację testu
  • TDD (Test-Driven Development) — metodyka rozwoju przez testowanie: czerwony-zielony-refaktoryzacja
  • Piramida testów — testy jednostkowe zajmują 70% piramidy, zapewniając szybkie informacje zwrotne przy każdym commicie

Czym są testy jednostkowe?

Testy jednostkowe to proces sprawdzania poszczególnych jednostek (unit) kodu źródłowego — funkcji, metod, klas — w izolacji od reszty programu. Każdy test uruchamia konkretny scenariusz użycia modułu i sprawdza, czy wynik odpowiada oczekiwanemu. Testy jednostkowe pisze się w tym samym języku programowania co kod główny i wykonuje automatycznie w środowisku programistycznym lub w pipeline’ie CI/CD. W przeciwieństwie do testów integracyjnych, testy jednostkowe nie współdziałają z rzeczywistymi bazami danych, systemem plików ani usługami sieciowymi.

Po co są testy jednostkowe?

Głównym celem jest szybkie informacje zwrotne o poprawności kodu po zmianach. Jeśli programista refaktoryzuje metodę, zestaw testów jednostkowych potwierdza, że działanie nie zostało zepsute. Według Google Testing Blog (2025), projekty z pokryciem testami jednostkowymi powyżej 60% mają 2,5 razy mniej incydentów produkcyjnych. Dodatkowe zalety: dokumentacja kodu (testy pokazują, jak używać API), uproszczenie refaktoryzacji (można zmieniać implementację, zachowując działanie) i szybka diagnostyka regresji.

Co uznaje się za test jednostkowy?

Nie każdy automatyczny test to test jednostkowy. Kryteria: testowany jest jeden moduł (klasa lub funkcja), zewnętrzne zależności zastąpiono makami lub stubami, test wykonuje się w milisekundach, nie wymaga uruchomienia serwera ani bazy danych. Test odwołujący się do rzeczywistej bazy danych to test integracyjny. Test otwierający przeglądarkę to test E2E. Zrozumienie granic między typami testów jest ważne dla prawidłowego rozkładu wysiłków w piramidzie testowania.

Zasady FIRST i struktura AAA

Wysokiej jakości testy jednostkowe przestrzegają zasad FIRST sformułowanych przez Roberta C. Martina. Każdy test powinien być Fast (szybki — milisekundy), Isolated (izolowany — nie zależy od innych testów), Repeatable (powtarzalny — taki sam wynik na każdej maszynie), Self-validating (samosprawdzający się — wynik „passed” lub „failed”, bez ręcznej weryfikacji) i Timely (terminowy — napisany przed lub jednocześnie z kodem). Naruszenie którejkolwiek zasady zmniejsza wartość testu.

Struktura AAA (Arrange-Act-Assert)

Standardowy wzór do pisania testów jednostkowych. Arrange — przygotowanie danych i zależności: tworzenie obiektów, konfiguracja maków, ustawienie parametrów wejściowych. Act — wykonanie testowanej akcji: wywołanie metody lub funkcji. Assert — weryfikacja wyniku: porównanie rzeczywistej wartości z oczekiwaną. Podział na trzy bloki czyni test czytelnym i zrozumiałym. Jeśli blok Assert wymaga złożonej logiki — test prawdopodobnie sprawdza zbyt wiele naraz.

kotlin
// Przykład testu jednostkowego według wzorca AAA w Kotlinie z JUnit 5
class CalculatorTest {

    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        // ARRANGE — przygotowujemy testowany obiekt
        calculator = Calculator()
    }

    @Test
    fun addition_shouldReturnCorrectSum() {
        // ACT — wykonujemy akcję
        val result = calculator.add(2, 3)

        // ASSERT — sprawdzamy wynik
        Assertions.assertEquals(5, result)
    }
}

Nazewnictwo testów

Nazwa testu powinna opisywać, co jest sprawdzane i jaki jest oczekiwany rezultat. Format: [methodName]_[scenario]_[expectedResult]. Przykład: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Dobra nazwa testu zastępuje komentarz i przy niepowodzeniu od razu wskazuje, która funkcjonalność jest naruszona. Unikaj nazw takich jak test1, checkSomething czy verify — nie niosą one informacji i utrudniają diagnostykę.

Maki, stuby i fake’i: co i kiedy stosować

Do izolacji testowanego modułu od zewnętrznych zależności używa się dublerów testowych (test doubles). Główne typy: maki (mocks) — sprawdzają, czy określona metoda została wywołana z odpowiednimi parametrami; stuby (stubs) — zwracają zadane wartości przy wywołaniu metody; fake’i (fakes) — uproszczona implementacja rzeczywistego komponentu (np. InMemoryUserRepository zamiast UserRepository działającego z bazą danych). Wybór zależy od tego, co trzeba sprawdzić: stan (stub) czy interakcję (mak).

DublerCo sprawdzaPrzykład
MakWywołanie metody z poprawnymi parametramiuserRepository.save(user) został wywołany dokładnie 1 raz
StubZwracana wartośćrepository.findById(1) zwraca User(id=1, name=„Test”)
FakeLogika przez uproszczoną implementacjęInMemoryMapUserRepository z HashMap zamiast bazy danych
SzpiegCięściowe makiowanie rzeczywistego obiektuspy(repo).when(findById).thenReturn(user)

Mockito: przykład makiowania w Javie/Kotlinie

Mockito to najpopularniejszy framework do makiowania w Javie i Kotlinie. Pozwala tworzyć maki przez mock(), konfigurować zwracanie wartości przez when().thenReturn() i sprawdzać wywołania przez verify(). Nowoczesne wersje Mockito (5.x) obsługują statyczne maki (mockStatic) i uproszczoną składnię przez BDDMockito (given-willReturn). Ważna zasada: nie makuj tego, co nie jest twoje — nie twórz maków dla obiektów wartościowych i standardowych bibliotek.

kotlin
// Przykład testu jednostkowego z Mockito w Kotlinie
class OrderServiceTest {

    @Mock
    private lateinit var paymentGateway: PaymentGateway

    @Mock
    private lateinit var userRepository: UserRepository

    private lateinit var orderService: OrderService

    @BeforeEach
    fun init() {
        MockitoAnnotations.openMocks(this)
        orderService = OrderService(paymentGateway, userRepository)
    }

    @Test
    fun processOrder_whenPaymentFails_shouldThrowException() {
        // given
        val user = User(id = 1, balance = 100.0)
        val order = Order(amount = 200.0)
        Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
        Mockito.`when`(userRepository.findById(1)).thenReturn(user)

        // when & then
        assert Throws<PaymentException> {
            orderService.processOrder(user.id, order)
        }

        // verify
        Mockito.verify(paymentGateway).charge(any())
    }
}

TDD: rozwój przez testowanie

TDD (Test-Driven Development) — metodyka, w której test pisze się przed implementacją kodu. Cykl „Red-Green-Refactor”: napisz test, który nie przechodzi (Red), napisz minimalny kod do przejścia testu (Green), popraw kod bez zmiany działania (Refactor). TDD gwarantuje, że cały kod jest pokryty testami (coverage = 100% dla napisanej funkcjonalności) i że kod jest testowalny — jeśli kod jest trudny do przetestowania, oznacza to, że architektura wymaga poprawy.

Zalety TDD

Według badań IBM (2006-2026, badanie podłużne), zespoły stosujące TDD popełniają o 40-80% mniej defektów w produkcji w porównaniu z zespołami piszącymi testy po kodzie. TDD poprawia również architekturę: programista musi myśleć o projekcie API przed implementacją, co prowadzi do słabej zależności (loose coupling) i wysokiej spójności (high cohesion). Dodatkowy efekt — dokumentacja żywym kodem: testy służą jako specyfikacja zachowania modułu, która jest zawsze aktualna.

Kiedy TDD nie jest odpowiednie?

TDD nie zawsze jest optymalne. Komponenty UI trudno testować w izolacji — dla nich skuteczniejsze są testy snapshot lub wizualne testy regresji (Percy, Chromatic). Prototypowanie i badania (spike solutions) nie wymagają testów. Legacy kod bez testów trudno pokryć przez TDD — tutaj najpierw potrzebne są characterization tests (testy utrwalające obecne zachowanie przed refaktoryzacją). W tych przypadkach TDD nie jest całkowicie odrzucane, ale adaptowane — pisze się testy dla zmienianej funkcjonalności, a nie dla całego legacy kodu.

Testy jednostkowe w aplikacjach mobilnych

Rozwój mobilny ma swoją specyfikę: logika biznesowa często jest wymieszana z kodem UI (Activity, ViewController, ViewModel), co utrudnia testy jednostkowe. Najlepszą praktyką są cienkie widoki, grube ViewModele: przenoś całą logikę z komponentów UI do osobnych klas (UseCase, Repository, ViewModel), które łatwo testować bez emulatora. Dla Android i iOS istnieją natywne frameworki testów jednostkowych działające na JVM/Native bez uruchamiania urządzenia.

Testy jednostkowe na Androidzie (JUnit + Mockito/Robolectric)

Testy jednostkowe Androida wykonują się na lokalnej JVM bez emulatora, co zapewnia szybkość wykonania — typowy test zajmuje poniżej 100ms. JUnit 5 to główny runner. Do testów ViewModel używaj kotlinx-coroutines-test do testowania korutyn i Turbine do testowania StateFlow. Robolectric pozwala testować komponenty zależne od Androida (Context, Resources) bez emulatora, ładując shadow-klasy. Do testów Compose używaj Compose UI Test — ale to już testy UI, nie jednostkowe.

Testy jednostkowe na iOS (XCTest + Quick/Nimble)

Testy jednostkowe iOS pisze się w Swift z XCTest (wbudowany w Xcode). Quick + Nimble — frameworki BDD do bardziej czytelnych testów (describe/context/it). Do makiowania używaj Cuckoo (generacja maków) lub SwiftyMocky. Swift obsługuje protokoły i wstrzykiwanie zależności, co ułatwia wymianę zależności. Kluczowe: testy jednostkowe iOS wykonują się na symulatorze macOS, a nie na rzeczywistym urządzeniu. Testy wymagające funkcji sprzętowych (aparat, Bluetooth) to testy integracyjne.

Testy jednostkowe na Flutterze (flutter_test + Mockito)

Testy jednostkowe Fluttera używają pakietu flutter_test i wykonują się na Dart VM bez emulatora. Do makiowania — pakiet mockito z generatorem kodu (build_runner). Testy widgetów (w tym samym pakiecie) testują poszczególne widgety, ale wymagają renderowania i działają wolniej — używaj ich tylko do sprawdzania logiki UI. Czysta logika Dart (modele, repozytoria, bloby) jest testowana jak zwykłe testy Dart bez importowania flutter_test.

dart
// Przykład testu jednostkowego na Flutterze z mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';

void main() {
    late MockApiClient mockApi;
    late UserRepository repository;

    setUp(() {
        mockApi = MockApiClient();
        repository = UserRepository(mockApi);
    });

    test('fetchUser returns user when API succeeds', () async {
        // Arrange
        final expectedUser = User(id: 1, name: 'Test');
        when(mockApi.getUser(1))
            .thenAnswer((_) async => expectedUser);

        // Act
        final result = await repository.fetchUser(1);

        // Assert
        expect(result, expectedUser);
        verify(mockApi.getUser(1)).called(1);
    });
}

Najlepsze praktyki i typowe błędy

Efektywne testy jednostkowe wymagają dyscypliny. Główna zasada: testuj zachowanie, a nie implementację. Test nie powinien wiedzieć, jak moduł jest zaimplementowany wewnętrznie (jakie prywatne metody są wywoływane, w jakiej kolejności). Jeśli test jest związany z implementacją, psuje się przy każdej refaktoryzacji i traci wartość. Test sprawdza kontrakt: przy wejściu X powinno być wyjście Y. Wyjątek — testy dla algorytmów o krytycznej wydajności, gdzie ważna jest kolejność wywołań.

  • Jedno sprawdzenie na test — jeden assert lub jedna grupa powiązanych assertów na jedno logiczne sprawdzenie
  • Unikaj powielania — używaj @BeforeEach / setUp do wspólnej inicjalizacji, testów parametryzowanych dla różnych danych wejściowych
  • Nie testuj prywatnych metod — testuj przez publiczne API. Jeśli prywatna metoda nie jest pokryta — oznacza to, że jej logika nie jest widoczna z zewnątrz
  • Pokrywaj przypadki graniczne — puste kolekcje, null/undefined, liczby ujemne, maksymalne wartości
  • Nie używaj Thread.sleep w testach — to czyni testy wolnymi i niestabilnymi. Używaj testowych timeoutów i korutyn

Jaki poziom pokrycia jest wystarczający?

100% pokrycia to nieosiągalny i niepotrzebny cel. Według Google Testing Blog (2025), optymalny poziom pokrycia dla testów jednostkowych to 70-80% linii kodu. 100% pokrycia często osiąga się kosztem testowania getterów, setterów i konstruktorów, co nie przynosi wartości. Skupiaj się na krytycznej logice biznesowej: złożone obliczenia, walidacja, obsługa błędów, przypadki brzegowe. Używaj JaCoCo (Java), Coverage.py (Python), Istanbul (JS) do pomiaru i ustaw próg w CI — upadek builda przy pokryciu poniżej 60%.

CI/CD a testy jednostkowe

Testy jednostkowe to pierwszy etap każdego pipeline’a CI/CD. Wykonują się przy każdym pushu do repozytorium, przed buildem i deployem. Średni czas wykonania testów jednostkowych w projekcie nie powinien przekraczać 5 minut — jeśli jest dłuższy, testy przestają być „szybkie” i programiści przestają je uruchamiać lokalnie. Dziel testy na szybkie (jednostkowe) i wolne (integracyjne) i uruchamiaj je w różnych etapach pipeline’a. Używaj równoległego wykonania i fail-fast dla przyspieszenia.

Często zadawane pytania

Czym różni się test jednostkowy od integracyjnego?

Test jednostkowy sprawdza jeden moduł w izolacji, zastępując zewnętrzne zależności makami. Test integracyjny sprawdza współdziałanie kilku rzeczywistych komponentów (bazy danych, API, system plików). Testy jednostkowe wykonują się w milisekundach, integracyjne — w sekundach. W piramidzie testowania testy jednostkowe zajmują 70%.

Jaki framework wybrać do testów jednostkowych?

Wybór zależy od platformy: JUnit 5 dla Javy/Kotlina, XCTest dla iOS/Swift, pytest dla Pythona, Jest/Vitest dla JavaScript/TypeScript, flutter_test dla Fluttera. Do makiowania używaj Mockito (Java), Cuckoo (iOS), unittest.mock (Python) lub vitest.mock (JS). Wszystkie nowoczesne frameworki obsługują testy parametryzowane, wbudowane asercje i równoległe uruchamianie.

Czym są zasady F.I.R.S.T. w testowaniu?

Fast — test wykonuje się w milisekundach. Isolated — nie zależy od innych testów i systemów zewnętrznych. Repeatable — daje taki sam wynik na każdej maszynie. Self-validating — sprawdza wynik automatycznie. Timely — napisany przed lub synchronicznie z kodem. Naruszenie choćby jednej zasady obniża efektywność testowania.

Czy trzeba pisać testy jednostkowe dla ViewModel w Android/iOS?

Tak, obowiązkowo. ViewModel zawiera logikę biznesową — obsługę zdarzeń, transformację danych, zarządzanie stanem. Na Androidzie używaj kotlinx-coroutines-test dla korutyn i Turbine do testowania StateFlow. Na iOS testuj Combine Publishers lub async/await w ViewModel. Testy ViewModel to czyste testy jednostkowe działające na JVM/macOS bez emulatora.

Jak testować kod z zapytaniami sieciowymi?

Zapytania sieciowe w testach jednostkowych nie są wykonywane — zastępuje się je makami klienta HTTP. Na Androidzie używaj MockWebServer (OkHttp) — uruchamia on lokalny serwer HTTP, co jest preferowane nad makami, ponieważ odtwarza rzeczywistą interakcje sieciową. MockWebServer zapewnia izolację bez utraty realizmu. Dla iOS — OHHTTPStubs lub URLProtocol do przechwytywania i podmiany odpowiedzi.

Podsumowanie

  • Testy jednostkowe — sprawdzanie poszczególnych modułów w izolacji od zewnętrznych zależności z szybkimi informacjami zwrotnymi
  • Struktura AAA — Arrange (przygotowanie), Act (akcja), Assert (sprawdzenie) — standardowy wzorzec testu
  • Maki i stuby — dublery testowe do izolacji: maki sprawdzają wywołania, stuby zwracają wartości
  • TDD — rozwój przez testowanie (Red-Green-Refactor) zmniejsza liczbę defektów o 40-80%
  • Zasady FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — podstawa wysokiej jakości testu
  • Narzędzia platformowe — JUnit 5 (Android), XCTest (iOS), flutter_test (Flutter), Jest (React Native)
  • Pokrycie 70-80% — optymalny poziom dla krytycznej logiki biznesowej, gettery i settery nie wymagają testów

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również