Testowanie aplikacji .NET MAUI w 2026: xUnit, DeviceTests i Appium (kompletny przewodnik)

Przewodnik po testowaniu aplikacji .NET MAUI w 2026: testy jednostkowe ViewModel z xUnit v3, DeviceTests dla handlerów, testy UI z Appium 2 oraz gotowy pipeline GitHub Actions z pokryciem kodu i mutation testingiem.

Testowanie .NET MAUI 2026: xUnit + Appium

Zaktualizowano: 5 sierpnia 2026

Testowanie aplikacji .NET MAUI w 2026 roku opiera się na trzech niezależnych warstwach: testach jednostkowych ViewModel z użyciem xUnit i Moq/NSubstitute, testach na urządzeniu (DeviceTests) uruchamianych przez runner MAUI dla handlerów i kontrolek, oraz testach UI end-to-end z Appium 2 lub oficjalnego pakietu Microsoft.Maui.Controls.UITesting. Każda warstwa ma inny cel, koszt i szybkość. Dobra strategia łączy je w piramidę, gdzie 70% to testy jednostkowe, 20% integracyjne i 10% pełne testy UI na iOS/Android. W tym przewodniku pokazuję kompletną konfigurację z .NET 10, xUnit v3 i Appium 2.11, wraz z pipeline'em GitHub Actions, którego sam używam w produkcji od kilkunastu miesięcy.

  • Piramida testów w .NET MAUI: testy jednostkowe ViewModel (xUnit v3) jako fundament, DeviceTests dla platformozależnych handlerów, oraz Appium 2 / MAUI UI Testing dla scenariuszy end-to-end.
  • Wraz z .NET MAUI 10 Microsoft ustabilizował pakiet Microsoft.Maui.Controls.UITesting zbudowany na Appium 2, eliminując zależność od wycofanego Xamarin.UITest.
  • Testy ViewModel wymagają wzorca MVVM z CommunityToolkit.Mvvm, wstrzykiwania zależności i fake implementacji serwisów, dzięki czemu logika biznesowa jest testowana bez emulatora.
  • DeviceTests uruchamiane są w kontekście aplikacji MAUI, więc mają dostęp do MauiApp, DI i platformozależnych API. To idealne środowisko dla własnych handlerów.
  • W CI/CD (GitHub Actions) macierz windows-latest plus macos-14 wystarcza do budowy i uruchomienia testów Android emulatora oraz iOS Simulator.
  • Pokrycie kodu warto mierzyć przez coverlet.collector i publikować raport z ReportGenerator, celując w 70–80% dla warstwy ViewModel oraz Services.

Piramida testów w .NET MAUI

Szczerze mówiąc, w mojej praktyce z aplikacjami .NET MAUI dostarczonymi do produkcji od 2023 roku jedna zasada pozostaje niezmienna: to, co da się przetestować bez emulatora, powinno być przetestowane bez emulatora. Emulator Android startuje 40–90 sekund, a iOS Simulator 20–30 sekund. Jeżeli 500 testów wymaga urządzenia, pipeline CI trwa 45 minut zamiast 4.

