Unit testy prostego serwisu

Unit testy prostego serwisu
SA
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:6
0

Cześć,

w jaki sposób powinienem przetestować prosty serwis?

Czy powinienem testować wywołania metod? Argumenty z którymi ta metoda została wywołana? Zwracaną wartość?

Jeżeli wszystkie z powyższych to czy powinienem pakować je do jednego testu czy osobnych?

Przykład:

Kopiuj
@Service
public class ClientServiceImpl implements ClientService {

	private final ClientRepository clientRepository;	
	
	public ClientServiceImpl(ClientRepository clientRepository) {
		this.clientRepository = clientRepository;
	}

	@Override
	public Iterable<Client> findAll() {
		return clientRepository.findAll();
	}

	@Override
	public Client findOne(Long id) {
		return clientRepository.findOne(id);
	}

	@Override
	public Client save(Client client) {
		return clientRepository.save(client);
	}

	@Override
	public void remove(Long id) {
		clientRepository.delete(id);
	}
}

Jak powinny wyglądać testy tego serwisu?

Kopiuj
public class ClientServiceImplTest {

	private ClientService clientService;
	
	@Mock
	private ClientRepository clientRepository;
	
	@Before
	public void beforeEach() {
		clientService = new ClientServiceImpl(clientRepository);
	}
	
	//Test wywołania metody
	@Test
	public void check_findAll_invoke_repository_method() {
		clientService.findAll();
		
		verify(clientRepository, times(1)).findAll();		
	}
	
	//Test zwracanej wartości
	@Test
	public void check_findAll_return() {
		Iterable<Client> value = new ArrayList<>();
		when(clientRepository.findAll()).thenReturn(value);
		
		Iterable<Client> result = clientService.findAll();
		
		assertThat(result).isEqualTo(value);
	}
	
	//Test wywołania z argumentem
	@Test
	public void check_getById_invoke_repository_method() {		
		Long id = Long.valueOf(0);
		
		clientService.findOne(id);
		
		verify(clientRepository, times(1)).findOne(id);
	}	
}
edytowany 2x, ostatnio: sajmplus
zyxist
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 6 lat
  • Postów:101
0

Witaj!

Wiesz, myślę, że należałoby zacząć od odpowiedzenia sobie na pytanie, do czego Ci jest ten serwis potrzebny? Bo zauważ, że jedyne co robisz w każdej metodzie, to wywołujesz dokładnie taką samą metodę z obiektu innej klasy. Gdybym natrafił na taki kod w projekcie, najpierw zastanawiałbym się, jaki jest racjonalny powód istnienia takiej klasy. I gdyby okazało się, że żaden, albo że można to zrobić inaczej, to po prostu nacisnąłbym klawisz Delete i tym samym rozwiązał też problem, co testować :).

Osobiście nie piszę dedykowanych testów na gettery i settery. To jest wskazówka ode mnie.


SZ
  • Rejestracja:prawie 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:616
1

brak logiki w serwisie = brak testu

edytowany 1x, ostatnio: Szczery
SA
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:6
0
zyxist napisał(a):

Witaj!

Wiesz, myślę, że należałoby zacząć od odpowiedzenia sobie na pytanie, do czego Ci jest ten serwis potrzebny? Bo zauważ, że jedyne co robisz w każdej metodzie, to wywołujesz dokładnie taką samą metodę z obiektu innej klasy. Gdybym natrafił na taki kod w projekcie, najpierw zastanawiałbym się, jaki jest racjonalny powód istnienia takiej klasy. I gdyby okazało się, że żaden, albo że można to zrobić inaczej, to po prostu nacisnąłbym klawisz Delete i tym samym rozwiązał też problem, co testować :).

Osobiście nie piszę dedykowanych testów na gettery i settery. To jest wskazówka ode mnie.

Racjonalnym powodem istnienia tej klasy jest rozszerzalność. Zauważ że klasa ta implementuje interfejs.

Co do testowania getterow i setterow to nie testujemy ich chyba z tego powodu że są generowane przez IDE?

@Szczery
Jaka jest dla Ciebie definicja logiki? Czy wywołanie metody składnika nie jest jakąś logiką?

