Unit testy prostego serwisu

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:

@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?

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);
	}	
}
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.

1

brak logiki w serwisie = brak testu

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ą?

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.

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:

//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

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?

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ć?

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:

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ą !

0

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

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.

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