Dlatego dobra piramida testów wygląda tak: na dole stoją setki testów jednostkowych ViewModel, serwisów i mapperów (czysty C#, brak MAUI). Środek to testy integracyjne z prawdziwą bazą SQLite (in-memory lub tymczasowy plik) i kilkanaście DeviceTests dla własnych handlerów. Wierzchołek to 20–30 testów UI end-to-end pokrywających kluczowe ścieżki użytkownika, na przykład logowanie, dodawanie zamówienia czy offline sync.

Testy jednostkowe uruchamiają się w milisekundach, DeviceTests w sekundach, a testy UI w dziesiątkach sekund. Jeśli odwrócisz proporcje (80% testów UI i 20% jednostkowych), otrzymasz pipeline, w którym każdy pull request kosztuje 30 minut CI i zniechęca zespół do pisania testów w ogóle.

Analogicznie jak w architekturze aplikacji MAUI, separacja warstw jest tu kluczowa. Wzorzec MVVM w .NET MAUI z CommunityToolkit istnieje właśnie po to, by logika biznesowa była testowalna bez UI. Podobne podejście stosujemy przy optymalizacji: dobre profilowanie wydajności .NET MAUI wymaga metryk, a metryki wymagają powtarzalnych testów.

Konfiguracja projektu testowego

Zaczynamy od utworzenia dwóch osobnych projektów: MyApp.UnitTests jako standardowa biblioteka .NET 10 oraz MyApp.DeviceTests jako projekt MAUI z runnerem xUnit. Struktura solucji powinna wyglądać następująco: src/MyApp (aplikacja MAUI), src/MyApp.Core (logika biznesowa jako biblioteka .NET), tests/MyApp.UnitTests, tests/MyApp.DeviceTests i tests/MyApp.UITests.

Wydzielenie MyApp.Core to jeden z najważniejszych kroków. Pozwala ono testować ViewModel i serwisy bez ciężaru workloadu MAUI, przyspiesza dotnet restore nawet dziesięciokrotnie i eliminuje konieczność instalacji Android SDK na maszynach deweloperów pracujących wyłącznie nad logiką biznesową. W moim ostatnim projekcie CI dla samej warstwy Core zszedł z 6 minut do 40 sekund po tej jednej zmianie.

Plik MyApp.UnitTests.csproj powinien mieć następującą zawartość:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <IsPackable>false</IsPackable>
    <Nullable>enable</Nullable>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="xunit.v3" Version="1.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="3.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
    <PackageReference Include="NSubstitute" Version="5.3.0" />
    <PackageReference Include="FluentAssertions" Version="6.12.2" />
    <PackageReference Include="coverlet.collector" Version="6.0.4" />
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\src\MyApp.Core\MyApp.Core.csproj" />
  </ItemGroup>
</Project>

Kluczowa uwaga: dla testów ViewModel nigdy nie referuj projektu MAUI. Referencja do MyApp.Core wystarczy, a jeżeli ViewModel potrzebuje typów MAUI (np. INavigation, IDispatcher), zdefiniuj je jako abstrakcje w Core i wstrzykuj implementacje. To pozwala projektowi testowemu uruchomić się jako czysty net10.0, bez workloadu net10.0-android czy net10.0-ios.

Jak testować ViewModel w .NET MAUI?

Testy ViewModel są sercem strategii testowej każdej aplikacji MVVM. Rozważmy prosty LoginViewModel z CommunityToolkit.Mvvm, który waliduje dane logowania, wywołuje serwis autoryzacji i nawiguje do ekranu głównego. Aby test był w pełni niezależny od MAUI, wszystkie zależności (serwis autoryzacji, nawigacja, dispatcher) są wstrzykiwane przez konstruktor.

Poniżej pokazuję kod produkcyjny, który testujemy, a następnie zestaw testów pokrywających trzy najważniejsze ścieżki: walidację, sukces logowania oraz błąd zwrócony przez serwis.

// MyApp.Core/ViewModels/LoginViewModel.cs
public partial class LoginViewModel : ObservableObject
{
    private readonly IAuthService _auth;
    private readonly INavigationService _navigation;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(LoginCommand))]
    private string _email = string.Empty;

    [ObservableProperty]
    [NotifyCanExecuteChangedFor(nameof(LoginCommand))]
    private string _password = string.Empty;

    [ObservableProperty]
    private string? _errorMessage;

    public LoginViewModel(IAuthService auth, INavigationService navigation)
    {
        _auth = auth;
        _navigation = navigation;
    }

    private bool CanLogin() =>
        !string.IsNullOrWhiteSpace(Email) && Password.Length >= 8;

    [RelayCommand(CanExecute = nameof(CanLogin))]
    private async Task LoginAsync(CancellationToken ct)
    {
        ErrorMessage = null;
        var result = await _auth.SignInAsync(Email, Password, ct);
        if (result.IsSuccess)
            await _navigation.GoToAsync("//home");
        else
            ErrorMessage = result.Error;
    }
}

A oto komplet testów xUnit pokrywający walidację, sukces logowania i błąd serwisu:

