Field Injection vs Constructor Injection

0

Które rozwiązanie preferujecie?

@Autowired
private A a;

czy:

private final A a;

@Autowired
B(A a) { this.a = a; }

Osobiście jestem za constructor injection z uwagi na możliwość tworzenia pól final co ma oczywiste zalety.
Jednak większość osób z jakimi się spotkałem stosuje field injection.

0

Konstruktor bo ułatwia testowanie ;)

3

Ankieta w stylu "Wolisz bić żonę czy przynosić jej kwiaty", ale niech będzie. Zagłosuję :P.

0

Konstruktor, ale bez tego @Autoweird. Też ułatwia testowanie, o jedna linijkę mniej i nie trzeba dodawać Springa do bibliotek :-)

0

@scibi92: @jarekr000000

Moglibyście wytłumaczyć?
Rozumiem, że można użyć final, ale czy jest coś nie tak z @InjectMocks?

1

Co tu do tłumaczenia? Polecam artykuły zalinkowane przez @rav3n - tam nawet jeden gość rozwija dalej, że kontenery sa do d..y.
See tez to: http://www.yegor256.com/2014/10/03/di-containers-are-evil.html

A mocki to kolejne przekleństwo "enterprise" w javie. Dzieki nim można napisać mnóstwo testów, które się nie wywalą się nawet jak usuniesz implementację metod z logiki. Ukochane narzędzie konsultantów i firm zewnętrznych wciskających Ci "super profesjonalny" kod.
(Btw. mockowanie IO czy tego typu rzeczy rozumiem, ale już np. nie ogarniam mockowania sobie własnej bazy danych, repo..., albo testujemy co ten kod robi albo udajemy).

Ogólnie miłośnikiem injectów możesz być - to się da zrozumieć . Można długo pisać kod, cieszyć się, że coś się wstrzykuje i nie przejmować się tym co zamawiał klient tylko pogrążać się w otchłani enterpsie designu. To daje mega satysfakcję. Jak by ten design nie był rozdmuchany to te wszystkie singletony jakoś się powstrzykują - nieważne co jest w której warstwie kontener i tak to odnajdzie.
Aż do dnia kiedy magia przestanie działać....

2

od wersji 4.3 w Springu @Autowired nie jest wymagane jeśli klasa definiuje tylko jeden konstruktor
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/new-in-4.3.html

co do ankiety - przez konstruktor

0

Jesli masz obiekt serwisowy który wczytuje encje User z repo i mapuje do Springowego UserDetails (interface do Spring Security) i mockujesz mu repozytorium i sprawdzasz czy odpowiednio zmapował to rozumiem że to nie jest test, bo nie zostało zaciągnięte z bazy danych? :D
A poza tym jak masz dao czy różne pomocnicze klasy za każdym razem new używasz?
A poza tym @jarekr000000 jak masz DataSource i chcesz w teście automatycznym end-to-end podmienić baze danych na testową jak osiągniesz to bez IoC?

0
jarekr000000 napisał(a):

A mocki to kolejne przekleństwo "enterprise" w javie. Dzieki nim można 100% napisać mnóstwo testów, które się nie wywalą się nawet jak usuniesz implementację metod z logiki. Ukochane narzędzie konsultantów i firm zewnętrznych wciskających Ci "super profesjonalny" kod.
(Btw. mockowanie IO czy tego typu rzeczy rozumiem, ale już np. nie ogarniam mockowania sobie własnej bazy danych, repo..., albo testujemy co ten kod robi albo udajemy).

Hmm. To ja zupełnie inaczej rozumiem testowanie... Może mam błędne przeświadczenie, ale moje testy teraz wyglądają np. tak.

Przyjmijmy, że mamy metodę:
Optional<Person> getPerson(Integer brainId);
i ta metoda używa w sobie repository ktore wyciąga jakąś składową człowieka ( mózg ) po id.
i na jego podstawie za pomocą jakiejś własnej logiki tworzy człowieka.

  1. Piszę test na tą logikę getPerson.
    a) mockuję wyciąganie mózgu z bazy
    b) sprawdzam czy dla wyciagnietego mozgu został prawidłowo stworzony człowiek
  2. Piszę test akceptacyjny BDD typu
    given: mozg id 10
    when: getPerson
    then: person data shoud be x y z

Akceptacyjny już bez mockowania bazy.

Ewentualnie jak ten mock nie odnosi sie do bazy tylko do jakiejs zaleznosci posiadającej logikę (np. jakiś parser)
to wtedy piszę na tą zależność osobny unit test.

Jak padnie akceptacyjny to padnie też unitowy i mogę szybko zlokalizować błąd.
Jak padnie akceptacyjny a nie padnie junit to błąd leży w elemencie zamockowanym czyli repository, taka sytuacja jednak zdarza mi się bardzo rzadko.

Coś tu robię źle? Jakbym mógł to polepszyć zmieniając injection z field na constructor?

0

@mariusz_s: a jak chcesz zmokować DAO jak masz to jako prywatne pole

0

@scibi92:

Robię tak:

@InjectMocks
private MyService service;

@Mock
private MyDao dao;

public void test() {
    // given
    Obj expected = ...
    
    // when  
    when(dao.findProfileBySomething(id)).thenReturn(Stream.of(a, b, c).collect(toList());
    Obj actual = service.doSomethingWithProfile(...);

    // then
    assertEquals(expected, actual)
    verify(cosTam, ... )...
}

Do tego jakiś argument capture itp.

No i jak mockuje coś takiego to albo to testuje osobno, albo jak to jest baza to robie test akceptacyjny.

0

No i własnie korzystasz z jakiś dziwnych adnotacji, a tak byś mógł utworzyć serwis z użyciem konstruktora i tam podac mocki jako argumenty, uniezależniłbyś się od Mockito i byłoby to zgodne z programowaniem obiektowym. W OOP zewnętrzne komponenty raczej powinny być przekazywane jako argument :)

