Czy warto stosować Dependency Injection poza UnitTesting

17

Biorąc pod uwagę konstruktor, który nigdy nie będzie musiał używać różnych implementacji kilku obiektów, które inicjuje, czy nadal jest praktyczne stosowanie DI? W końcu nadal możemy chcieć przeprowadzić test jednostkowy.

Klasa, o której mowa, inicjuje kilka innych klas w swoim konstruktorze, a klasy, których używa, są dość specyficzne. Nigdy nie użyje innej implementacji. Czy mamy rację, unikając próby zaprogramowania interfejsu?

Seph
źródło
3
KISS - to zawsze najlepsze podejście.
Reactgular,
5
Moim zdaniem pytanie to jest bardziej szczegółowe niż proponowany duplikat. projektowania dla przyszłościowych zmian-or-rozwiązać pytania z problemem-at-strony , dlatego głos na nie blisko to
k3b
1
Czy testowalność nie jest wystarczającym powodem?
ConditionRacer
Tak naprawdę to nie brzmi jak duplikat tak samo, jak samo odpowiadanie. Jeśli tak się nigdy nie stanie, projektowanie dla niego nie jest planowaniem przyszłych zmian. Nawet astronauci z architektury projektują tylko zmiany, które nigdy nie nastąpią z powodu złej oceny.
Steve314,

Odpowiedzi:

13

Zależy to od tego, czy masz pewność, że „nigdy, nigdy” nie jest poprawne (w czasie, gdy aplikacja będzie używana).

Ogólnie:

  • Nie modyfikuj projektu tylko w celu przetestowania. Testy jednostkowe służą ci, a nie odwrotnie.
  • Nie powtarzaj się. Tworzenie interfejsu, który będzie miał tylko jednego spadkobiercę, jest bezużyteczne.
  • Jeśli klasa nie jest testowalna, prawdopodobnie nie jest również elastyczna / rozszerzalna w rzeczywistych przypadkach użycia. Czasami jest to w porządku - prawdopodobnie nie potrzebujesz tej elastyczności, ale prostota / niezawodność / łatwość konserwacji / wydajność / cokolwiek jest ważniejsze.
  • Jeśli nie wiesz, kod do interfejsu. Wymagania się zmieniają.
Telastyn
źródło
12
Prawie dałem ci -1 za „nie modyfikuj swojego projektu tylko dla testowalności”. Aby uzyskać testowalność, IMHO jest jednym z najlepszych powodów zmiany projektu! Ale twoje inne punkty są w porządku.
Doc Brown
5
@docBrown - (i inne) Różnię się tutaj od wielu, a może nawet większości. ~ 95% czasu, dzięki czemu rzeczy można przetestować, czyni je elastycznymi lub rozszerzalnymi. Powinieneś mieć to w swoim projekcie (lub dodać to do swojego projektu) przez większość czasu - cholerność testowania. Pozostałe ~ 5% albo ma dziwne wymagania, które uniemożliwiają tego rodzaju elastyczność na korzyść czegoś innego, albo jest tak niezbędna / niezmienna, że ​​dowiesz się dość szybko, jeśli zostanie zepsuta, nawet bez dedykowanych testów.
Telastyn
4
może być pewna, ale twoje pierwsze stwierdzenie może być zbyt źle zinterpretowane jako „zostaw źle zaprojektowany kod, tak jak wtedy, gdy jedyną troską jest testowalność”.
Doc Brown,
1
Dlaczego powiedziałbyś „Stworzenie interfejsu, który będzie miał tylko jednego spadkobiercę, jest bezużyteczne”. Interfejs może działać jako umowa.
kaptan
2
@kaptan - ponieważ konkretny obiekt może działać równie dobrze jak umowa. Tworzenie dwóch oznacza po prostu, że musisz napisać kod dwa razy (i zmodyfikować kod dwa razy, gdy się zmieni). To prawda, że ​​zdecydowana większość interfejsów będzie miała wiele implementacji (nietestowych) lub w razie potrzeby trzeba się przygotować na taką ewentualność. Ale w tym rzadkim przypadku, gdy wszystko, czego potrzebujesz, to zwykły zapieczętowany przedmiot, po prostu użyj go bezpośrednio.
Telastyn
12

Czy mamy rację, unikając próby zaprogramowania interfejsu?

Chociaż przewaga kodowania na interfejsie jest dość oczywista, powinieneś zadać sobie pytanie, co dokładnie zyskasz, nie robiąc tego.

Następne pytanie brzmi: czy odpowiedzialność za wybór implementacji należy do klasy, czy nie? Może tak być lub nie i powinieneś działać odpowiednio.

