Testowanie jednostkowe na przykładowym kodzie

1

Będzie dłuższy post, kod będzie w pseudokodzie przypominającym Javę.

Mamy taką metodę pomocniczą w zewnętrznej bibliotece:

public <T, Client> T doWithOptionalBackupConnection(Client mainClient, Client backupClient, Function<Client, T> action){
    try {
        return action.apply(mainClient);
    } catch(Exception e){
        logger.log(e);
    }

    return action.apply(backupClient)
}

Czyli ta metoda próbuje wykonać dowolną lambdę, najpierw przy pomocy głównego połączenia, a jeżeli cokolwiek się popsuje, to próbuje jeszcze raz przy pomocy zapasowego. Jak drugie podejście się nie uda, to po prostu propaguje wyjątek i tyle.

Treść tej metody jest tak naprawdę nieistotna. Liczy się tylko to, że ona próbuje jakoś załapać wyjątek i obsłużyć, ale na potrzeby dalszej dyskusji przyjmujemy, że nie znamy ciała tej metody, jest ona w zewnętrznym komponencie, który dodatkowo wymaga konfigurowania, połączenia z bazą, stanu i takich tam. Równie dobrze może to być jakieś bardzo drogie wywołanie sieciowe, bez różnicy. Ważny jest tylko kontrakt, że jak lambda wywali się wyjątkiem przy pierwszym kliencie, to próbujemy to odpalić z drugim klientem.

Teraz chcemy użyć tej metody na przykład z czymś takim:

interface Client {
    String getData(String parameter);
}

public String doWork(Client mainClient, Client backupClient){
    String first = utilInstance.doWithOptionalBackupConnection(mainClient, backupClient, client -> client.getData("First"));
    String second = utilInstance.doWithOptionalBackupConnection(mainClient, backupClient, client -> client.getData("Second"));
    String third = utilInstance.doWithOptionalBackupConnection(mainClient, backupClient, client -> client.getData("Third"));
    return first + second + third;
}

Czyli odpalamy metodę pomocniczą trzy razy, za każdym razem przekazując inną lambdę.

Teraz chcemy to przetestować, ale nie chcemy używać prawdziwego doWithOptionalBackupConnection (bo on jest w zewnętrznym pakiecie, jest on ciężki, wolny, nie znamy jego bebechów i takie tam). Czyli go mockujemy w jakikolwiek sposób i gotowe. Od razu zaznaczę, że oczywiście mamy też testy end to end, ale w tym temacie interesują mnie tylko testy jednostkowe.

Kod działa, testy na zielono, ale mija trochę czasu i zaczynamy narzekać, że te wszystkie operacje wykonują się synchronicznie i po kolei. Chcemy to załatwić asynchronicznie, więc zmieniamy naszego klienta i metodę go wołającą:

interface Client {
    CompletableFuture<String> getData(String parameter);
}

public String doWork(Client mainClient, Client backupClient){
    CompletableFuture<String> first = utilInstance.doWithOptionalBackupConnection(mainClient, backupClient, client -> client.getData("First"));
    CompletableFuture<String> second = utilInstance.doWithOptionalBackupConnection(mainClient, backupClient, client -> client.getData("Second"));
    CompletableFuture<String> third = utilInstance.doWithOptionalBackupConnection(mainClient, backupClient, client -> client.getData("Third"));
    return first.WaitForResult() + second.WaitForResult() + third.WaitForResult();
}

Czyli zmieniliśmy klienta tak, żeby zwracał jakąś promesę, następnie po wywołaniu metody doWithOptionalBackupConnection zapisujemy ją do zmiennej. Jak mamy trzy takie promesy, to czekamy na nie po kolei. W efekcie operacje lecą asynchronicznie i wszystko jest szybsze.

Odpalamy testy jednostkowe, przechodzą. Następnie wrzucamy kod na testy end to end (albo od razu na produkcję, a co tam ;) ) i niespodzianka — nie działa.

Najpierw proponuję zagadkę — dlaczego to nagle przestało działać?

Milion enterów…