// MyApp.UnitTests/ViewModels/LoginViewModelTests.cs
public class LoginViewModelTests
{
    private readonly IAuthService _auth = Substitute.For<IAuthService>();
    private readonly INavigationService _nav = Substitute.For<INavigationService>();

    private LoginViewModel CreateSut() => new(_auth, _nav);

    [Theory]
    [InlineData("", "password12", false)]
    [InlineData("[email protected]", "short", false)]
    [InlineData("[email protected]", "password12", true)]
    public void LoginCommand_CanExecute_matches_validation_rules(
        string email, string password, bool expected)
    {
        var vm = CreateSut();
        vm.Email = email;
        vm.Password = password;

        vm.LoginCommand.CanExecute(null).Should().Be(expected);
    }

    [Fact]
    public async Task LoginAsync_navigates_home_on_success()
    {
        var vm = CreateSut();
        vm.Email = "[email protected]";
        vm.Password = "password12";
        _auth.SignInAsync("[email protected]", "password12", Arg.Any<CancellationToken>())
             .Returns(AuthResult.Success("token"));

        await vm.LoginCommand.ExecuteAsync(null);

        await _nav.Received(1).GoToAsync("//home");
        vm.ErrorMessage.Should().BeNull();
    }

    [Fact]
    public async Task LoginAsync_sets_error_message_on_failure()
    {
        var vm = CreateSut();
        vm.Email = "[email protected]";
        vm.Password = "password12";
        _auth.SignInAsync(default!, default!, default)
             .ReturnsForAnyArgs(AuthResult.Fail("Nieprawidłowe hasło"));

        await vm.LoginCommand.ExecuteAsync(null);

        vm.ErrorMessage.Should().Be("Nieprawidłowe hasło");
        await _nav.DidNotReceiveWithAnyArgs().GoToAsync(default!);
    }
}

Zauważ, że w tym teście nigdzie nie pojawia się MauiApp, MainThread ani Dispatcher. To dlatego, że logika ViewModel została zaprojektowana jako czysty C#. Atrybut [RelayCommand] z CommunityToolkit generuje command asynchroniczny, który nie wymaga wątku UI. Jeżeli w kodzie masz MainThread.BeginInvokeOnMainThread, opakuj go w abstrakcję IUiDispatcher i wstrzykuj. W przeciwnym razie testy będą wymagały specyficznego kontekstu synchronizacji.

DeviceTests: testowanie handlerów i platformozależnego kodu

DeviceTests to koncepcja wprowadzona przez zespół .NET MAUI, w której runner xUnit uruchamia się wewnątrz aplikacji MAUI na docelowej platformie (Android, iOS, macOS, Windows). Dzięki temu masz dostęp do prawdziwego MauiApp, kontenera DI oraz platformowych API. To idealne środowisko dla testów własnych handlerów MAUI (dokumentacja Microsoft), custom renderowania czy weryfikacji, że twoja kontrolka faktycznie tworzy natywny UIView lub android.view.View.

Aby dodać DeviceTests do projektu, użyj szablonu maui-unittests z SDK MAUI (dostępny od .NET 8, ustabilizowany w .NET 10):

dotnet new install Microsoft.Maui.Templates.net10
dotnet new maui-unittests -n MyApp.DeviceTests -o tests/MyApp.DeviceTests

Wygenerowany projekt zawiera MauiProgram.cs zbliżony do aplikacji głównej, ale rejestruje testowy runner. Przykładowy test handlera dla własnej kontrolki BadgeView, która renderuje okrągły znacznik z liczbą:

// MyApp.DeviceTests/Handlers/BadgeViewHandlerTests.cs
public class BadgeViewHandlerTests
{
    [Fact]
    public async Task Handler_creates_native_view_with_correct_text()
    {
        var badge = new BadgeView { Count = 5 };

        await InvokeOnMainThreadAsync(() =>
        {
            var handler = badge.ToHandler(MauiContext);
#if ANDROID
            var native = handler.PlatformView as global::Android.Widget.TextView;
            Assert.NotNull(native);
            Assert.Equal("5", native!.Text);
#elif IOS
            var native = handler.PlatformView as UIKit.UILabel;
            Assert.NotNull(native);
            Assert.Equal("5", native!.Text);
#endif
        });
    }

