PRA02.rst

Programming Laboratory

Debugowanie i Testowanie

Uwaga! Kod z którego będziemy korzystać na zajęciach jest dostępny na branchu TestAndDebugStart w repozytorium https://github.com/WitMar/PRO2023.git . Kod końcowy można znaleźć na branchu TestAndDebugEnd.

Jeżeli nie widzisz odpowiednich gałęzi na GitHubie wykonaj Ctr+T, a jak to nie pomoże to wybierz z menu Git->Fetch.

Podsumowanie zajęć I

Co wiemy po pierwszych zajęciach:

* Projekt podzielony jest na katalogi: źródłowy - src, testowy - test, zasobów - resources.
* W katalogu projektu umieszczony jest plik pom.xml odpowiadający za ustawienia Mavena, w pliku tym dodajemy odnośniki do zewnętrznych bibliotek i ustawienia budowania aplikacji.
* Ustawienia loggera znajdziemy w pliku log4.properties w katalogu resources. W przypadku gdy inna biblioteka będzie potrzebowała ustawień będziemy także szukać i dodawać je w katalogu resources w odpowiednio nazwanym pliku.
* Pracujemy na GIT - nowa funkcjonalność (nowy temat zajęć) zakładana jest na nowej gałęzi (ang. branch).

Synchronizacja kodu Git z oryginalnym repo

Przejdź do Git -> Manage Remotes

Dodaj wpis o repozytorium prowadzącego

nazwij je najlepiej jako PrzedmiotoweRepo

Wybierz Ctrl + T, aby odświeżyć zależności.

Jeżeli Ctrl+T z jakiegoś powodu nie ściągnie oczekiwanych zmian, to żeby wymusić ściągnięcie zmian wybierz Git -> fetch.

Wtedy na liście branchy w prawym dolnym rogu ekranu powinieneś widzieć gałęzie z repo przedmiotowego i ze swojego.

Przejdź na gałąź TestAndDebugStart.

Jako że dodaliśmy nowe repozytorium, możliwe, że musisz ponownie skonfigurować mavena. Przejdź do zakładki Maven i sprawdź, czy widzisz goale (Lifecycle, Plugin, Dependencies), jeżeli nie, spróbuj odświeżyć Mavena (dwie strzałki w kółko). Jeśli to nie pomoże wybierz lupkę wpisz add maven project i wskaż plik pom.xml z dysku twardego. Następnie odśwież ponownie Mavena. To powinno pomóc - sprawdź, czy twoje pliki .java mają zielone kółko obok nich w zakładce Project.

mavenTryAgain.png

Przydatne skróty IdeaJ

Lista skrótów IdeaJ

Alt + Enter pokaż podpowiedź rozwiązania błędu

Ctrl + W, Ctrl + Shift + W zaznaczenie całej sekcji, rozszerzenie zaznaczenia, zawężenie zaznaczenia.

Ctrl + Shift + N wyszukanie pliku po nazwie

Shift + Shift wyszukiwanie wszędzie

Alt + Insert generuj kod

Ctrl + / zakomentuj line

Ctrl + Shift + / odkomentuj linie

Ctrl + E ostatnio otwierane pliki

Ctrl + Alt + L formatuj kod

Tab, Shift+Tab wcięcie, cofnij wcięcie

Shift + F6 zmiana nazwy

Ctrl + Shift + Alt + T refaktoruj

Ctrl + Alt + M wyekstraktuj metodę

Alt + Right/Left przełączaj zakładki

Ctrl + Alt + Right / Left nawiguj wstecz / do przodu

Ctrl + (klik na nazwie metody) pokaż użycie

Ctrl + Alt + (klik na nazwie metody) pokaż implementację

Ctrl + K służy do commitowania kodu lokalnie do repozytorium

Ctrl + Shift + K służy do commitowania kodu do zewnętrznego repozytorium

Ctrl + T służy do odświeżania projektu - ściągania zmian z serwera

Uwaga!! Jeśli używasz Linuksa lub innego systemu operacyjnego innego niż Windows (lub zdalnego pulpitu), niektóre z tych skrótów mogą być już zarezerwowane w systemie, wtedy musisz zmienić ustawienia systemowe lub zmienić je w Settins->Keymap w Intellij