Różnica jest taka, że teraz lambda zwraca promesę, przez co jeżeli główny klient nie zadziała, to i tak nie ma to znaczenia, bo wszystko jest opakowane. Wyjątek zostanie spropagowany dopiero w momencie wołania WaitForResult, w efekcie czego klient pomocniczy nigdy nie zostanie wykorzystany i cały mechanizm traci sens.

I teraz pytanie właściwe — jak od samego początku napisalibyście test jednostkowy, żeby w razie czego wyłapał taki błąd?

Od razu dodam, że ja wiem, jak to przetestować jednostkowo, żeby wyłapać taki problem, ale nie chcę sugerować rozwiązania.

1

A nie jest tak, ze podstawowy błąd tkwi tutaj w fakcie, że zakładasz w jakimś niejawnym kontrakcie, że ten Client zwróci wynik, albo walnie wyjątkiem, a później ten niejawny kontrakt łamiesz wstawiając klienta, który wyjątkiem nie ma szansy rzucić?

--edit
A odnośnie testów, to należałoby chyba sprawdzić klienta, czy jego zachowanie jest zgodne z oczekiwaniami, czyli czy zwraca oczekiwany wynik, albo rzuci spodziewanym wyjątkiem.

0

Czy chodzi o to, że brakowało test case w którym zmockowany główny klient na getData rzucał wyjątkiem? Wtedy powinniśmy zweryfikować, czy zawołana została ta sama metoda drugiego klienta.

Edit: Coś mi się to rozwiązanie wydaje za oczywiste, no ale ok, zobaczymy.

0

Ja bym powiedział, że mockowany powinien być też obiekt typu Client, a nie tylko doWithOptionalBackupConnection. I ten client powinien rzucać jakieś wyjątki.

Z drugiej strony, po co duplikować testy integracyjne/automatyczne w unitach :)

1

Napisałbym test doWork_WhenFirstClientFails_ThenBackupClientIsUsed, w którym mock pierwszego klienta rzuca wyjątkiem, i sprawdził, czy dostajemy wynik z drugiego mocka.

0

@maszrum: @twoj_stary_pijany @somekind Wasze podejście opiera się na znajomości bebechów doWithOptionalBackupConnection, a jak napisałem wyżej, implementacja jest dla nas nieistotna (a potencjalnie nawet nieznana). Dla ustalenia uwagi możemy przyjąć, że implementacja została zmieniona i teraz wygląda tak:

public <T, Client> T doWithOptionalBackupConnection(Client mainClient, Client backupClient, Function<Client, T> action){
    if(rand()%2 == 0){
       // First main, then backup
       return doInternal(mainClient, backupClient, action);
    }else {
       // First backup, then main
       return (doInternal, backupClient, mainClient, action);
    }
}

private <T, Client> T doInternal(Client mainClient, Client backupClient, Function<Client, T> action){
    try {
        return action.apply(mainClient);
    } catch(Exception e){
        logger.log(e);
    }

    return action.apply(backupClient)
}

Ponadto, jak już napisałem wcześniej, nie możemy używać prawdziwego doWithOptionalBackupConnection w testach jednostkowych, bo ta metoda jest szalenie droga, strzela po bazach danych i takie tam. Jedyne, co możemy zrobić, to ją wymockować.

No i jest jeszcze ważniejszy problem z Waszym podejściem — jeżeli wymockujecie Client tak, że on rzuci wyjątek, to może i przetestujecie wszystko, ale Wasze testy w ogóle nie wyłapią błędu w zmienionej implementacji! Tu nie chodzi o to, żeby sobie przetestować mocki na zielono, tylko żeby mieć test, który wywali się na czerwono, gdy zmieniliśmy klienta z synchronicznego na asynchronicznego. O ile dobrze rozumiem Wasze podejście, to właśnie u Was wszystko będzie na zielono, a potem na produkcji będzie klops.

4

Jakim cudem chcesz przetestować doWithOptionalBackupConnection, skoro jako warunki brzegowe zakładasz, że nie można tej funkcji przetestować.