Ostatecznie zawsze istnieje możliwość, że jakieś głupie ograniczenia wyjdą z nieba. Możesz uruchomić ten sam fragment kodu jednocześnie, a możliwość wprowadzenia implementacji może pomóc w synchronizacji. Lub po profilowaniu aplikacji możesz odkryć, że chcesz mieć inną strategię alokacji niż zwykłe tworzenie instancji. Albo pojawiają się problemy przekrojowe i nie masz pod ręką wsparcia AOP. Kto wie?

YAGNI sugeruje, że podczas pisania kodu ważne jest, aby nie dodawać nic zbędnego. Jedną z rzeczy, które programiści dodają do swojego kodu, nawet nie zdając sobie z tego sprawy, są niepotrzebne założenia. Na przykład „ta metoda może się przydać” lub „to się nigdy nie zmieni”. Oba dodają bałaganu do twojego projektu.

back2dos
źródło
2
+1 za cytowanie tutaj YAGNI jako argumentu dla DI, a nie przeciwko niemu.
Doc Brown,
4
@DocBrown: Biorąc pod uwagę, że można tutaj użyć YAGNI, aby uzasadnić również nieużywanie DI. Myślę, że to raczej argument przeciwko temu, że YAGNI jest użytecznym środkiem do wszystkiego.
Steven Evers,
1
+1 za zbędne założenia zawarte w YAGNI, które kilka razy uderzyły nas w głowę
Izkata
@ SteveEvers: Myślę, że użycie YAGNI do uzasadnienia ignorowania zasady inwersji zależności wyklucza brak zrozumienia YAGNI lub DIP, a może nawet niektóre bardziej fundamentalne zasady programowania i wnioskowania. Powinieneś zignorować DIP tylko w razie potrzeby - okoliczność, w której YAGNI po prostu nie ma zastosowania ze względu na swój charakter.
back2dos
@ back2dos: Przypuszczenie, że DI jest przydatne ze względu na „być może wymagania” i „kto wie?” jest właśnie tokiem myślenia, że ​​YAGNI ma walczyć, zamiast tego używasz go jako uzasadnienia dla dodatkowego oprzyrządowania i infrastruktury.
Steven Evers,
7

To zależy od różnych parametrów, ale oto kilka powodów, dla których nadal możesz chcieć używać DI:

  • Wierzę, że testowanie to całkiem dobry powód sam w sobie
  • klasy wymagane przez obiekt mogą być używane w innych miejscach - jeśli je wstrzykniesz, możesz połączyć zasoby (na przykład, jeśli klasy są wątkami lub połączeniami z bazą danych), nie chcesz, aby każda z twoich klas tworzyła nowe znajomości)
  • jeśli zainicjowanie tych innych obiektów może się nie powieść, kod powyżej stosu prawdopodobnie lepiej poradzi sobie z problemami niż twoja klasa, co prawdopodobnie po prostu przekaże błędy

Podsumowując: prawdopodobnie w takim przypadku możesz żyć bez DI, ale są szanse, że użycie DI skończy się na czystszym kodzie.

assylias
źródło
3

Kiedy projektujesz program lub funkcję, częścią procesu myślowego powinno być to, jak zamierzasz ją przetestować. Wstrzykiwanie zależności jest jednym z narzędzi testujących i powinno być traktowane jako część zestawu.

Dodatkowe zalety wstrzykiwania zależności i używania interfejsów zamiast klas są również korzystne, gdy aplikacja jest rozszerzana, ale można je również później łatwo i bezpiecznie zainstalować w kodzie źródłowym za pomocą funkcji wyszukiwania i zamiany. - nawet przy bardzo dużych projektach.

Michael Shaw
źródło
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Wiele odpowiedzi na to pytanie można przedyskutować jako „możesz go potrzebować, ponieważ ...” jako YAGNI, jeśli nie chcesz robić nonszalancji.

Jeśli zastanawiasz się, jakie powody poza jednostkami wymagającymi programowania do interfejsu

Tak , zależne od iniekcji warto poza testem jednostkowym, jeśli potrzebujesz odwrócenia kontroli . Na przykład, jeśli implementacja jednego modułu wymaga innego modułu, który nie jest dostępny w jego warstwie.

Przykład: jeśli masz moduły gui=> businesslayer=> driver=> common.

W tym scenariuszu businesslayermożna użyć, driverale drivernie można użyć businesslayer.

Jeśli driverpotrzebujesz funkcji wyższego poziomu, możesz zaimplementować tę funkcję, w businesslayerktórej implementuje interfejs w commonwarstwie. driverwystarczy znać interfejs w commonwarstwie.

