Czy wstrzykiwanie większości zależności jest konieczne dla testowania?

0
Wibowit napisał(a):
YetAnohterone napisał(a):

Załóżmy teraz, że zgodnie z promowanymi przez niektórych "najlepszymi praktykami" znaczna część tych klas będzie wstrzykiwana przez DI.

wstrzykiwanie zależności to nie tylko 'najlepsze praktyki', ale przede wszystkim porządna testowalność.

brak porządnych testów prowadzi do destruktywnych refaktorów, a to prowadzi do unikania refaktorów w ogóle i narastania szajsowatego kodu.

Nie rozumiem, co ma jedno do drugiego?

Wstrzykiwanie zależności nie jest konieczne dla testowania klas, tylko do ich mockowania.

Nie jest prawdą, wbrew niektórym, że testowanie = mockowanie.

Zacytuję Martina Fowlera:

But not all unit testers use solitary unit tests. Indeed when xunit testing began in the 90's we made no attempt to go solitary unless communicating with the collaborators was awkward (such as a remote credit card verification system). We didn't find it difficult to track down the actual fault, even if it caused neighboring tests to fail. So we felt allowing our tests to be sociable didn't lead to problems in practice.

Indeed using sociable unit tests was one of the reasons we were criticized for our use of the term "unit testing". I think that the term "unit testing" is appropriate because these tests are tests of the behavior of a single unit. We write the tests assuming everything other than that unit is working correctly.

As xunit testing became more popular in the 2000's the notion of solitary tests came back, at least for some people. We saw the rise of Mock Objects and frameworks to support mocking. Two schools of xunit testing developed, which I call the classic and mockist styles. One of the differences between the two styles is that mockists insist upon solitary unit tests, while classicists prefer sociable tests. Today I know and respect xunit testers of both styles (personally I've stayed with classic style).

Even a classic tester like myself uses test doubles when there's an awkward collaboration. They are invaluable to remove non-determinism when talking to remote services. Indeed some classicist xunit testers also argue that any collaboration with external resources, such as a database or filesystem, should use doubles. Partly this is due to non-determinism risk, partly due to speed. While I think this is a useful guideline, I don't treat using doubles for external resources as an absolute rule. If talking to the resource is stable and fast enough for you then there's no reason not to do it in your unit tests.