Okej, możesz sobie testować mocka doWithOptionalBackupConnection, życzę dobrej zabawy, ewentualnie upewnisz się, że twój mock działa dobrze, ale... jaki ma to sens?

2

Testy jednostkowe tego nie wyłapią. Metoda doWithOptionalBackupConnection z zewnętrznej biblioteki ma jakiś tam niepisany (a może pisany, nie wiem) kontrakt co do przyjmowanych argumentów. To albo piszesz unit test, który sprawdza czy wrzucasz dobre argumenty do tej metody, zgodnie z kontraktem albo piszesz test integracyjny z działającą metodą doWithOptionalBackupConnection.

0
Tyvrel napisał(a):

Jakim cudem chcesz przetestować doWithOptionalBackupConnection, skoro jako warunki brzegowe zakładasz, że nie można tej funkcji przetestować.

Bardzo dobre rozumowanie. Odpowiedź brzmi — nie chcę. Ta metoda przychodzi z zewnątrz i jej nie testuję, tak samo jak nie testuję biblioteki standardowej.

Tyvrel napisał(a):

Okej, możesz sobie testować mocka doWithOptionalBackupConnection, życzę dobrej zabawy, ewentualnie upewnisz się, że twój mock działa dobrze, ale... jaki ma to sens?

Dokładnie tak, to nie ma sensu. O to się rozchodzi w temacie, żeby zastanowić się, jaki test jednostkowy ma sens i zadziała. Jednocześnie ludzie wyżej proponowali de facto testowanie mocka, więc najwyraźniej ktoś tak robi.

2

doWithOptionalBackupConnection Ma działać tak, że podajesz 2 różne suppliery i lambdę, która określa co należy na nich zrobić. Oczekujesz, że jeżeli podczas wykonywania jakiejś operacji na pierwszym zostanie rzucony wyjątek, to podjęta zostanie próba wykonania tej samej operacji na drugim.
co stoi na przeszkodzie napisać taki test:
doWithOptionalBackupConnection(mockedCrashingClient, mockedSuccessClient, o-> o.getData()) + kilka dodatkowych warunków gdzie pierwszy klient jest OK, drugi nie itp.

Problem w tym, że ta metoda wymaga, żeby rezultatem akcji były dane, albo wyjątek co nie jest oczywiste patrząc na jej sygnaturę, co może prowadzić do błędu, gdy ktoś, np. jak w twoim przypadku z Future zwróci z lambdy zawsze sukces, albo zwyczajnie nie rzuci wyjątkiem. Czy to oznacza, że doWithOptionalBackupConnection działa nieprawidłowo? No nie, bo robi dokładnie to czego się od niej oczekuje. Błędnie działa fragment, w którym jej użyto.
co gdyby np. ta metoda miała taką sygnaturę:
<CompletableFuture<T>, Client> T doWithOptionalBackupConnection(Client mainClient, Client backupClient, Function<Client, T> action)

W obecnym kształcie nie ma za bardzo możliwości przetestowania tego kodu, bo metoda działa prawidłowo, a jak ktoś jej użyje, to będzie testować swój kod, a nie kod tej metody. Sytuacja jest trochę analogiczna do takiej (java):

Map<MutableWhatever, String> map = new HashMap<>();
MutableWhatever key = new MutableWhatever();
map.put(key, "blabla")
key.setWhatever("other whatever");


map.get(key).length(); //NPE

Co jest zrąbane, implementacja HashMap, czy jej użycie powyżej?

0

Okej, to w takim razie napiszę, o co w tym chodzi. Zasadniczo @piotrpo od razu podał przyczynę, ale na szczęście były też inne propozycje, więc wywiązała się dyskusja :)

Problemem jest to, że w tym kodzie łamiemy LSP. Metoda doWithOptionalBackupConnection ma kontrakt (o którym explicite napisałem na samym początku), że drugi klient jest użyty tylko w przypadku, gdy pierwszy rzuci wyjątkiem. Trudność w tym, że właściciel metody doWithOptionalBackupConnection nie może zweryfikować tego kontraktu automatycznie, bo ani kompilator tego nie gwarantuje, ani system typów, ani nawet nie da się napisać testu jednostkowego po stronie właściciela, bo tam nie wiadomo, jaka lambda będzie podana w parametrze.