k3b
źródło
1

Tak, jesteś - po pierwsze, zapomnij o testowaniu jednostkowym jako przyczynie zaprojektowania kodu wokół narzędzi do testowania jednostkowego, nigdy nie jest dobrym pomysłem zginanie projektu kodu w celu dopasowania go do sztucznego ograniczenia. Jeśli twoje narzędzia zmuszają cię do tego, zdobądź lepsze narzędzia (np. Microsoft Fake / Moles, które pozwalają na wiele więcej opcji tworzenia fałszywych obiektów).

Na przykład, czy podzieliłbyś swoje klasy na tylko publiczne metody tylko dlatego, że narzędzia testowe nie działają z metodami prywatnymi? (Wiem, że panuje mądrość udawania, że ​​nie trzeba testować prywatnych metod, ale czuję, że jest to reakcja na trudność w wykonaniu obecnych narzędzi, a nie prawdziwa reakcja na brak konieczności testowania prywatnych).,

W sumie sprowadza się to do tego, jakim jesteś TDDerem - „mockist”, jak to opisuje Fowler , musi zmienić kod, aby pasował do narzędzi, których używają, podczas gdy „klasyczni” testerzy tworzą testy, które są bardziej zintegrowane z naturą (tj. przetestuj klasę jako jednostkę, a nie każdą metodę), więc potrzeba interfejsów jest mniejsza, zwłaszcza jeśli używasz narzędzi, które mogą kpić z konkretnych klas.

gbjbaanb
źródło
3
Nie sądzę, że powszechna mądrość, że prywatne metody nie powinny być testowane, ma coś wspólnego z trudnością ich testowania. Jeśli w metodzie prywatnej wystąpił błąd, ale nie wpłynął on na zachowanie obiektu, to nie wpłynąłby na nic. Jeśli w metodzie prywatnej wystąpił błąd, który wpłynął na zachowanie obiektu, oznacza to, że test nie powiódł się lub nie został przeprowadzony co najmniej na jednej z publicznych metod obiektu.
Michael Shaw,
Sprowadza się to do tego, czy testujesz zachowanie lub stan. Oczywiście metody prywatne muszą zostać w jakiś sposób przetestowane, ale jeśli jesteś klasyczną osobą TDD, przetestujesz je, testując klasę „jako całość”, narzędzia testowe, z których korzysta większość ludzi, skupiają się na testowaniu każdej metody indywidualnie ... i chodzi mi o to, że te ostatnie faktycznie nie testują poprawnie, ponieważ tracą szansę na wykonanie pełnego testu. Zgadzam się z tobą, starając się podkreślić, jak TDD zostało obalone przez to, jak niektóre (większość?) Ludzi dzisiaj testuje jednostki.
gbjbaanb
1

Wstrzykiwanie zależności sprawia również, że wyraźniej jest, że obiekt HAS jest zależny.

Gdy ktoś inny użyje twojego obiektu naiwnie (bez patrzenia na konstruktor), przekona się, że będzie musiał skonfigurować usługi, połączenia itp. Nie było to oczywiste, ponieważ nie musiał przekazywać ich do konstruktora . Przekazywanie zależności sprawia, że ​​jest bardziej zrozumiałe, co jest potrzebne.

Potencjalnie ukrywasz również fakt, że Twoje klasy mogą naruszać SRP. Jeśli przekazujesz wiele zależności (więcej niż 3), twoja klasa może robić zbyt wiele i powinna zostać zrefaktoryzowana. Ale jeśli tworzysz je w konstruktorze, ten zapach kodu zostanie ukryty.

Schleis
źródło
0

Zgadzam się z oboma obozami tutaj i moim zdaniem sprawa jest nadal otwarta do debaty. YAGNI wymaga, abym nie próbował zmieniać mojego kodu, aby pasował do przypuszczeń. Testowanie jednostkowe nie jest tutaj problemem, ponieważ mocno wierzę, że zależność nigdy się nie zmieni. Ale gdybym początkowo testował Jednostki, i tak nigdy bym tego nie osiągnął. Jakbym ostatecznie wśliznął się do DI.

Pozwól, że zaoferuję inną alternatywę dla mojego scenariusza

Dzięki wzorowi fabrycznemu mogę zlokalizować zależności w jednym miejscu. Podczas testowania mogę zmienić implementację w oparciu o kontekst i to wystarczy. Nie jest to sprzeczne z YAGNI ze względu na zalety abstrakcji kilku konstrukcji klasowych.

Seph
źródło