Telefony

Testowanie jednostkowe za pomocą MVVM w systemie iOS

Autor: Peter Berry
Data Utworzenia: 11 Lipiec 2021
Data Aktualizacji: 12 Móc 2024
Anonim
MVVM + Dependency Injection in Swift | Unit Testing | iOS
Wideo: MVVM + Dependency Injection in Swift | Unit Testing | iOS

Zawartość

Jestem bardzo zainteresowany znajdowaniem różnych rozwiązań problemu. Obecnie badam różne architektury dla aplikacji iOS.

Krótki przegląd MVVM

W moim poprzednim artykule „MVVM ulepszenie w MVC”, widzieliśmy, jak MVVM oddziela stan widoku od kontrolera w ViewModel. Możemy sterować stanami View reprezentowanymi przez ViewModel w izolacji. Daje nam to możliwość pisania przypadków testowych w celu weryfikacji różnych zachowań View.

Przedmiot testu

W tym samouczku będziemy testować aplikację „Gry wideo”. To prosta aplikacja, która ładuje i wyświetla listę gier na ekranie głównym. Nasz przepływ aplikacji wygląda następująco:

  1. Aplikacja rozpocznie ładowanie gier przy ładowaniu widoku.
  2. Podczas ładowania aplikacja wyświetli pokrętło na ekranie.
  3. Jeśli dane zostaną załadowane pomyślnie, wyświetlimy elementy w widoku tabeli.
  4. Jeśli ładowanie zakończy się błędem, aplikacja wyświetli komunikat „Nie można załadować gier. Spróbuj ponownie!” wiadomość.
  5. Jeśli liczba załadowanych elementów wynosi 0, aplikacja wyświetli komunikat „Brak dostępnych gier w tej chwili”. wiadomość.

Przygotowałem projekt początkowy, abyś mógł ćwiczyć w trakcie. Pobierz projekt stąd i otwórz projekt w folderze Starter, aby rozpocząć.


Model widoku listy gier

Zanim zaczniemy pisać przypadki testowe naszego GamesListViewModel, przyjrzyjmy się jego zachowaniu. Możesz znaleźć GamesListViewModel w katalogu głównym startera porject.