Wykonaj

Przedź na branch TestAndDebugStart

Wyszukaj klasę o nazwie ClassThatHaveItAll (Ctrl + Shift + N).

Zformatuj kod (Ctrl + Alt + L) - na wydziałowym linuxie ten skrót oznacza wyloguj, musisz zmienić skrót w ustawieniach systemu żeby móc go używać.

Dodaj do klasy w nagłówku linijkę

List <Long> list;

Wybierz automatyczną poprawę błędu (Alt + Enter).

Następnie wybierz (Alt + Insert) wygeneruj konstruktor, oraz settery i gettery dla klasy.

Znajdź interfejs InterfaceOne (Ctr + Shift + f). Wyszukaj użycie i implementacje metody printMe() (Ctrl + click na nazwie metody) klikając na implementacje jak i definicje metody.

Znajdź zastosowania metody ** printMe ** (Alt + F7).

Sprawdź, jak poruszać się między zakładkami (Alt + prawo / lewo) i przejść do poprzednich zmian (Ctrl + Alt + prawo / lewo).

Debug

Poprzez wybór Run -> Debug lub wybierając na klasie prawym przyciskiem myszy Debug możemy debugować nasz kod czyli uruchamiać w kontrolowany sposób, tak by móc zatrzymywać w dowolnym momencie wywołanie kodu.

Czym jest debugowanie?

Aby uruchomić kod lub uruchomić debuggowanie możesz też kliknąć prawym przyciskiem na zielony trójkąt obok nazwy klasy i wybrać opcję DEBUG.

hereForDebug.png

Pomocne w debugowaniu są tzw. breakpointy, czyli miejsce w kodzie, w którym chcemy zatrzymać wywołanie kodu by móc podejrzeć wartości zmiennych i stan programu. Żeby dodać breakpoint klikamy obok numeru linii tak, by pojawiła się czerwona kropka.

breapoint.png

Po kliknięciu prawym przyciskiem myszy możemy określić także specjalny warunek przy breakpoincie.

warunek.png

Po uruchomieniu trybu debug na dole pojawia się nam okno debugowania na którym wyświetlone są aktualne wartości zmiennych. Dodatkowo możemy wybrać przez Alt+F8 lub klikając na ikonę kalkulatora okno ewaluacji wyrażeń i wykonać ewaluację jakiegoś wyrażenia na obecnych wartościach zmiennych. Ewaluacja wyrażeń jest przydatna przy debugowaniu np. wartości list, których wartości nie jesteśmy w stanie przejrzeć pojedynczo.

debug.png

Przy odpowiednich ustawieniach możliwe jest także debugowanie aplikacji działających na serwerze w czasie uruchomienia (więcej na ten temat na przyszłych zajęciach).

Wykonaj

Uruchom klasę Breakpoints. Jaki błąd wystąpił?

Uruchom debug klasy Breakpoints.

Żeby zatrzymać wywołanie dodaj breakpoint w klasie.

Użyj F8 (lub strzałka w dół na panelu debugingu) aby przejść w wywołaniu kodu linijka po linijce.

Użyj StepInto F7 (strzała w prawo-dół na panelu debugingu) aby wejść w wywołanie metody.

Znajdź i popraw błąd.

. class:: tag

Wykonaj

Uruchom klasę EvaluateExpressions. Jaki błąd wystąpił?

Ustaw breakpointa w metodzie ProcessElementAtIndex.

Otwórz okno ewaluacji wyrażeń i wpisz w nie list.get(index) .

Przechodź F9 (zielony trójkąt na panelu debug) do kolejnych wystąpień breakpointa i podglądaj wartości zmiennych.

By sprawdzić jak naprawdę wyglądają ostatnie elementy listy wpisz w oknie evaluacji następujący kod:

Collections.reverse(myList);
myList

Znajdź i popraw błąd.

Wykonaj

Uruchom klasę ConditionalBreak. Jaki błąd wystąpił?

W linii 18, ustaw zwykły breakpoint, czy łatwo jest znaleźć błąd?

