Unit testy prostego serwisu

Unit testy prostego serwisu
SA
  • Rejestracja:ponad 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:ponad 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:około 11 lat
  • Ostatnio:ponad 4 lata
  • Postów:616
1

brak logiki w serwisie = brak testu

edytowany 1x, ostatnio: Szczery
SA
  • Rejestracja:ponad 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:prawie 8 lat
  • Ostatnio:6 miesięcy
  • 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:ponad 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:ponad 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:prawie 7 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:11 minut
  • Lokalizacja:U krasnoludów - pod górą
  • Postów:4709
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:około 18 lat
  • Ostatnio:8 dni
  • Lokalizacja:Stacktrace
  • Postów:6822
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

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.