Wobec tego ten kontrakt musi przetestować wołający. Czyli w naszym kodzie musimy upewnić się, że przekazywana lambda (a w efekcie implementacja interfejsu Client) działa tak, jak kontrakt wymaga, czyli rzuca wyjątkiem w przypadku błędu (zamiast zwracać promesę z ukrytym wyjątkiem). Gdybyśmy mieli taki test od samego początku, to w trakcie zmieniania klienta na asynchroniczny odpowiednik byłoby widać, że test nie przechodzi i nie da się go naprawić. Programista powinien wtedy się zatrzymać i zastanowić, czy błąd jest w teście, czy może w implementacji (i dojść do wniosku, że to drugie).

Jakikolwiek test jednostkowy mockujący klienta nie ma sensu, bo zmockowany klient będzie działał tak, jak tego sobie życzymy, ale to prawdziwy klient jest skopany. Mockowanie klienta prowadzi do testowania mocka.

Jeszcze odniosę się do tego:

piotrpo napisał(a):

co stoi na przeszkodzie napisać taki test:
doWithOptionalBackupConnection(mockedCrashingClient, mockedSuccessClient, o-> o.getData()) + kilka dodatkowych warunków gdzie pierwszy klient jest OK, drugi nie itp.

To nie jest okej, bo tu piszemy test pod implementację doWithOptionalBackupConnection, a tego robić nie chcemy (bo implementacja może mieć chociażby random, jak pokazałem wyżej). No i mockedCrashingClient będzie po prostu zgodny z kontraktem (bo rzuci wyjątkiem) i w efekcie lambda też będzie zgodna z kontraktem, czyli znowu testujesz mocka.

Co więcej, jakiekolwiek jednostkowe testowanie doWithOptionalBackupConnection jest zbędne i bez sensu. Czy testujemy Math.max? To dlaczego testujemy coś, co przychodzi z zewnętrznej biblioteki? Ten kod działa i nie ma powodu, żeby go dodatkowo testować.

0

Czytam i nie rozumiem gdzie jest problem. Czemu nie zrobić tak, że action zwraca inne wyniki w zależności jaki to jest client i na tej podstawie sprawdzić w asercjach jaki jest wynik?

0

@Afish:

To nie jest okej, bo tu piszemy test pod implementację doWithOptionalBackupConnection, a tego robić nie chcemy (bo implementacja może mieć chociażby random, jak pokazałem wyżej). No i mockedCrashingClient będzie po prostu zgodny z kontraktem (bo rzuci wyjątkiem) i w efekcie lambda też będzie zgodna z kontraktem, czyli znowu testujesz mocka.

Nie zgadzam się. To nie jest ani testowanie mocka, ani testowanie pod implementację. Załóżmy, że nieco staroświecko przerobimy sygnaturę metody na:

/**
Applies action to first client. If exception is thrown, it will be internally intercepted and action will be applied to second client
**/
public <T, Client> T doWithOptionalBackupConnection(Client mainClient, Client backupClient, Function<Client, T> action)

Moim skromnym zdaniem, to co zaproponowałem testuje dokładnie to, metoda obiecuje, że robi. Rola mocka jest dokładnie taka jak powinna być - zapewnia, że warunki testu zawsze będą identyczne.
Można by również spróbować zrobić to czytelniej i bardziej współcześnie,:

interface Action<T>{
boolean isSuccessful();
Optional<T> getResult();
}
public <T, Client> T doWithOptionalBackupConnection(Client mainClient, Client backupClient, Function<Client, Action<T>> action)
1
Afish napisał(a):

@maszrum: @twoj_stary_pijany @somekind Wasze podejście opiera się na znajomości bebechów doWithOptionalBackupConnection

