Mokowanie callbacku

0

Mam zagwozdkę podczas testowania prezentera. W pierwszym teście sprawdzam po prostu czy wywołanie metody prezentera wywołuje odpowiednią metodę w DAO i wyświetla listę na widoku, coś w stylu:

@Test
    public void loadTasks_showInView() {
       presenter.loadTasks();

        verify(taskDAO).getTasks(loadTasksCallbackCaptor.capture());
        loadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS); // TASKS to jakaś statyczna array lista

        ArgumentCaptor<List> showTasksArgumentCaptor = ArgumentCaptor.forClass(List.class);
        verify(view).showTasks(showTasksArgumentCaptor.capture());
        assertTrue(showTasksArgumentCaptor.getValue().size() == 3);
}

Kolejnym testem jaki chce napisać jest sprawdzenie dodania nowego tasku - w sensie, jeśli dodam task, którego nazwa już jest na wczytanej liście ma pojawić się info, że taki już jest, a jeśli go nie ma to go po prostu dodaje. I teoretycznie mógłbym to zrobić w tym teście wyżej: wywołać na końcu coś w stylu(gdzie ta statyczna lista TASKS zawierałaby już taki):

presenter.saveTask(new Task("task_name");
verify(view).showTaskExistInfo();

i potem jeszcze drugi przypadek dopisać

presenter.saveTask(new Task("new_task_name");
verify(view).showNewTaskInfo();

Ale nie wiem czy nie lepiej to rozbić na 2 dodatkowe testy ? Tylko jak wtedy przekażę ten statyczny array list TASKS do tego prezentera ? Bo w 1 metodzie robię to za pomocą captora w metodze verify, ale wydaje i się głupim 2x weryfikować to samo. Widziałem rozwiązanie za pomocą obiektu Answer ale mimo wszystko ten captor wydaje się prostszy .. jak się to POWINNO robić ?

0

W temacie 1: https://dev.to/danlebrero/the-tragedy-of-100-code-coverage

W temacie 2:
Generalnie uważam londyńskie TDD czyli weryfikowanie wywołań mocków za bezedurę.
Ja bym testował tak:
test1
dodaje task blurfgsdkasjdgad
efektem wywołania metody jest Result( z komunikatem) i to sprawdam w teście

test 2
dodaje task blurfgsdkasjdgad.
dodaje task blurfgsdkasjdgad.
efektem wywołania metody jest Result( z komunikatem) i to sprawdam w teście

Ale nie znam twojego całego frameworku i co robisz. Ale generalnie jak masz metody void to są one co najwyżej kiepsko testowalne.

0

Apka jest na Androida, na razie testuję prezenter. To tak naprawdę mój pierwszy test i pojęcia jak Londyńskie TDD i weryfikowanie Mocków jest mi obce .. jak również nie rozumiem dlaczego miałoby być bzdurne :) jeśli znajdziesz chwilę to poproszę o rozwinięcie tematu ..

To może łatwiej jak opiszę jak ma działać apka:

Uruchamiany, mamy pustą listę, klikamy na button z wczytaniem listy tasków i pobiera je z serwera. Po pobraniu wyświetla na ekranie. Możemy dodać nowe taski tak jak napisałem. Dlatego tak to muszę wytestować, że po pierwsze nie mogę nic dodać jeśli jeszcze lista jest pusta, nie mogę też nic dodać jak task już jest na liście i normalnie dodać jeśli go nie ma - dlatego muszę jakoś zasymulować tą wstępną listę tasków wczytaną z APi

0

Czyli masz jakąs logikę /reguły,
Wydziel tą logikę do klas. Zapomnij o GUI.
Napisz testy dla każdej z tych reguł.
Wyjdzie Ci co masz zrobić.
Podpowiadam coś w stylu:

class UnitializedTaskList implements TaskList {

      LoadedTaskList load() {
          ...
      }
       
      List<Task> allTasks() { 
            return List.empty();
      }
         
}

class LoadedTaskList  implements TaskList {

     AddResult addTask(String taskName) {
     }

      List<Task> allTasks() { 
         ....
      }
}

class AddResult {
   boolean wasAdded; 
   String message;
}

Dodatkowo: Reguła niezaładowania z serwera (niezaicjalizowania) została zastąpiona typem.

0

Tak, stosuję clean architecture, mam w tym przypadku dwa UseCasy: GetAllTasks i AddNewTask, w pierwszym z nich odbywa się pobieranie z serwera, które jest asynchroniczne a w drugim jest cała logika z dodawaniem i sprawdzaniem tego - prezenter i cała logika nic nie wie o frameworku - mogę to odpalić w konsoli.

Ogólnie chodzi o to, że gdyby pobieranie tej listy nie było asynchroniczne to łatwo sobie zmokuję ten rezultat na konkretną listę tasków, które "niby" pobieram a tak to jest ten callback bo przecież ze serwera idzie asynchronicznie.

Czyli z tego co mówisz i z tego co przeczytałem nie warto testować presentery tylko raczej skupić się na tych UseCase'ach tak ?

1

Generalnie tak. Po to jest ten podział na Logikę i resztę :-).