Ustaw w linii 18 warunkowy breakpoint tak, by zatrzymywał się przy spełnieniu warunku !everythingIsOk.

Jak widzisz breakpoint nie działa, przenieś go do linii 19.

Uruchom kod, co wywołało błąd?

Shelve ans Stash

Czasami w czasie pracy jesteś zmuszony przełączyć się między różnymi zadaniami z niedokończonym kodem, a następnie wrócić do niego. IntelliJ IDEA zapewnia kilka sposobów wygodnej pracy nad kilkoma różnymi funkcjami bez utraty jej wyników:

  • utwórz nowy branch dla osobnego kodu

  • odłóż zmiany na półkę (ang. stash)

Nie można odkładać na półkę niewersjonowanych plików, które nie zostały dodane do kontroli wersji.

W widoku Local Changes na karcie Git pod kodem kliknij prawym przyciskiem myszy pliki lub listę zmian, które chcesz umieścić na półce, i wybierz Shelve z menu kontekstowego.

shelf.png

Czasami może być konieczne przywrócenie oryginalnej postaci gałęzi - HEAD, co zrobić by nie utracić w tym momencie wykonanych już zmian.

Stash działa podobnie jak shelve tylko zamiast obsługi poprzez GIT wykorzystuje IDE do przechowywania zmian.

Aby schować, zamiany lokalnie w IDE a nie w GIT wybierz Git | VCS Operations | Stash Changes, aby usunąć schowek, wybierz Git | VCS Operations | Unstash Changes.

Jeśli chcesz utworzyć nową gałąź na podstawie wybranego schowka zamiast stosować go do aktualnej gałęzi, wpisz nową nazwę w polu "As new branch".

JUnit

JUnit to biblioteka służąca do pisania testów kodu. Testy mają na celu kontrolę jakości kodu, różnych ścieżek wywołań a także tego czy nowe zmiany nie wprowadzają błędów i zachowują starą funkcjonalność.

Proces budowania wersji przez Maven domyślnie uruchamia wszystkie testy znajdujące się w katalogu TEST podczas budowania pliku wykonywalnego.

Najpierw musimy dodać w pliku pom.xml zależność dla modułu testowego.

W naszym branchu jest już stworzona klasa testowa jednak by dodać nową należałoby wykonać poniższe operacje: wejść do klasy AdvanceMath i wybrać Ctrl + Shift + T , kliknąć create new Test. Aby osiągnąć to samo inaczej można wybrać nazwę klasy i kliknąć Alt + Enter i wybrać create test.

Biblioteka JUnit korzysta z tzw. adnotacji. Przed każdą metodą która ma być uruchamiana jako test umieszczamy @Test.

Inną istotną adnotacją jest @Before która pozwala nam zdefiniować operacje które będą wykonywane przed uruchomieniem każdego testu.

Przykładowa klasa testująca:

package second.junit;

import com.sun.xml.internal.ws.policy.AssertionSet;
import example.HelloWorld;
import org.apache.log4j.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class AdvanceMathTest {

    AdvanceMath math;
    final static Logger logger = Logger.getLogger(AdvanceMath.class);

    @Before
    public void setUp(){
        logger.info("Odpalam setUpa");
        math = new AdvanceMath();
    }

    @Test
    public void additionTest() {
        Integer a = math.addition(1,4);
        assertTrue(a==5);
    }

    @Test
    public void additionTestString() {
        long a = math.addition("1",4);
        Assert.assertEquals(5L, a);
    }


    @Test(expected = Exception.class)
    public void additionTestString2() {
        int a = math.addition("a1",4);
    }

}

Test możemy uruchomić klikając na klasę i wybierając run lub przez zakładkę Maven -> Test. Testy są też dodawane automatycznie przy wykonaniu Install w zakładce Mavena.

Przykłady wykorzystania biblioteki JUnit :

Polecam spojrzeć na przykłady na temat testowania list i map.

AssertJ

Lepszą biblioteką do pisania bardziej złożonych testów jest biblioteka AssertJ.

W pliku pom.xml dodano zależność do biblioteki assertj.