Moje podejście opiera się na tym, że widzę to: public String doWork(Client mainClient, Client backupClient) i to testuję. Żadnego doWithOptionalBackupConnection z mojego punktu widzenia tam nie ma.

Ponadto, jak już napisałem wcześniej, nie możemy używać prawdziwego doWithOptionalBackupConnection w testach jednostkowych, bo ta metoda jest szalenie droga, strzela po bazach danych i takie tam. Jedyne, co możemy zrobić, to ją wymockować.

W sensie ma zewnętrzne zależności inne niż Clienty, których nie da się podmienić?
No to tak, wtedy ten kod jest po prostu nietestowalny jednostkowo.

O ile dobrze rozumiem Wasze podejście, to właśnie u Was wszystko będzie na zielono, a potem na produkcji będzie klops.

Jestem prawie pewien, że testy sprawdzające, czy klient został wywołany zapobiegną tej niespodziance na produkcji.

0
piotrpo napisał(a):

Nie zgadzam się. To nie jest ani testowanie mocka, ani testowanie pod implementację. Załóżmy, że nieco staroświecko przerobimy sygnaturę metody na:

/**
Applies action to first client. If exception is thrown, it will be internally intercepted and action will be applied to second client
**/
public <T, Client> T doWithOptionalBackupConnection(Client mainClient, Client backupClient, Function<Client, T> action)

Hmm, nie widzę tutaj zmiany względem mojej sygnatury. Chodzi Ci o komentarz?

piotrpo napisał(a):

Moim skromnym zdaniem, to co zaproponowałem testuje dokładnie to, metoda obiecuje, że robi. Rola mocka jest dokładnie taka jak powinna być - zapewnia, że warunki testu zawsze będą identyczne.

No tak, ale jak wymockujesz klienta, to właśnie nie wykryjesz problemu. Jeżeli jest inaczej, to najlepiej pokaż konkretny kod.

somekind napisał(a):

Moje podejście opiera się na tym, że widzę to: public String doWork(Client mainClient, Client backupClient) i to testuję. Żadnego doWithOptionalBackupConnection z mojego punktu widzenia tam nie ma.

Tak, ale chcesz mockować klienta, więc nie sprawdzisz tym samym kontraktu.

somekind napisał(a):

W sensie ma zewnętrzne zależności inne niż Clienty, których nie da się podmienić?

No na przykład. Powody nie są istotne, jedynie chodzi o takie ustalenie kontekstu, żebyś w teście musiał wymockować doWithOptionalBackupConnection, a konkretne powody nie są istotne. To może być random, promieniowanie kosmiczne, brak licencji na oprogramowanie, cokolwiek.

somekind napisał(a):

Jestem prawie pewien, że testy sprawdzające, czy klient został wywołany zapobiegną tej niespodziance na produkcji.

To z chęcią zobaczę ten kod, jak mockujesz klienta i unikasz tego problemu.

0

Wobec tego ten kontrakt musi przetestować wołający.

Generalnie sztuka mówi, że w przypadku unitów producent powinien zrobić testy po swojej stronie, konsument po swojej stronie, a także konsument może zrobić testy na bibliotece producenta, żeby wiedzieć kiedy coś wybuchnie bo kontrakt się zmienił po podbiciu wersji.

Co do testów integracyjnych/automatycznych większość osób zgadza się z tym, że można testować całość, ale to zostawmy.

Skoro promesy nie były przetestowane po stronie producenta to moim zdaniem można zakładać, że taki kontrakt jest fałszywy i nigdy producent nie deklarował kontraktu dla promes, bez względu na to co mówią typy.

Jakikolwiek test jednostkowy mockujący klienta nie ma sensu, bo zmockowany klient będzie działał tak, jak tego sobie życzymy, ale to prawdziwy klient jest skopany. Mockowanie klienta prowadzi do testowania mocka.