edytowany 1x, ostatnio: sajmplus
KL
  • Rejestracja:ponad 7 lat
  • Ostatnio:4 miesiące
  • Postów:191
0

Logika jest tam, gdzie wykonywane są jakieś obliczenia, istnieje jakiś proces decyzyzjny w aplikacji, który jest znaczący od strony biznesowej. Operacje związane z I/O źródła danych(wyciąganie czy tworzenie nowych rekordów) same w sobie nie są zatem logiką w tym rozumieniu.

A9
A9
  • Rejestracja:około 7 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Zgorzelec/Görlitz
  • Postów:14
0

Cześć @sajmplus,

Zauważ, że mając taki interfejs możesz również go zaimplementować w innych klasach. Nie jest to problemem. Rozumiem jednak, że ta klasa jest swego rodzaju pewnym repozytorium. Twoje testy wyglądają dosyć solidnie w tym przypadku. Ich wartość jest jednak niewielka. Jak pisali przedmówcy warto mieć jakąś logikę, którą przetestujesz przy użyciu metod z tej klasy, to uprości robotę. Warto zapoznać się z biblioteką Mockito, która pozwala świetnie zmockować zachowanie takiej klasy. Czyli jak to powinno wyglądać? Ano mniej więcej tak:

Kopiuj
//najpierw tworzysz przykładowo jakąś pustą listę klientów
private ObservableList<Client> clientList = FXCollections.observableArrayList(); //polecam gorąco :) u Ciebie trzeba zrobić Iterable

//nastepnie w setUpie czy jak to u Ciebie jest beforze mockujesz sobie findAlla, na wzór
public void setUp(){
    when(clientRepository.findAll().thenReturn(clientList);
}

Dzięki temu masz mocka, którego nauczyłeś fajnej rzeczy zwracania tego czego oczekujesz i wiesz jak wygląda i dzięki temu możesz dodawać i usuwać wartości wedle uznania. Poszukaj CookBook Mockito when/then i szybko załapiesz temat. Sam wprawdzie nie jestem mistrzem testów, ale mam małe doświadczenie, także jeżeli masz pytania to wal śmiało.

Pozdrawiam,
adaszewski95

IC
Ja za to proponuje nie testować czy mockito dobrze działa, do 2017 roku powstało już dużo unit testów sprawdzających jego działanie i myślę, że jako postanowienie noworoczne można spróbować przestać to robić.
A9
adaszewski95
Powiedz mi proszę dlaczego to napisałeś, może robię coś źle i nie wiem o tym?
IC
Mockowanie jest dosyć przydatną techniką, ale nie gdy testujesz jedną klasę, i wszystkie jej funkcjonalności są zamockowane. Co testujesz gdy w repozytorium pod metode 'findAll' wrzucasz listę obiektów, a potem sprawdzasz czy na pewno zamockowana metoda zwróciła to co podstawiłeś?
A9
adaszewski95
Dzięki wielkie za ten komentarz. Aktualnie jestem juniorem także jeszcze dużo się uczę. Jak wrócę do pracy po nowym roku to zajrzę w mój kod i zobaczę czy właśnie tego nie robiłem. Bo mam takie wrażenie, że akurat to mogłem robić w moich testach. Aha już wiem co u mnie jest inaczej. Co jeżeli przykładowe 'findAll' jest asynchroniczne? Wydaje mi się, że dlatego tak to zaimplementowałem u siebie w projekcie.
SA
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 7 lat
  • Postów:6
0

Jasne, wykorzystuje mockito nawet w testach w pierwszym poście.

Chodziło mi o samą zasadność testowania metod które tylko wywołują inne metody.

W jaki sposób można więc podejść do takich klas w metodzie TDD?

A9
adaszewski95
Przetestuj po prostu metody, które są przez nie wywoływane. Inaczej myślę, że po prostu podwajasz kod testów. Ale mogę się mylić, bo w rezultacie pozostawiasz tę klasę bez testów.
MS
  • Rejestracja:ponad 7 lat
  • Ostatnio:ponad 6 lat
  • Postów:1
1

Ja to widzę tak:

Albo ignorujesz swój serwis w testach ze względu na brak właściwej logiki biznesowej - nie masz po prostu czego testować - bo twój serwis dodaje tylko warstwę nad repozytorium i przez to tak naprawdę testujesz poprawne działanie providera dostarczającego warstwę repozytorium, czego po prostu się nie robi bo testujesz nie twój kod.

Albo piszesz testy, które sprawdzają poprawność CRUD-a na bazie, ale wtedy to już nie są w zasadzie testy jednostkowe tylko integracyjne, bo potrzebujesz jakiejś bazy danych.

W twoim przypadku jeżeli zmokujesz repozytorium to tak jakbyś testował poprawność działania mocka więc nie widzę tu sęsu. Zabawy z mockiem mają sęs tylko jeżeli masz jakąś konkretną logikę do przetestowania i potrzebujesz zmockować jakąś zależność aby przyśpieszyć wykonanie testów jednostkowych.

Jak zawsze nie ma tutaj jednej słusznej ścieżki. Pytanie brzmi co chcesz osiągnąć? Co testy mają tak naprawdę testować?

zyxist
Fajny wpis podsumowujący dyskusję. Jedynie: "sens", zamiast "sęs".
jarekr000000
  • Rejestracja:ponad 8 lat
  • Ostatnio:około 6 godzin
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4707
6

Co do testowania getterów i setterów to zasada jest taka, że* najlepiej nie testować metod* - żadnych! Również getterów i setterów.
Testuj wyłącznie funkcjonalność czyli co ma dany serwis robić. Zwykle wiąże się to z wywołaniem jakiejś metody.
Chyba, że masz serwis co potrafi magicznie coś zrobić bez wywoływania go -> tym lepiej :-)