Zobacz klasę Frodo.java w katalogu test. Testy w niej są o wiele łatwiejsze do zrozumienia i definiowania w przypadku złożonych warunków.

Dokumentacja:

Mock

W przypadku gdy testujemy złożone systemy nasz kod może zależeć od zewnętrznych bibliotek, serwisów, albo danych. W przypadku testów jednostkowych oczekiwanie jest, że będziemy je wykonywać wielokrotnie a czas ich wykonywania będzie ograniczony. W związku z tym nie chcemy testując naszego kodu czekać na połączenie, podbierać dużych plików danych, czy zależeć od dostępności zewnętrznych elementów.

Jak więc testować kod, który takie zależności posiada? Z pomocą przychodzą nam tzw. Mocki, czyli klasy udające inne klasy, dla których możemy sami definiować odpowiedź klasy dla wywołania jej metod (sami tym samym dostarczając danych testowych dla naszych przypadków testowych).

Do tworzenia Mock-ów będziemy stosować bibliotekę Mockito.

Zadanie

Znajdź odpowiedni wpis w Maven repository i dodaj Mockito do projektu.

Tworzenie mock’ów sprowadza się do wywołania metody metodę Mockito.mock().

Class1 mock1 = Mockito.mock(Class1.class);
Class2 mock2 = Mockito.mock(Class2.class);

TestedClass testedClass = new TestedClass(mock1, mock2);

Wykorzystanie mock'ów. Dla zadanych parametrów funkcji z mockowanej klasy zwróc dany wynik jako odpowiedź. To w jaki sposób mock ma zareagować na wywołanie metody definiujemy używając Mockito.when.

Mockito.when(Class1.method1(any(), any()).thenReturn("mockedOutput");

Do określenia zachowania mock’a możesz także użyć metod:

Mockito.doReturn - zwróć wartość
Mockito.doThrow - zwróć wyjątek
Mockito.doNothing - nic nie zwracaj
Mockito.doAnswer - gdy chcesz zwrócić wartość w oparciu np. o wejściowe parametry

Zobacz jak działa klasa ProcessQuery.

W naszym przypadku chcemy przetestować metodę process klasy ProcessQuery, metoda ta korzysta z klasy HttpQueryClass, która oryginalnie w kodzie wywołuje zapytanie HTTP o dane z zewnętrznego serwera HTTP. To jest klasa, którą chcielibyśmy zamockować, a dokładniej wywołanie metody query z niej.

@Test
public void mockTestExample() {
    when(httpQueryClass.query()).thenReturn("test");

    ProcessQuery processQuery = new ProcessQuery(httpQueryClass);

    String result = processQuery.process();
    assertThat(result).isEqualTo("TEST");
}

Zadanie

Dodaj do metody query parametr i zmień definicję mock tak by działała z wywołaniem metody z parametrem, dla konkretnej wartości jak i dla dowolnej wartości.

Extra

Good practices of code development
  • Make indents inside loops and if-statements

function foo() {
    if ($maybe) {
        do_it_now();
        again();
    } else {
        abort_mission();
    }
    finalize();
}
  • You can deal with curly brackets also like this:

function foo()
{
    if ($maybe)
    {
        do_it_now();
        again();
    }
    else
    {
        abort_mission();
    }
    finalize();
}
  • Use blank lines consistently and as required. Blank lines may be used for separating code lines or line groups semantically for readability.

  • Character count of a line should be limited for readability.

  • Name variables, procedures, functions in sensible way, start with small letter and introduce new word with capital letter

studentsCounter;
listIterator;
averageOverLastWeek;
findBestInClass();
computeAverage();
  • Name classes in sensible way, start with capital letter and introduce new word with capital letter

NightShift;
FastCar;
  • Name constant values with all capital letters, separate words with _

DAYS_IN_THE_WEEK();
NUMBER_OF_SHIFTS();
  • Using space chars in code should also be consistent in whole application. Generally, situations below are suitable for using spaces:

  • Between operators and operands:

a += b , c = 0; (a == b)
  • Between statement keywords and brackets:

if (value) {, public class A {
  • After ';' char in loops:

for (int i = 0; i < length; i++)
  • Between type casters and operands:

(int) value , (String) value