    [Fact]
    public async Task Setting_count_above_99_shows_plus_indicator()
    {
        var badge = new BadgeView { Count = 128 };

        await InvokeOnMainThreadAsync(() =>
        {
            var handler = badge.ToHandler(MauiContext);
            var text = badge.DisplayText;
            Assert.Equal("99+", text);
        });
    }
}

Testy UI z Appium 2 i .NET MAUI UI Testing

Do końca 2024 roku standardem był Xamarin.UITest, który został formalnie wycofany razem z Xamarin.Forms 1 maja 2024 roku. Wraz z .NET MAUI 10 Microsoft dostarcza oficjalny pakiet Microsoft.Maui.Controls.UITesting zbudowany na Appium 2, który upraszcza pisanie testów end-to-end w C#. Alternatywnie można używać czystego Appium 2 z Appium.WebDriver. Daje to większą kontrolę, ale wymaga ręcznej konfiguracji sesji. Szczegóły dostępnych sterowników znajdziesz w oficjalnej dokumentacji Appium 2.

Instalacja Appium 2 i sterowników dla Androida oraz iOS:

npm install -g [email protected]
appium driver install uiautomator2   # Android
appium driver install xcuitest       # iOS
appium driver install mac2           # macOS
appium driver install windows        # Windows

Przykładowy test logowania w Appium 2, oparty na WebDriver. Aplikacja MAUI musi być zbudowana w konfiguracji Release z odblokowaną automatyzacją. Dla Androida wystarczy standardowy APK, natomiast dla iOS potrzebny jest Debug lub specjalny build z zainstalowanym Xcode Automation.

// MyApp.UITests/Scenarios/LoginScenarioTests.cs
public class LoginScenarioTests : IAsyncLifetime
{
    private AndroidDriver _driver = null!;

    public async Task InitializeAsync()
    {
        var options = new AppiumOptions
        {
            AutomationName = "UiAutomator2",
            PlatformName = "Android",
            DeviceName = "Pixel_8_API_35",
            App = Path.GetFullPath("../../../artifacts/com.myapp.apk"),
            NewCommandTimeout = TimeSpan.FromMinutes(2),
        };
        _driver = new AndroidDriver(new Uri("http://127.0.0.1:4723"), options);
        await Task.CompletedTask;
    }

    public Task DisposeAsync()
    {
        _driver?.Quit();
        return Task.CompletedTask;
    }

    [Fact]
    public void User_can_log_in_with_valid_credentials()
    {
        // AutomationId="EmailEntry" w XAML -> accessibility id w Appium
        _driver.FindElement(MobileBy.AccessibilityId("EmailEntry"))
               .SendKeys("[email protected]");

        _driver.FindElement(MobileBy.AccessibilityId("PasswordEntry"))
               .SendKeys("Test1234!");

        _driver.FindElement(MobileBy.AccessibilityId("LoginButton"))
               .Click();

        var welcome = new WebDriverWait(_driver, TimeSpan.FromSeconds(10))
            .Until(d => d.FindElement(MobileBy.AccessibilityId("WelcomeLabel")));

        welcome.Text.Should().StartWith("Witaj");
    }
}

Kluczowe: ustaw AutomationId na każdym elemencie XAML, który chcesz testować (<Entry AutomationId="EmailEntry" />). W Appium jest to następnie mapowane na MobileBy.AccessibilityId. Bez tego zostajesz z lokalizacją elementów po XPath, który jest kruchy i wolny. Dodatkowo AutomationId poprawia dostępność aplikacji dla czytników ekranu, więc korzyść jest podwójna.

Testy integracyjne z SQLite in-memory

Jeżeli twoja aplikacja przechowuje dane lokalnie (a większość aplikacji MAUI to robi), warto pokryć testami integracyjnymi warstwę repozytorium wraz z EF Core i SQLite. Zamiast mockować DbContext, co jest anty-wzorcem, bo nie testujesz prawdziwych zapytań, użyj SQLite w trybie in-memory. Uruchamia się w milisekundach i daje realne zachowanie bazy, wliczając transakcje i migracje. Ta strategia świetnie komponuje się z architekturą offline-first w .NET MAUI z SQLite i EF Core, gdzie warstwa danych jest kluczowym miejscem powstawania błędów.