Co do testowania getterów i setterów to zasada jest taka, że najlepiej nie mieć żadnych setterów i getterów. Jest rok 2017, a nie 2001.

Co do podanego serwisu - to jest CRUD - czyli serwis bez sensu, Nie wiem po co Ci taki. Twoje aplikacja używa CRUdów ? Piszesz coś dla klientów z 2001 roku ?

Co do testów Mockito typu:

Kopiuj
verify(clientRepository, times(1)).findAll();       

To sprawdza czy metoda findAll wywoła z repozytorium findAll... czyli jeśli np. kiedyś na poziomie tego biednego serwisu zrobisz jakiś cache i nie bedziesz już wywoływać findAll z repo tylko z cache to test się wywali. Mimo, że metoda zwraca poprawny wynik... i może nawet jest poprawiona wydajnościowo.
Precz z mocksturbacją !


jeden i pół terabajta powinno wystarczyć każdemu
edytowany 4x, ostatnio: jarekr000000
FE
Przecież CRUD jak najbardziej ma sens, po prostu jest nudny. A gettery i settery często są wymagane przez biblioteki.
DI
  • Rejestracja:prawie 11 lat
  • Ostatnio:ponad 6 lat
  • Postów:103
0

Taki tip: przy używaniu verify, times(1) jest wartością domyślną, nie trzeba więc go przekazywać jawnie.

Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:około miesiąc
  • Lokalizacja:Stacktrace
  • Postów:6821
1

Test, który podałeś testuje mockito, a nie serwis. Ten serwis to implementacja wzorca „Encja na twarz i pchasz”:

Jak to przetestować jednostkowo? Nie testować jednostkowo, a napisać test integracyjny, albo jeszcze lepiej zapoznać się z REST Repositories i pozwolić by działa się springowa magia.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
W0
"We can solve any problem by introducing an extra level of indirection... except for the problem of too many levels of indirection" :) Zgadzam się, że serwisy w stylu "podaj dalej" i przekazywanie encji do widoku to problem, tyle tylko że nie jestem przekonany czy dorzucenie warstw Application (pomiędzy Controller a Service) oraz Command/Query (pomiędzy Service a DAO) jest dobrym pomysłem, zwłaszcza jeśli chodzi o prostsze aplikacje.
Koziołek
@wartek01: w prostych aplikacjach ma to sens. Wymaga jednak ogarnięcia kilku rzeczy. W przypadku springa jest to security. Można też jeszcze prościej https://postgrest.com/en/v4.3/
W0
Miałem na myśli link z YT, a nie RESTy
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)