Jeśli to zrobisz w miarę dobrze to dostajesz prosty kod prezentera... gdzie niewiele może się popsuć.
Z drugiej strony jak już coś się popsuje to raczej w stylu - po dodaniu 10 tasku rozwala się layout i nie da się scrollować.
Jeśli by już to testować to jako test GUI a nie unit test.

Nie znam się na testowaniu android ale mam podobny problem z Web.
Testy GUI (w Web to selenium) są przeważnie nieopłacalne: duży nakład pracy, duży koszt utrzymania -
a pokazują fałszywe alarmy i nie pokazują zaś prawdziwych problemów (czyli prawie zawsze rozwalenia się styli :-)).

0

@jarekr000000:

Kurde, mega dzięki, poogarniałem trochę tematu i faktycznie to testowanie samych UseCase'ów wydaje się mega sensowne. Tylko mam taką zagwodkę odnośnie testowania i nie wiem czy dobrze robię.

Załóżmy, że mamy klasę do tesowania usecasa, który pobiera sobie na początku jakąś listę obiektów, wykonuje na niej jakieś operacje i zwraca wartości wyjściowe ( jakieś np info o tej liście ). I teraz tak: mam pisać dwie osobne metody do testowania samego stanu listy i do wartości wyjściowych, które zwraca czy to wszystko ma być w jednej metodzie jako jeden przypadek użycia ?

Dla przykładu, załóżmy, że mam UseCase'a, który odhacza taski na liście, zatem przez konstruktor dostarczam mu listę tasków, na której będzie operował, potem jako wartość wejściową dostarczam mu konkretnego taska, którego ma sprawdzić. No i on w zależności od tego taska albo coś zmieni na tej liście albo nie i zwróci jakieś tam dane na temat tej listy. I teraz tak, to ma wyglądać w ten sposób:

@Test
    public void taskIsRead_checkItInList() {
        
        Task task = new Task(5); // 5 to jakieś tam id
        
        useCase.setTaskList(/* jakas lista*/);
        
        useCase.execute();
        
        //sprawdzenie czy faktycznie na liscie go odznaczyl jako przeczytany
        
    }

    @Test
    public void taskIsRead_incrementTaskReadAmount() {

        Task task = new Task(5); // 5 to jakieś tam id

        useCase.setTaskList(/* jakas lista*/);

        useCase.execute();

        //sprawdzenie czy zwiększył listę przeczytanych tasków

    }

czy jednak dać to w 1 metodzie ? Drugim testem byłoby sprawdzenie nie zmienia stanu listy jeśli taska na niej nie ma, nie zwiększenie licznika itd.

Logiczniejsze mi się wydaje dać to w 1 metodze ze względu jakby na 1 przypadek użycia, kolejnym byłoby sprawdzenie taska, który został już odhaczony, kolejnym sprawdzenie taska na liście, która jest nullem - i byłyby 3 metody tak jak 3 możliwości, które mogą zajść, tylko jak wtedy nazwać taki test ?:

"taskIsRead_checkItInListAndIncrementTaskReadAmountAndCosTamJeszcze() ?? No trochę dziwne.

0

Jedna funkcjonalność - jedna metoda.
Odchaczanie -> 1
Zwiększanie -> 1

Czyli 2.
Co do trzeciej ....
NULL ???? ale dlaczego - co chcesz zrobić z nulllem? Sprawdzenie czy metoda rzuci NullPointerException ? :-)
Jestem wyznawcą prostej religii - jak ktoś Ci wrzuca null do metody to znaczy, że chciałby dostać NullPointerException. Nie należy tej przyjemności nikomu odmawiać.

Niestety nawet jak masz porozdzielanie to w javie takie głupie metody (nazwy( w testach wychodzą. Ja raczej nazywam w stylu BDD - schouldMarkAsReadWhenNotReadBefore ( koszmarek - ale da się z tym żyć).

Ciekawą opcj, która mocno poprawi Ci czytelnośc testów jest Spock
http://spockframework.org/spock/docs/1.1/all_in_one.html

Ale trzeba się z groovym zaprzyjaźnić (ja się nigdy do końca nie przekonałem - choć jezyk jest fajny i czytelny)

0

Czyli jeśli mam w modelu odpowiedzi takie parametry jak:

  1. bool czy odhaczyło już wszystko
  2. bool czy odhaczyło coś w aktualnym sprawdzaniu
  3. ilość przeczytanych

to dla każdego robię osobny test tak ? A z tym nullem sprawdzam, bo jak lista jest null to wtedy na callbacku rzucam onError() i w presenterze mogę wywował jakąś akcję widoku z pokazaniem info, że nie można nic zrobić bo nie ma wczytanej listy

0

Generalnie nie używaj null - to się zwykle źle kończy.
Jak to obejść pokazałem parę postów wyżej (lista niezaincjalizowana).
Możesz doczytać o Null Object Pattern
oraz o Optional i coś z tego wybrać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1