public protocol GamesListViewModelDelegate {func errorDidOccur (vm: GamesListViewModel) func didStartLoading (vm: GamesListViewModel) func itemsLoaded (vm: GamesListViewModel)} public class GamesListViewModel {/ListView Powiadamia o różnych zdarzeniach przepływu public varegate? /// Powtarza się, czy jest jakiś błąd do wyświetlenia /// Prawda, jeśli tak, inna mądra Fałsz publiczny var showError: Bool {get} /// Komunikat o błędzie do wyświetlenia na ekranie publiczny var errorMessage: String {get} /// Number pozycji na liście załadowanych gier public var itemsCount: Int {get} /// Reprezentuje albo pokazanie znaku ładowania, albo nie publiczny var showLoading: Bool {get} /// Ta metoda inicjuje żądanie załadowania do usługi DataService public func loadGames () /// Zwraca model widoku elementu do wyświetlenia na liście public func getItem (at index: Int) -> GameListItemViewModel? /// Pobiera usługę danych do wykonywania operacji na danych public init (_ dataService: DataService)}

W GameListViewModel ujawniamy wszystkie informacje o stanie, powiadomienia o zmianie stanu (GameListViewModelDelegate) i akcje, które może zainicjować interfejs użytkownika. GameListViewModel ma jedną zależność, czyli DataService, która pozwala nam załadować listę gier.


Przygotuj środowisko testowe

Jedną z zasad testów jednostkowych jest odizolowanie obiektu testu i zasymulowanie jego zależności w celu przetestowania wszystkich możliwych przypadków.

Nasz obiekt testowy GameListViewModel ma jedną zależność, tj.DaneService. Aby wyodrębnić i zasymulować środowisko GameListViewModel, musimy udawać zachowania usługi DataService. Poniżej znajduje się próbny serwis, którego będziemy używać:

protocol DataService {func loadGames (_ Complete: @escaping ([Game] ?, Error?) -> Void)} struct MockDataService: DataService {var games: [Game]? var błąd: Błąd? func loadGames (_ Complete: @escaping ([Game] ?, Error?) -> Void) {DispatchQueue.main.async {guard let games = self.games else {complete (nil, self.error!) return} complete ( games, nil)}}}

GameListViewModel powiadamia o zdarzeniach za pośrednictwem GameListViewModelDelegate i do monitorowania tych zdarzeń użyjemy:

public struct MonitorGamesListViewModelDelegate: GamesListViewModelDelegate {public var errorDidOccurCallback: ((_ vm: GamesListViewModel) -> Void)? public var didStartLoadingCallback: ((_ vm: GamesListViewModel) -> Void)? public var itemsLoadedCallback: ((_ vm: GamesListViewModel) -> Void)? public func errorDidOccur (vm: GamesListViewModel) {errorDidOccurCallback? (vm)} public func didStartLoading (vm: GamesListViewModel) {didStartLoadingCallback? (vm)} public func itemsLoaded (vm: GamesListViewModel) {itemsLoading?

Zacznijmy testować

W naszym pierwszym przypadku testowym przetestujemy zachowanie GamesListViewModel, jeśli dane zostaną pomyślnie załadowane bez pustej listy. Aby to zrobić, mówimy MockDataService, aby wysłał listę gier z jednym elementem i sprawdzimy odpowiedź GamesListViewModel za pomocą MonitorGamesListViewModelDelegate.


/// Po pomyślnym załadowaniu listy gier /// Oczekiwane zachowanie: /// - pokaż tylko ładowanie powinno być prawdziwe, a rozpoczęte ładowanie powinno zostać wywołane /// - należy wywołać załadowane elementy, a liczba elementów powinna być poprawna func testDataLoadSuccessfully () { // ustawianie oczekiwań let startedLoadingExpectation = oczekiwanie (description: "got callback start loading") let itemsLoadedExpectation = oczekiwanie (description: "itemy zostały załadowane oczekiwanie") // symulowanie środowiska mockDataService.error = nil mockDataService.games = [Game (title : "Happy life")] // konfigurowanie monitorów odpowiedzi responseMonitor.didStartLoadingCallback = {vm in XCTAssert (vm.showLoading, "Flaga ładowania powinna być prawdziwa") XCTAssert (! Vm.showError, "Flaga błędu nie powinna być prawdziwa") startedLoadingExpectation.fulfill ()} responseMonitor.errorDidOccurCallback = {_ in XCTAssert (false, "Invalid error callback")} responseMonitor.itemsLoadedCallback = {vm in XCTAssert (! vm.showLoading, "Flaga ładowania nie powinna być prawdą")! vm.showError, "Flaga błędu nie powinna być prawdziwa") XCTAssert (vm.itemsCount == 1, "Wczytano nieprawidłowe elementy") itemsLoadedExpectation.fulfill ()} // wykonanie akcji vm.loadGames () // sprawdzenie oczekiwania na oczekiwanie ( for: [startedLoadingExpectation, itemsLoadedExpectation], limit czasu: 1, egzekwowanieOrder: true)}

W tym przypadku testowym wykonujemy pięć kroków:

  1. Zdefiniowane oczekiwania, które należy spełnić przed zakończeniem testu.
  2. Konfigurowanie zależności, aby wysłać prawidłową odpowiedź bez pustej listy gier.
  3. Konfigurowanie monitora odpowiedzi, który nasłuchuje zdarzeń i potwierdza oczekiwany stan ViewModel.
  4. Symulowanie akcji „loadGames” w ViewModel.
  5. Oczekiwanie na spełnienie się oczekiwań w określonej kolejności.

Teraz sprawdzimy, co się stanie, jeśli ładowanie żądania danych zakończy się błędem. Zgodnie z naszym wymaganiem showError powinno mieć wartość true, a errorMessage - „Nie można załadować gier. Spróbuj ponownie!”

/// Ładowanie listy gier kończy się błędem func testDataLoadedWithError () {// ustawianie oczekiwania let startedLoadingExpectation = Expectation (description: "got callback start loading.") Let errorOccuredExpectation = oczekiwanie (opis: "oczekiwanie błędu ładowania.") // symulowanie środowiska mockDataService.error = AppDataServiceError.invalidResponse mockDataService.games = nil // konfigurowanie monitorów odpowiedzi responseMonitor.didStartLoadingCallback = {vm in XCTAssert (vm.showLoading, "Loading flag should be true. „Flaga błędu nie powinna być prawdziwa.”) StartedLoadingExpectation.fulfill ()} responseMonitor.errorDidOccurCallback = {vm w XCTAssert (! Vm.showLoading, „Flaga ładowania nie powinna być prawdziwa.”) XCTAssert (vm.showError, „Flaga błędu powinna być prawdą. ") XCTAssert (vm.errorMessage ==" Nie można załadować gier.Spróbuj ponownie! "," Nieprawidłowy komunikat o błędzie. ") XCTAssert (vm.itemsCount == 0," Liczba pozycji powinna wynosić 0. ") errorOccuredExpectation.fulfill ()} responseMonitor.itemsLoadedCallback = {_ in XCTAssert (false," Items załadowane wywołanie zwrotne nie powinno być wywoływane. ")} // wykonanie akcji vm.loadGames () // sprawdzenie oczekiwania na oczekiwanie (for: [startedLoadingExpectation, errorOccuredExpectation], timeout: 1, egzekwowanieOrder: true)}

Teraz nasz ostatni przypadek, dane zostały załadowane pomyślnie, ale lista gier jest pusta. Pożądanym wynikiem w tym przypadku jest, showError powinno mieć wartość true, a errorMessage powinien zawierać informację „Brak dostępnych gier w tej chwili”.

/// Lista wczytanych gier jest pusta func testDataLoadedWithEmptyList () {// ustawianie oczekiwań let startedLoadingExpectation = Expectation (description: "got callback start loading.") Let errorOccuredExpectation = oczekiwanie (opis: "oczekiwanie błędu ładowania.") // symulowanie the enviornment mockDataService.error = nil mockDataService.games = [] // konfigurowanie monitorów odpowiedzi responseMonitor.didStartLoadingCallback = {vm in XCTAssert (vm.showLoading, "Flaga ładowania powinna być prawdziwa.") XCTAssert (! vm.showError, "Błąd flaga nie powinna być prawdziwa. ") startedLoadingExpectation.fulfill ()} responseMonitor.errorDidOccurCallback = {vm w XCTAssert (! vm.showLoading," Flaga ładowania nie powinna być prawdziwa. ") XCTAssert (vm.showError," Flaga błędu powinna być prawdziwa . ") XCTAssert (vm.errorMessage ==" Brak dostępnych gier. "," Nieprawidłowy komunikat o błędzie. ") XCTAssert (vm.itemsCount == 0," Liczba pozycji powinna wynosić 0 ") errorOccuredExpectation.fulfill () } responseMonitor.itemsLoadedCallback = {_ in XCTAssert (false , "Pozycje załadowane wywołania zwrotnego nie powinny być wywoływane.")} // wykonanie akcji vm.loadGames () // sprawdzenie oczekiwania na oczekiwanie (for: [startedLoadingExpectation, errorOccuredExpectation], timeout: 1, egzekwowanieOrder: true)}

Wniosek

Widzieliśmy, jak MVVM ułatwia symulację zachowania użytkownika bez zajmowania się widokiem. Napisaliśmy trzy przypadki testowe, aby zobaczyć, jak możemy przetestować różne stany Widoku.

Możesz pobrać ostateczny projekt stąd. Również jeśli masz jakieś pytanie lub sugestię, zostaw komentarz poniżej.

Wybór Strony

Nasza Rekomendacja

4 najlepsze 13-calowe obudowy MacBooka Pro na 2021 rok
Komputery

4 najlepsze 13-calowe obudowy MacBooka Pro na 2021 rok

Paul je t pa jonatem nowych technologii i przez wiele lat prowadził w Wielkiej Brytanii naukę o mediach cyfrowych. Obecnie mie zka na Florydzie w U A.Pracując od lat z komputerami Mac, wierzę, że mogę...
Jak używać miniatur w YouTube do budowania marki swojego kanału
Internet

Jak używać miniatur w YouTube do budowania marki swojego kanału

Uwielbiam grać w gry wideo i dzielić ię woimi odkryciami, w kazówkami i ztuczkami.Jeśli zagłębiłeś ię w zeroki świat prowadzenia kanału YouTube, bez wątpienia ły załeś kla yczne rady dotyczące bu...