Tak jak wspomniał @piotrpo i @somekind. Skoro testuję funkcję B doWork(A a, B b, A -> B) to mogę sobie wrzucić dowolne argumenty, czy to mocki czy to cokolwiek i kontrakt powinien działać. Skoro doWork ma zależność na jakiś ciężki serwis to być może ta funkcja powinna wyglądać tak: B doWork(Service service, A a, B b, A -> B) i wtedy mogę dodatkowo zamockować sobie jeszcze ten serwis albo wrzucić jakąś prostą implementację. Wtedy będę miał unit test i faktycznie będę testował wyłącznie logikę doWork, a nie jakieś zewnętrzne biblioteki.

Nie bardzo rozumiem jak można jednocześnie traktować doWithOptionalBackupConnection jako black box oraz zakładać, że istnieje taki kontrakt, że jeżeli pierwszy klient rzuci wyjątkiem to wywoływany jest drugi klient. Przecież czynimy założenie o implementacji tej funkcji.

0
twoj_stary_pijany napisał(a):

Nie bardzo rozumiem jak można jednocześnie traktować doWithOptionalBackupConnection jako black box oraz zakładać, że istnieje taki kontrakt, że jeżeli pierwszy klient rzuci wyjątkiem to wywoływany jest drugi klient. Przecież czynimy założenie o implementacji tej funkcji.

Normalnie, na tym się opiera całe LSP. Masz wyspecyfikowany kontrakt od autora i to wszystko, szczegóły implementacyjne są nieistotne.

2
Afish napisał(a):

Czyli zmieniliśmy klienta tak, żeby zwracał jakąś promesę, następnie po wywołaniu metody doWithOptionalBackupConnection zapisujemy ją do zmiennej. Jak mamy trzy takie promesy, to czekamy na nie po kolei. W efekcie operacje lecą asynchronicznie i wszystko jest szybsze.

Odpalamy testy jednostkowe, przechodzą. Następnie wrzucamy kod na testy end to end (albo od razu na produkcję, a co tam ;) ) i niespodzianka — nie działa.

Musiałeś nie napisać testu pod case w którym client.getData() rzuca wyjątek i tyle. Niekompletne testy, moim zdaniem.

0
Afish napisał(a):

Tak, ale chcesz mockować klienta, więc nie sprawdzisz tym samym kontraktu.

Chcę mockować klienta, bo jest zewnętrzną zależnością, a w teście jednostkowym interesuje mnie sprawdzenie poprawności mojego algorytmu.

To z chęcią zobaczę ten kod, jak mockujesz klienta i unikasz tego problemu.

Nie ma sprawy, podeślij CV przekażę do HRu. :P

A tak na poważnie, to chyba nie wątpisz, że w normalnej sytuacji (czyli gdy kod, który testuję nie zależy od niczego zależy tylko od zewnętrznego świata, a nie od dziwnych pakietów), to test taką zmianę wykryje? O ile kompilator w ogóle do etapu testów dopuści.

Natomiast w sytuacji, gdy nie możemy napisać normalnego testu jednostkowego (czyli zamockować wszystkich zewnętrznych zależności), to jak napisałem wcześniej, ten kod jest po prostu nietestowalny jednostkowo.

0
somekind napisał(a):

A tak na poważnie, to chyba nie wątpisz, że w normalnej sytuacji (czyli gdy kod, który testuję nie zależy od niczego zależy tylko od zewnętrznego świata, a nie od dziwnych pakietów), to test taką zmianę wykryje? O ile kompilator w ogóle do etapu testów dopuści.

Heh, coś czuję, że już wiem, gdzie to zmierza. W sumie to ciekawe, że jest to forum programistyczne, ale w tematach ogólnych ludzie niemal nigdy nie pokażą kodu, za to bez problemu piszą, jakiego to oni kodu nie potrafią stworzyć.

Wymaganie jest proste: nie możesz użyć prawdziwego doWithOptionalBackupConnection, tylko musisz go wymockować. Napisałeś wcześniej, że to bankowo da się przetestować mockując Client, więc pokaż ten test i po sprawie.

1

Ja bym to napisał tak: link. Przed zmianami testy mi przechodzą, po zmianach przechodzi tylko 1 z 2.

0
maszrum napisał(a):