(https://martinfowler.com/bliki/UnitTest.html)

Zatem unikanie mocków jest sensownym podejściem do testowania.

Jest to zgodne z moją intuicją, o czym pisałem już w kilku wcześniejszych wątkach na tym forum. Test, wydaje mi się, winien testować zachowanie kodu, a nie sposób, w jaki jest napisany. Mockowanie zależności wiąże test z obecną architekturą kodu, co czyni go podatnym na ciągłe przepisywania. Taki test jest tez mniej wiarygodny, bo nie wykryje błędów wynikłych z niezrozumienia zalezności. Wadą/zaletą mockowania jest, że wymusza ono "testowalną architekturę". Niektórzy uważają taką architekturę za pożądaną samą z siebie, ale ona ma też wady: powoduje eksplozję liczby klas i linijek w kodzie, co czyni cały projekt trudnym do zrozumienia w całości.

W większości mozna mocków uniknąć. Załóżmy na przykład, że stosujemy imperative borders, functional core. Dzielimy kod na zgrubsza dwie części: kod, który musi mieć efekty uboczne i kod, który nie musi mieć efektów ubocznych, więc nie powinien ich mieć - to będzie logika biznesowa. W odróżnieniu od tzw. "czystej architektury" logika biznesowa nie ma (nawet odwróconej) zależności od db. Np. kontroler (który oczywiście należy do imperative borders) otrzymuje zapytanie, pobiera z db konieczne dane, woła BL, BL w czysto funkcyjny sposób wykonuje obliczenia i zwraca wynik, na jego podstawie kontroler zapisuje nowe dane do DB i zwraca odpowiedź na zapytanie.

Wtedy logika biznesowa (czyli functional core) może, w większości, być napisana nawet po prostu metodami statycznymi. Dzięki brakowi efektów ubocznych testowanie BL jest trywialne: dajemy funkcji z góry wyznaczone argumenty, sprawdzamy, czy wynik się zgadza. Nie potrzeba żadnych mocków.

Z kolei sensem i celem imperative borders jest rozmawianie z zaleznościami i sklejanie ich ze sobą. Db, sieć, jakieś inne RESTowe API, może system plików, itp, no i oczywiście BL. Ten kod, w dużej mierze, jest poprawny wtedy i tylko wtedy, jeśli piszący go dobrze rozumie, jak działają wołane przezeń zależnosci i nie zapomniał o rozmaitych założeniach interfejsów. Dlatego mockowanie zależności nie ma sensu: moje rozumienie zależności będę musiał zakodować w mocku, więc test staje się tautologią ("kod, którego poprawność zależy od mojego rozumienia zależności, jest poprawny jeśli dobrze rozumiem zależności i prawidłowo je zakodowałem w mocku"). Sprawdzić jego poprawność mogę tylko i wyłącznie testami integracyjnymi. O ile to możliwe, nie będę mockował nawet db ani innych webservice'ów ani nic. Ponieważ ten obszar kodu powinien zawierać względnie mało logiki, to testy integracyjne wystarczą do pełnego pokrycia.

Czasami mocki są nieuniknione. Na przykład komunikacja z systemem bankowym, tak jak napisał Fowler, musi być zamockowana, z przyczyn oczywistych. Jeśli jakaś metoda logiki biznesowej wykonuje się bardzo długo, bo jest jakiś skomplikowany algorytm, który nie może krócej działać, no to pewnie też go trzeba zamockowac. Ale mockujemy tylko wtedy, gdy musimy, a nie wszystko.

Wadą/zaletą tego podejścia jest to, że (a) testów będzie mniej, ale wystarczą one do pokrycia całego kodu, (b) nie trzeba wszędzie stosować DI ani robić eksplozji liczby klas i linijek kodu - więc całość jest prostsza (choć niektórzy uznają, że wprost przeciwnie, jest bardziej skomplikowana, bo pojedyncza klasa jest na dwa ekrany, a nie tylko na jeden i nie jest zachowany SRP w jego najbardziej dogmatycznej formie).

Wiem, że to, co napisałem wyżej, jest bardzo kontrowersyjne. Wielu kurczowo trzyma się piramidy testów Mike'a Cohna, przy czym słówko "unit" w sformułowaniu "unit test" rozumieją jako "unit of code", a nie "unit of behavior". Jeśli zalezności nie sa zamockowane, to test wg nich przestaje być już jednostkowy, bo testuje więcej niż jedną jednostkę na raz. Oczekuje się więc, że każda metoda będzie sparowana z testem testującym tę metodę, i każda klasa będzie sparowana ze swoim mockiem. Oczekuje się, że każda klasa będzie miała 100% pokrycia testami jednostkowymi tak zdefiniowanymi, a oprócz tego trzeba będzie jeszcze pisać testy na wyższym poziomie integracji - testy modularne, potem integracyjne, potem end-to-end, wreszcie testy scenariuszowe, gdzie boty klikają po apce. Ludzie, którzy to promują, sami przyznają, że ich testy jednostkowe testują implementację kodu, a nie jego zachowanie - ale uważają, że jest to OK. Im na wyższy poziom integracji w testach wchodzimy, tym bardziej testujemy wymogi biznesowe, a nie sposób, w jaki kod jest napisany.

Oczywistą wadą powyższego podejścia jest absolutnie gigantyczna ilość testów i podobnie gigantyczna ilość czasu poświęconego na ich pisanie. Refaktoryzacja kodu wymaga przepisania testów, toteż refaktoryzacja także jest utrudniona i czyni ona testy jednostkowe mniej użytecznymi (skoro są przepisywane właśnie wtedy, gdy nadchodzi refaktoryzacja). Podnosi się natomisat następujące zalety: Failujące testy od razu wskazują dokładnie miejsce, w którym jest bląd bez żadnego dodatkowego debugowania (docelowo: failuje test przypisany tej konkretnie metodze, w której jest bug, a jeśli metody mają, jak chce Uncle Bob, po 4 linijki max, to błąd jest widoczny natychmiast); Unit testy wykazują, że kod jest poprawny w momencie, w którym go piszemy (jeśli stosujemy TDD), podczsa gdy testy integracyjne wymuszają, by kod dalej był poprawny, gdy go refaktoryzujemy; Takie unit testy są niezwykle szybkie, więc wszystkie unit testy można uruchomić w przeciągu pół sekundy max, więc można co 30 sekund uruchamiać cały zestaw unit testów (ale po co?! skoro takie unit testy z założenia testują wył. jedną metodę, góra klasę, więc nie ma szans, by one sfailowały, gdy pracuję nad inną częścią kodu?! Sfailować może wył. test, który przed chwilą sam napisałem)

Są też podejścia próbujące znaleźć złoty środek pomiędzy tymi dwoma ekstremami. Wielu na przykład nie wyobraża sobie, by jakiekolwiek zalezności zewnętrzne (db, inne webservice'y, itp) nie były zamockowane. Z doświadczenia wiem, że w niektórych miejscach pracy żąda się wręcz, by dać pełne pokrycie testami jednostkowymi mockującymi db i w ogóle wszystkie zależności wykraczające poza danego exeka.

Podsumowując:

  • Nieprawdą jest, że testy jednostkowe i TDD wymagają mockowania zależności (skoro istnieje podejście unikające mockowania);
  • Jeśli nie mockujemy, wówczas DI oraz IoC przestaje być konieczne do testowania;
  • Wydaje mi się, że w takim wypadku DI traci sporo sensu i można ograniczać zakres jego stosowania;
  • Wydaje mi się, że o ile zarówno mockowanie, jak i unikanie mockowania mają swoje wady i zalety, o tyle szala przechyla się zdecydowanie na korzyść unikania mockowania, o ile to możliwe;
  • Wiem, że wielu uważa to, co napisałem wyżej za herezje.

Obecna moja praca zaczyna powoli wymuszać pokrywanie kodu testami (to dobrze, bo dotąd pokrycie było mikroskopijne), ale jednocześnie wymusza podejście bardziej mockujące (to już mi się zdecydowanie mniej podoba). Zatem możliwe, że moje obserwacje, jak działają testy jednostkowe z mockami zmieni moje podejście i za dwa lata nie będę sobie wyobrażał, jak można pisać kod inaczej, niż tylko stosując TDD mockujące każdą zależność. Czas pokaże.

5
YetAnohterone napisał(a):
  • Nieprawdą jest, że testy jednostkowe i TDD wymagają mockowania zależności (skoro istnieje podejście unikające mockowania);\

Tak.

  • Jeśli nie mockujemy, wówczas DI oraz IoC przestaje być konieczne do testowania;

Tak.
Edit: - bardziej IoC przestaje być konieczne do testowania. Normalne DI to w zasadzie standard w testowaniu - całe "given" to w zasadzie DI. I nie ma nic wspólnego z mockowaniem - po prostu konfiguracja obiektów do testowania.

  • Wydaje mi się, że w takim wypadku DI traci sporo sensu i można ograniczać zakres jego stosowania;

DI to jeden z wielu mechanizmow organizacji i ponownego użycia kodu. Jest to niezależne od testowania. Oczywiście, przy testowaniu jest użyteczny - tak jak w wielu innych miejscach.

  • Wydaje mi się, że o ile zarówno mockowanie, jak i unikanie mockowania mają swoje wady i zalety, o tyle szala przechyla się zdecydowanie na korzyść unikania mockowania, o ile to możliwe;

Zalety mockowania są porówywalne z zaletami opaski uciskowej. Jak Ci urwie głowę/rękę/nogę to możesz powstrzymać krwotok. Najlepiej jednak nie mieć takiego problemu.
Czasem korzystam z bibliotek, serwisów itp., które zmuszają mnie do mockowania - normalka. Ale najpierw sprawdzam, czy jednak nie da się czegoś zrobić, żeby tego mockowania nie mieć (odpalić serwis na dockerze, użyć innej biblioteki itp.).

  • Wiem, że wielu uważa to, co napisałem wyżej za herezje.

Tak.

Obecna moja praca zaczyna powoli wymuszać pokrywanie kodu testami (to dobrze, bo dotąd pokrycie było mikroskopijne), ale jednocześnie wymusza podejście bardziej mockujące (to już mi się zdecydowanie mniej podoba). Zatem możliwe, że moje obserwacje, jak działają testy jednostkowe z mockami zmieni moje podejście i za dwa lata nie będę sobie wyobrażał, jak można pisać kod inaczej, niż tylko stosując TDD mockujące każdą zależność. Czas pokaże.

W mojej bańce mocksturbacja to raczej wstydliwa rzecz, którą uprawiało się w młodości. Z wolna wypieramy nawet, że to robiliśmy. Czasem tylko trafi sie na archaiczny kawałek kodu, który nam o tym przypomina.

1

Masz rację mówiąc, że wszystko co jest pure-function nie musi być mockowane i wtedy Dependency Injection nie jest w żaden sposób potrzebne do testów.

Ale nie bierzesz pod uwagę tego, że nie każdy kod da się zrobić pure. Dla przykładu: mamy moduł który jest prawie pure, ale w pewnym momencie chcemy żeby wysłał maila. Jak napisać test pod coś takiego? Tylko wstrzykując zależność w teście (przez konstruktor albo metodę).

3
Riddle napisał(a):

Ale nie bierzesz pod uwagę tego, że nie każdy kod da się zrobić pure. Dla przykładu: mamy moduł który jest prawie pure, ale w pewnym momencie chcemy żeby wysłał maila. Jak napisać test pod coś takiego? Tylko wstrzykując zależność w teście (przez konstruktor albo metodę).

Oczywiście, że nie.
https://github.com/dasniko/testcontainers-mailhog/blob/master/src/test/java/dasniko/smtp/SmtpTest.java

Robiłem takie testy na długo przed testcontainers (prosty smtp server odpalany z javy).

0

Edit: usunąłem całą poprzednią treść posta - @jarekr000000 zauważył że faktycznie nie powiedziałem wszystkie, więc się poprawię:

  • Jeśli masz pure-functions, to faktycznie da się wszystko przetestować bez DI
  • Jeśli nie masz pure-functions, ale masz side-effecty na które da się nałożyć programistyczny interfejs (np. pliki które można po prostu odczytać, bazę z której można querować, widok który można sparsować, api które można odebrać), to wtedy też jaknajbardziej bez DI "da się żyć", i ma to pełne prawo działać (dzięki @jarekr000000 za zwrócenie uwagi).

Ale są też dwa case'y które widzę, gdzie bez DI byłoby trudno, a to są:

  • Case'y z side-effectami, na które bardzo trudno nałożyć interfejs programistyczny np. program który wypluwa jakiś obraz lub dźwięk.
  • Case gdzie chcemy skorzystać z polimorfizmu na potrzeby testowanej klasy np. wzorzec observer, wizytor, ale też cokolwiek z Dependency Inversion (nie injection).
1
Riddle napisał(a):

Ale nie wszystko da się tak zrobić, albo nawet jak się da, to nie wszystko jest sens tak testować - dowolna rzeczy która jest na krawędzi systemu: widok, integracje, baza.

Czemu mi nikt nie powiedział? Cała moja firma tak testuje integracje i bazy (tylko z widokami nie wiemy o co chodzi, pewnie dlatego, że nie mamy widoków).
Będę musiał wytłumaczyć chłopakom, że nie ogarniają.

0
jarekr000000 napisał(a):

Oczywiście, że nie.
https://github.com/dasniko/testcontainers-mailhog/blob/master/src/test/java/dasniko/smtp/SmtpTest.java

Robiłem takie testy na długo przed testcontainers (prosty smtp server odpalany z javy).

No dobrze, tak zrobisz w przypadku gdy docelowo komunikujesz się z usługą do wysyłania e-maili przez SMTP. Tutaj rzeczywiście nie ma problemów, bo to standardowy protokół.

A co jeśli korzystasz z jakiejś usługi do wysyłki maili, która albo w ogóle nie umożliwia komunikacji przez SMTP albo po prostu zdecydowałeś, że integrujesz się przy pomocy ich natywnego API, bo daje większe możliwości.
Nie znajdziesz do niej gotowej usługi do podmianki, a niekoniecznie z testów chciałbyś wysyłać maile, więc wypadałoby to przykryć. Albo pisząc sobie jakiś in-memory odpowiednik i go wstrzykując w teście, albo jak ktoś lubi akrobacje to postawić w oddzielnym procesie jakiś serwerek HTTP i z nim się komunikować korzystając z tej samej implementacji. To już lepiej chyba w takim przypadku zrobić implementację in-memory i ją wstrzyknąć.

1
YetAnohterone napisał(a):
Wibowit napisał(a):
YetAnohterone napisał(a):

Załóżmy teraz, że zgodnie z promowanymi przez niektórych "najlepszymi praktykami" znaczna część tych klas będzie wstrzykiwana przez DI.

wstrzykiwanie zależności to nie tylko 'najlepsze praktyki', ale przede wszystkim porządna testowalność.

brak porządnych testów prowadzi do destruktywnych refaktorów, a to prowadzi do unikania refaktorów w ogóle i narastania szajsowatego kodu.

Nie rozumiem, co ma jedno do drugiego?

Wstrzykiwanie zależności nie jest konieczne dla testowania klas, tylko do ich mockowania.

Nie jest prawdą, wbrew niektórym, że testowanie = mockowanie.

(... dużo tekstu o mockowaniu ...)

Podsumowując:

  • Nieprawdą jest, że testy jednostkowe i TDD wymagają mockowania zależności (skoro istnieje podejście unikające mockowania);
  • Jeśli nie mockujemy, wówczas DI oraz IoC przestaje być konieczne do testowania;
  • Wydaje mi się, że w takim wypadku DI traci sporo sensu i można ograniczać zakres jego stosowania;
  • Wydaje mi się, że o ile zarówno mockowanie, jak i unikanie mockowania mają swoje wady i zalety, o tyle szala przechyla się zdecydowanie na korzyść unikania mockowania, o ile to możliwe;
  • Wiem, że wielu uważa to, co napisałem wyżej za herezje.

Obecna moja praca zaczyna powoli wymuszać pokrywanie kodu testami (to dobrze, bo dotąd pokrycie było mikroskopijne), ale jednocześnie wymusza podejście bardziej mockujące (to już mi się zdecydowanie mniej podoba). Zatem możliwe, że moje obserwacje, jak działają testy jednostkowe z mockami zmieni moje podejście i za dwa lata nie będę sobie wyobrażał, jak można pisać kod inaczej, niż tylko stosując TDD mockujące każdą zależność. Czas pokaże.

rozpisałeś się o tym mockowaniu, a nie o to mi chodziło. ja unikam mocków (w sensie takich jak z mockito) w ogólności. za to czasem robię implementacje (podklasy) testowe klas produkcyjnych.

wstrzykiwanie zależności umożliwia wyniesienie efektów ubocznych poza konstruktor. efekty uboczne w konstruktorze to ogólnie rak, zarówno podczas testowania jak i dziedziczenia, bo wtedy trzeba dbać o to, żeby te efekty uboczne nie skończyły się jakąś katastrofą jeśli źle poustawiamy stan. jeśli konstruktor ogranicza się tylko do bycia punktem wejścia do wstrzykiwania (tzn. tylko i wyłącznie zapisuje wstrzykiwane parametry do pól instancji klasy) to mamy kontrolę nad tym kiedy odpalamy efekty uboczne i czy je odpalamy w ogóle, bo możemy zrobić np. podklasę jakiejś zależności, która omija efekty uboczne w ogóle. jeśli nasz test testuje tylko część stanu klasy, to resztę stanu możemy zastąpić jakimiś nicnierobiącymi zaślepkami.

mockowanie (a'la mockito), czyli nagrywanie zachowania (poprzez ustawianie sekwencji when/return) jest słabe, bo nie nadąża automatycznie za zmianami kontraktu w implementacjach. łatwo przegapić nagrane zachowanie z użyciem starego kontraktu, które powinno być zaktualizowane (potencjalnie razem z kolejnymi testowanymi klasami) i stąd wartość testów opartych o mocki, w kontekście wyłapywania regresji, jest moim zdaniem ogólnie rozczarowująco niska. natomiast testy do nowego kodu (czyli nie w kontekście wyłapywania regresji w istniejącym kodzie, a sprawdzaniu poprawności nowego) można pisać w dowolny sposób i każdy jest dobry jeśli faktycznie sprawdzi poprawność. sztuką jest więc pisanie testów sprawdzających się dobrze w wyłapywaniu regresji, tzn. padających jeśli zmiana kontraktu w jednym miejscu powinna pociągnąć zmianę implementacji w innych miejscach, ale jednocześnie nie padających jeśli wszystko gra (tutaj można nawiązać do mocków z metodami typu verify no more interactions czy innymi bardzo skrupulatnymi sprawdzeniami, które mogą się często sypać i ogólnie dają kiepski zysk z inwestycji).

możliwe, że mamy rozbieżności w terminologii i to powoduje problemy w komunikacji, ale jeśli potrafisz tak projektować aplikację i testy, żeby pokrycie kodu testami było porządne, a testy skutecznie wyłapywały regresje to gratulacje :) o to chodzi.

3
Riddle napisał(a):
  • Jeśli masz pure-functions, to faktycznie da się wszystko przetestować bez DI

jak masz pure-functions to masz wszystkie zależności podane jako argumenty, co w zasadzie jest DI samym w sobie

Czy wstrzykiwanie większości zależności jest konieczne dla testowania?

Jak dajesz radę bez to nie 😀
Natomiast na pewno mogą uprzyjemnić ten proces. Ja stosuję minimalną liczbę mocków ale bez nich pewna logika jest testowana wielokrotnie lub nawet w każdym teście co jest zbędne i niepotrzebnie wydłuża czas testów, a im dłużej się testy wykonują tym mniej nam się je chce puszczać i takie na przykład TDD staje się uporczywe

0
Klaun napisał(a):
jarekr000000 napisał(a):

Oczywiście, że nie.
https://github.com/dasniko/testcontainers-mailhog/blob/master/src/test/java/dasniko/smtp/SmtpTest.java

Robiłem takie testy na długo przed testcontainers (prosty smtp server odpalany z javy).

No dobrze, tak zrobisz w przypadku gdy docelowo komunikujesz się z usługą do wysyłania e-maili przez SMTP. Tutaj rzeczywiście nie ma problemów, bo to standardowy protokół.

A co jeśli korzystasz z jakiejś usługi do wysyłki maili, która albo w ogóle nie umożliwia komunikacji przez SMTP albo po prostu zdecydowałeś, że integrujesz się przy pomocy ich natywnego API, bo daje większe możliwości.
Nie znajdziesz do niej gotowej usługi do podmianki, a niekoniecznie z testów chciałbyś wysyłać maile, więc wypadałoby to przykryć. Albo pisząc sobie jakiś in-memory odpowiednik i go wstrzykując w teście, albo jak ktoś lubi akrobacje to postawić w oddzielnym procesie jakiś serwerek HTTP i z nim się komunikować korzystając z tej samej implementacji. To już lepiej chyba w takim przypadku zrobić implementację in-memory i ją wstrzyknąć.

jeśli chodzi o testowanie zewnętrznych zależności (a więc np. w przypadku mikroserwisów będzie to także inny mikroserwis, nawet z tego samego projektu) to mam mniej więcej taką zasadę:

  • jeśli protokół komunikacji jest super-stabilny (czyli smtp się zalicza, bo jest protokołem końcowym, bez nadbudówek, ale http już nie, bo http jest tylko bazą dla protokołu aplikacyjnego) to stosuję jakieś implementacje testowe (jeśli da się je rozsądnym wysiłkiem stworzyć). stabilność protokołu oznacza, że kontrakt się nie zmienia, więc moje implementacje testowe nie stają się nagle przestarzałe (błędne).
  • jeśli protokół komunikacji nie ma bardzo dobrych gwarancji stabilności lub trudno zrobić jego implementację testową to lepiej nic nie mockować czy stubować (czy jak to tam zwać), ale od razu przejść do testów integracyjnych.

zakładam, że protokół komunikacji między mikroserwisami w typowym projekcie ogólnie nie jest super-stabilny. z praktyki mogę stwierdzić, że wersjonowanie api jest robione ultra rzadko, a nawet jeśli jest robione to i tak niekonsekwentnie (np. może być nawet złamane w ten sposób, że jest wiele wersji danego api, ale i tak potem następują zmiany w konkretnej wersji zamiast robienia nowej).

co do samego smtp (wysyłania mejli) to można np. zrobić tak, że dzielisz przetwarzanie na dwa etapy - przygotowanie mejla i wysłanie go. przygotowanie mejla (czyli temat, ciało, adresatów, itp) możesz przetestować zwyczajnie jednostkowo (tzn. bez martwienia się o jakieś zewnętrzne zależności), a samo wysyłanie (czyli kawałek kodu pokryty testami integracyjnymi) wtedy zmniejszy się do minimum, co zmniejszy potencjał do robienia błędów w takim trudno testowalnym kodzie. podobną strategię można zastosować i do innych problemów tego typu (tzn. z zależnościami zewnętrznymi).

można też zrobić wewnętrzną abstrakcję na zależność zewnętrzną. wtedy w większości testów można używać tej abstrakcji, a dodatkowo trzeba napisać testy integracyjne, które sprawdzają czy ta abstrakcja nadal dobrze działa z najnowszymi wersjami zależności zewnętrzne.

ogólnie możliwości jest wiele, ale najgorszym wyjściem jest mockowanie bezpośrednio api, w którym prawdopobieństwo zmiany kontraktu nie jest znikome.

2
YetAnohterone napisał(a):

Wstrzykiwanie zależności nie jest konieczne dla testowania klas, tylko do ich mockowania.

Do mockowania też nie jest konieczne.

Przydaje się ogólnie do poukładania kodu, podzielenia na mniejsze fragmenty w sensowny sposób, i do udokumentowania tego, co od czego zależy.

Mockowanie zależności wiąże test z obecną architekturą kodu, co czyni go podatnym na ciągłe przepisywania.

Zależy, które zależności mockujesz. Jeśli swój kod, to tak. Jeśli tylko zewnętrzne, to niekoniecznie.

Wadą/zaletą tego podejścia jest to, że (a) testów będzie mniej, ale wystarczą one do pokrycia całego kodu, (b) nie trzeba wszędzie stosować DI ani robić eksplozji liczby klas i linijek kodu - więc całość jest prostsza (choć niektórzy uznają, że wprost przeciwnie, jest bardziej skomplikowana, bo pojedyncza klasa jest na dwa ekrany, a nie tylko na jeden i nie jest zachowany SRP w jego najbardziej dogmatycznej formie).

To, że nie nadużywamy mocków nie oznacza, że musimy pisać w ogólności poryty kod. Dziwna filozofia.

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