0

A poza tym @jarekr000000 jak masz DataSource i chcesz w teście automatycznym end-to-end podmienić baze danych na testową jak osiągniesz to bez IoC?

Przez Dependency Injection - do tego nie potrzeba żadnego kontenera ani annotacji.

0

Coś takiego?

new A(new B(new C()))); 
0

@scibi92:
No właśnie chodzi mi o to. Dlaczego @mock (lub Mockito.mock()) nie jest dziwne, ale @InjectMock już jest?
Rozumiem, że mogę się pozbyć adnotacji, ale czy ta adnotacja jest taka straszna, skoro i tak używam mockito?

1
  1. Kiedyś preferowałem field injection żeby nie mieć w kodzie czegoś co nie jest explicite używane, ale teraz preferuje constructor injection, bo np. można wygodnie tworzyć takie obiekty i bez kontenera i bez czarowania z inject mockami.
  2. Jeśli juz to @Inject żeby nie mieć zależnosci od Springa, a od wersji 4.3 w ogóle można i to pominąć. A jeśli jeszcze deklaracje beanów wyciągniesz do konfiguracji zamiast dawać @Service czy @Named (nie mówie ze powinieneś, ale możesz) to w ogóle zostajesz z klasami POJO, ale nadal zarządzanymi przez Springa w runtime.
0

@mariusz_s: przecież nie stosujemy @IncjectMock tylko coś takiego :

@Test
public void test userDetailsMapping(){
UserRepository userRepository = Mockito.mock(UserRepository.class);
DefaultUserService userService = new DefaultUserService(userRepository);
//bla bla bla
}
0

@scibi92:
Nie pisalem o injectmock tylko o mock.
Mniejsza z tym, chyba juz was rozumiem @scibi92 @Shalom

Lepiej użyć konstruktora bo po prostu mam pewność jakie zależności są wymagane i co konkretnie wstrzykuję.
Jeśli uzywam @InjectMock to 'czaruję', bo injectowanie odbywa się pod spodem, nie mam podpowiedzi, mogę pomylić zależności, lub sam mechanizm może zawieźć.

Tak?

1

Generalnie tak. Jak używasz field injection i inject mocks to nie zauważysz jak dodasz nowe pole do klasy i zapomnisz tego uwzglednić w testach. A to moze się wywalić w dziwny sposób i dopiero po czasie.

0

Dobra. Sprawa z injectem się wyjaśniła :)
A co z moimi testami? Bo trochę mnie @jarekr000000 zmartwił :D
Jestem jeszcze na etapie juniora, dlatego nie chciałbym sobie czegoś źle wpoić i brnąc w to.

1

@jarekr000000: nie lubi testów jednostkowych (wszystko testuje integracyjnie), ale takie przypadki są rzadkie.

0

Constructor injection i np Lombok do tego:

@RestController
@RequestMapping("/agent")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AgentController {

    private final AgentService agentService;

    @GetMapping("/{agentId}/image")
    public AgentDetails getAgents(@PathVariable String agentId) {
        String imageUrl = agentService.getAgentImageUrl(agentId);
        return createAgentDetails(imageUrl);
    }
    
    // code here
}

0

Na kiego grzyba Lombok do tego? Ja rozumiem gettery i settery ale można konstruktor taki normalnie zrobić :)

0

A po co konstruktor jak można Lombokiem to załatwić?
Co jeżeli masz, np 7 pól które chcesz wstrzyknąć?

0

No to IntelliJ generujesz konstruktor. 7 to już chyba za dużo :)

0

@mariusz_s problemu nie będziesz miał jeśli nie będziesz testował metod i klas... tylko funkcjonalności.

Chcesz coś dopisać? zastanów się co to robi? Jak sprawdzić, że działa dobrze z punktu widzenia klienta (twoim klientem może być kod) i wtedy napisz test który to weryfikuje. Jak tak zaczniesz robić to jakoś zniknie Ci konieczność mockowania naturalnie.

Podany wyżej przykład:

  1. Piszę test akceptacyjny BDD typu
    given: mozg id 10
    when: getPerson
    then: person data shoud be x y z

uważam za słaby... no bo niby dlaczego should be x y z. Ten mozg o id = 10 to jest zaszyty na stałe z takimi parametrami?
Raczej tu bym widział taki test:

given zapisując (save() mozg dla persony od id 10 i x y z
when: getPerson o id 10
then: person data shoud be x y z

0

@jarekr000000:
To był tylko taki wyssany z palca przykład. Nie będę tutaj rzucał kodem, ale dostarczanie danych wygląda mniej więcej tak jak opisałeś.
Dodatkowo staram się dokładnie opisać funkcjonalność i cały proces (cucumber).

0

No to całkiem masz szansę to ładnie robić. Po prostu jak będziesz podchodził od strony użytkowej, akceptacyjnej, to masz szansę zrobić dokładnie takie testy jak trzeba.
Poczytaj co to "londyńska szkoła TDD" i... nie rób tak :-) To droga do kupy (kiedyś takie TDD robiłem i wiem czym to się kończy).

2

nie uzywam setterow/kontenerow.
btw 'wstrzykiwianie' to strasznie wydumany termin sugerujacy jakas specjalistyczna operacje ;)

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.