Ja bym to napisał tak: link. Przed zmianami testy mi przechodzą, po zmianach przechodzi tylko 1 z 2.

+1 za konkretny kod!

O ile dobrze rozumiem (a jest piątek, to mogę źle czytać kody), to wywołujesz operację third party bez mockowania. O tu i tu:
https://github.com/maszrum/UnitTestingOnSampleCode/blob/master/src/RandomSolution.BeforeChanges/SomeWork.cs#L17
https://github.com/maszrum/UnitTestingOnSampleCode/blob/master/src/RandomSolution.AfterChanges/SomeWork.cs#L17

To teraz wymockuj tę operację (bo jest ciężka, kosztowna, droga, niedostępna i takie tam).

0

A to nie jest tak, że ta kosztowna operacja jest w prawdziwej implementacji Client? No i w ogóle którą metodę mamy przetestować? Bo jeśli testujemy doWork to tę zmockowaną wersję metody third party musielibyśmy jeszcze jakoś wstrzyknąć do doWork.

0
maszrum napisał(a):

A to nie jest tak, że ta kosztowna operacja jest w prawdziwej implementacji Client?

Też. Ale third party przychodzi z zewnętrznej blibioteki i nie chcemy go używać w testach, tylko mockujemy.

No i w ogóle którą metodę mamy przetestować? Bo jeśli testujemy doWork to tę zmockowaną wersję metody third party musielibyśmy jeszcze jakoś wstrzyknąć do doWork.

No to wstrzyknij jakkolwiek

0

Okej, wprowadziłem zmiany. Już nie korzystam z prawdziwej implementacji operacji third party. Wynik testów taki sam: przed zmianami zielone, po zmianach 1/2.

0
maszrum napisał(a):

Okej, wprowadziłem zmiany. Już nie korzystam z prawdziwej implementacji operacji third party. Wynik testów taki sam: przed zmianami zielone, po zmianach 1/2.

I teraz to działa, ma tylko dwie wady:

  1. Wiążesz swój test z zachowaniem mocka. Na pierwszy rzut oka to mocno wygląda na testowanie mocków, nie ma tam ani słowa o tym, że to ma taki sam kontrakt, jak właściwa implementacja. Co więcej, to wprowadza nowe obserwowalne zachownie, a to kiedyś może stać się kontraktem, zgodnie z prawem Hyruma.
  2. Teraz ten test nie przechodzi i jest pytanie, jak to naprawić. Naturalną pokusą jest poprawa mocka third party, ale właśnie nie o to chodzi.

Sensowniejszym w mojej opinii jest test, który sprawdza kontrakt explicite. Czyli po prostu sprawdza, że klient rzuca wyjątek, a nie robi mockowego klienta z mockową implementacją zależności.

0

Ale wiecie że kontraktów się nie da przetestować automatycznie, prawda? :D Jedynie zachowanie, które może być zgodne z kontraktem.

0

@TomRiddle: Dzięki! Bez tego komentarza językowego na pewno nie poszlibyśmy dalej :D

0
Afish napisał(a):

@TomRiddle: Dzięki! Bez tego komentarza językowego na pewno nie poszlibyśmy dalej :D

Językowego?

Czytam ten cały wątek, i z niego wygląda jakbyście chcieli zrobić coś niemożliwe - mianowicie przetestować kontrakt.

0
Afish napisał(a):

Sensowniejszym w mojej opinii jest test, który sprawdza kontrakt explicite. Czyli po prostu sprawdza, że klient rzuca wyjątek, a nie robi mockowego klienta z mockową implementacją zależności.

Nie rozumiem tej części wypowiedzi. W sensie, że testować na prawdziwym kliencie? Czy coś innego?

Generalnie zadanie było takie, żeby napisać testy które wykażą, że zmiana interfejsu Client na wersję asynchroniczną nie jest "kompatybilna" z operacją w jakiejś zewnętrznej bibliotece. I udało mi się napisać kod który właśnie takie coś robi. Przynajmniej tak mi się wydaje.

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