// MyApp.UnitTests/Data/OrderRepositoryTests.cs
public class OrderRepositoryTests : IAsyncLifetime
{
    private SqliteConnection _connection = null!;
    private AppDbContext _context = null!;

    public async Task InitializeAsync()
    {
        // Kluczowe: trzymamy otwarte polaczenie, inaczej baza in-memory znika
        _connection = new SqliteConnection("DataSource=:memory:");
        await _connection.OpenAsync();

        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite(_connection)
            .Options;
        _context = new AppDbContext(options);
        await _context.Database.EnsureCreatedAsync();
    }

    public async Task DisposeAsync()
    {
        await _context.DisposeAsync();
        await _connection.DisposeAsync();
    }

    [Fact]
    public async Task Adding_order_persists_it_with_generated_id()
    {
        var repo = new OrderRepository(_context);
        var order = new Order { CustomerName = "Anna Kowalska", Total = 129.50m };

        var saved = await repo.AddAsync(order);
        var loaded = await repo.GetByIdAsync(saved.Id);

        loaded.Should().NotBeNull();
        loaded!.CustomerName.Should().Be("Anna Kowalska");
    }
}

CI/CD w GitHub Actions dla testów MAUI

Uruchamianie pełnej piramidy testów w CI wymaga trzech jobów: unit (najszybszy, na każdym push), device (na PR do main) i ui (nocny lub przed release). Poniższy workflow uruchamia testy jednostkowe na Ubuntu (najtaniej, bez workloadu MAUI), a DeviceTests i UITests na macos-14 z emulatorem Androida.

# .github/workflows/tests.yml
name: tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  unit-tests:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'
      - name: Restore
        run: dotnet restore tests/MyApp.UnitTests/MyApp.UnitTests.csproj
      - name: Test
        run: |
          dotnet test tests/MyApp.UnitTests/MyApp.UnitTests.csproj \
            --configuration Release \
            --logger "trx;LogFileName=unit.trx" \
            --collect:"XPlat Code Coverage"

  device-tests-android:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'
      - name: Install MAUI workload
        run: dotnet workload install maui-android
      - name: Enable KVM
        run: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
      - name: Run device tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 35
          arch: x86_64
          script: dotnet build -t:Run tests/MyApp.DeviceTests -f net10.0-android

  ui-tests-appium:
    runs-on: macos-14
    needs: device-tests-android
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install Appium
        run: |
          npm install -g [email protected]
          appium driver install uiautomator2
      - name: Start Appium server
        run: appium --base-path /wd/hub &
      - name: Run UI tests
        run: dotnet test tests/MyApp.UITests --configuration Release

Pokrycie kodu i metryki jakości

Pokrycie kodu w .NET zbieramy przez coverlet.collector (zawarty w szablonie xUnit) i przetwarzamy raport za pomocą ReportGenerator. Wynik można wypchnąć do Codecov lub SonarCloud, aby monitorować trendy. Realistyczne cele: 75–85% dla warstwy Core (ViewModel, Services, Mappers) i 40–60% dla projektu MAUI (dużo kodu XAML i platformozależnego, których nie da się sensownie mierzyć). Nie ścigaj się do 100%. Testy pisane dla samego pokrycia szybko stają się kruchym balastem.

dotnet tool install --global dotnet-reportgenerator-globaltool
dotnet test --collect:"XPlat Code Coverage"
reportgenerator \
  -reports:"**/coverage.cobertura.xml" \
  -targetdir:"coveragereport" \
  -reporttypes:Html;Cobertura

Metryki, które faktycznie pomagają: liczba testów per warstwa (Core, Services, ViewModels), czas wykonania (jeśli suita jednostkowa przekracza 30 sekund, coś jest nie tak z zależnościami I/O w testach), oraz mutation score mierzony przez Stryker.NET. Mutation testing pokazuje, ile mutacji kodu twoje testy faktycznie wykrywają. To realny sprawdzian jakości pokrycia, znacznie ostrzejszy od zwykłego procenta linii.

Najczęstsze błędy w testach .NET MAUI

W ciągu ostatnich dwóch lat konsultacji przy migracji z Xamarin.UITest do .NET MAUI Testing widziałem te same błędy w kilkunastu zespołach. Oto lista, którą warto mieć przed oczami przed rozpoczęciem pisania pierwszej suity testów:

  1. Testowanie ViewModel z MainThread w środku. Zawsze abstrahuj do IUiDispatcher. Inaczej testy działają lokalnie, ale wywalają się w CI.
  2. Brak AutomationId na elementach XAML. Bez tego testy Appium używają XPath, który pęka przy każdej zmianie hierarchii Grid.
  3. Referowanie projektu MAUI z projektu testów jednostkowych. Zwiększa czas restore z 5 sekund do 45 i wymusza instalację workloadu MAUI na CI dla testów, które go nie potrzebują.
  4. Statyczny DateTime.Now. Wstrzykuj TimeProvider (dostępny od .NET 8), inaczej testy walidacji dat będą flaky raz na jakiś czas.
  5. Ignorowanie ConfigureAwait(false) w bibliotece Core. W testach xUnit v3 z paralelizacją może prowadzić do deadlocków, jeśli SynchronizationContext jest ustawiony.
  6. Uruchamianie testów UI na każdym push. To pęka pipeline czasowo i finansowo. Trzymaj UITests na PR do main lub w cyklu nocnym.

Najczęściej zadawane pytania

Czy .NET MAUI ma wbudowaną obsługę testów UI?

Od .NET MAUI 10 tak. Pakiet Microsoft.Maui.Controls.UITesting zbudowany na Appium 2 jest oficjalnym sposobem pisania testów UI. Wcześniej używano Xamarin.UITest, który został wycofany 1 maja 2024 roku wraz z Xamarin.Forms. Alternatywą pozostaje czysty Appium 2 z pakietem Appium.WebDriver, który daje większą kontrolę nad sesją i konfiguracją.

Jaki framework testowy jest zalecany dla .NET MAUI?

Dla testów jednostkowych najlepszym wyborem jest xUnit v3 (świetna integracja z .NET 10 i mocna paralelizacja). Dla mockowania używaj NSubstitute lub Moq. Dla asercji sprawdza się FluentAssertions albo wbudowane Assert. Dla testów UI Appium 2 z Microsoft.Maui.Controls.UITesting. Dla mutation testingu Stryker.NET.

Jak testować kod używający MainThread lub Dispatcher w .NET MAUI?

Nigdy nie wywołuj MainThread bezpośrednio w ViewModel. Zdefiniuj abstrakcję IUiDispatcher z metodą Task InvokeOnUiThreadAsync(Func<Task> action) i wstrzykuj implementację przez DI. W testach użyj implementacji fake, która po prostu wywołuje delegata synchronicznie. To eliminuje potrzebę uruchamiania testów jednostkowych w kontekście UI.

Czy można uruchamiać testy .NET MAUI na Linux?

Tak, ale tylko testy jednostkowe warstwy Core (czyste biblioteki .NET). DeviceTests dla Androida również działają na Linuxie z emulatorem, ale wymagają zainstalowania Android SDK i włączenia KVM. Testy iOS wymagają wyłącznie macOS z Xcode. To twarde ograniczenie Apple, którego nie da się obejść w CI.

Ile testów UI powinno mieć aplikacja .NET MAUI?

W praktyce 15–40 testów UI wystarczy nawet dla dużej aplikacji, jeśli pokrywają one najważniejsze ścieżki biznesowe (logowanie, główny flow zakupowy, obsługa błędów sieci, synchronizacja offline). Reszta powinna być pokryta testami jednostkowymi i integracyjnymi. Powyżej 100 testów UI koszt utrzymania i czas CI zaczyna przewyższać wartość tych testów.

Editorial Team
O Autorze Editorial Team

Our team of expert writers and editors.