Wątek przeniesiony 2023-06-12 18:13 z C# i .NET przez Riddle.

Jak przetestować metodę która zmienia prywatne pole?

2

Test read powinien polegać na tym, że masz jakiś predefiniowany input.file i sprawdzasz, czy to co read wypluło jest zgodne z tym, co według ciebie wypluć powinno.
W przypadku write masz sytuację odwrotną - czyli wrzucasz jakąś strukturę danych do write, tworzy ci się jakiś output.file, i na końcu sprawdzasz, czy ten plik jest zgodny z tym, co być powinno.

No ale teraz wg wcześniejszej logiki @Riddle testujesz prywatną implementację read / write. Bo zapis do pliku to przecież szczegół implementacyjny ;)

I tak, dopuszczam możliwość, że istnieją jakieś bardzo wyjątkowe sytuacje, w których monitorowanie prywatnych pól będzie uzasadnione. Ale są to właśnie wyjątkowe sytuacje, a co do zasady powinno się tego unikać.

A widzisz, i być może tu mamy różnicę poglądów wynikającą z rodzaju oprogramowania jaki piszemy.
Ja piszę oprogramowanie, które jest na ścieżce krytycznej wielu systemów i ma dość wysoko podniesioną poprzeczkę niezawodnościową.
Monitorowanie (metryki) oraz testowanie wewnętrznego stanu systemu (zarówno offline w postaci testów jak i online w postaci asercji działających na produkcji), plus bardzo rygorystyczne założenia wewnętrzne dotyczące budowy tych szczegółów implementacyjnych to podstawa budowy niezawodnych systemów.

Popatrz sobie na to zdjęcie:

screenshot-20230615112111.png

(obraz za wikipedia, Airbus A340)

Dlaczego tu jest taki pierdyliard przełączników, manipulatorów, wskaźników i ekranów?
Przecież interfejs publiczny samolotu jest dużo prostszy:

  • wolant / joystick / pedały do obsługi powierzchni sterowych
  • przepustnica do sterowania ciągiem
  • wajcha do wysuwania / wsuwania klap
  • podstawowa orientacja w przestrzeni: prędkość, pułap, sztuczny horyzont / busola
  • hamulce, podwozie
  • parę drobiazgów aby to wszystko włączyć / wyłączyć

A właściwie to można to nawet bardziej zredukować i zrobić samolot z trybem tylko automatycznym.
Nakręcisz sobie prędkość / wysokość / azymut na AP i lecisz. A lądujesz na ILS kat 3. Dziecko by umiało. :D

Po grzyba Ci wskaźnik ciśnienia płynu hydraulicznego, prędkość wirnika silnika 2, wskazanie ilości paliwa w zbiornikach, czy aż pięć odczytów wysokości?
Po co wskaźnik wysunięcia podwozia? :D

Myk polega na tym, że jak cokolwiek pójdzie nie tak, to musisz mieć możliwość po pierwsze:

  • wiedzieć że coś poszło nie tak, zanim powiedzą Ci o tym testy end-to-end (rozbiłeś się, koniec)
  • wiedzieć również możliwie najdokładniej co nie działa aby móc zareagować adekwatnie i szybko
0
Krolik napisał(a):

Dlaczego tu jest taki pierdyliard przełączników, manipulatorów, wskaźników i ekranów?
Przecież interfejs publiczny samolotu jest dużo prostszy:

Zależy dla kogo.

  • Dla ludzi spoza samolotu albo pasażerów, to tak - to nie jest publiczny interfejs, i żaden pasażer nie powinien nic wiedzieć o tym pierdyliardzie przełączników
  • Ale dla pilotów, to już jest publiczny interfejs, każdy jeden z tych maleńkich przełączników jest publicznym interfejsem tego samolotu.

Więc pytanie, z jakiej perspektywy zadajesz to pytanie.

1
  • Ale dla pilotów, to już jest publiczny interfejs, każdy jeden z tych maleńkich przełączników jest publicznym interfejsem tego samolotu.

Więc pytanie, z jakiej perspektywy zadajesz to pytanie.

Jednak istotą 90% tego interfejsu jest niezawodność / bezpieczeństwo, a nie główny cel biznesowy tj. dolecenie do celu w dobrych warunkach.

Abstrahując od samego lotu (tj produkcji jakbyśmy to nazwali u nas) zauważ też, że raczej nikt tam nie kwestionuje, że offline powinno się testować najmniejszą śrubkę czy łopatkę, nawet jeśli ona jest tylko bardzo prywatnym komponentem innego komponentu.
I jak najbardziej w testach będziesz testował np. takie rzeczy, czy po przyłożeniu odpowiedniej siły nie zmienił się np. kolor / widzialna struktura materiału tejże łopatki. Mimo że od samego koloru nic "biznesowo" nie zależy, to może to być proxy dla uszkodzeń, więc służy jako metoda diagnostyczna. Czyli jest sens testować prywatną metodę, jeśli od jej działania zależeć może działanie większego kawałka i testować nawet zgodność z założeniami wewnętrznymi, które nie mają związku z zewnętrznymi założeniami biznesowymi.

0
Krolik napisał(a):

Test read powinien polegać na tym, że masz jakiś predefiniowany input.file i sprawdzasz, czy to co read wypluło jest zgodne z tym, co według ciebie wypluć powinno.
W przypadku write masz sytuację odwrotną - czyli wrzucasz jakąś strukturę danych do write, tworzy ci się jakiś output.file, i na końcu sprawdzasz, czy ten plik jest zgodny z tym, co być powinno.

No ale teraz wg wcześniejszej logiki @Riddle testujesz prywatną implementację read / write. Bo zapis do pliku to przecież szczegół implementacyjny ;)

Znowu, zależy - co to jest za program.

  • Jeśli program operuje na plikach (np cat, tail, vim, nano, less) to taki odczyt jest interfejsem, i należy go odpowiednio przetestować, i wtedy możesz mieć osobne testy dla read i write
  • Ale jeśli program nie operuje na plikach, a jedynie używa ich wewnętrznie, jako cache, jak np ngrok jest tylko tunelem między dwoma HTTP, a plików używa jako cache, to wtedy już nie możesz takich plików traktować jak interfejs i nie powinieneś o nich wiedzieć.

Istotą jest, po co piszesz jakiś kod:

  • Piszesz jakiś kod, bo ten kod jestem celem samym w sobie - wtedy napisz test pod to.
  • Piszesz kod, jako środek do celu (np hashmapa, jako środek żeby coś znaleźć), wtedy nie pisz testu pod to, bo ten test Ci tylko zaszkodzi.
Krolik napisał(a):
  • Ale dla pilotów, to już jest publiczny interfejs, każdy jeden z tych maleńkich przełączników jest publicznym interfejsem tego samolotu.

Więc pytanie, z jakiej perspektywy zadajesz to pytanie.

Jednak istotą 90% tego interfejsu jest niezawodność / bezpieczeństwo, a nie główny cel biznesowy tj. dolecenie do celu w dobrych warunkach.

Powiedziałbym że oba cele są do osiągnięcia, zarówno niezawodność, bezpieczeństwo jak i cel biznesowy.

I jak najbardziej w testach będziesz testował np. takie rzeczy, czy po przyłożeniu odpowiedniej siły nie zmienił się np. kolor / widzialna struktura materiału tejże łopatki. Mimo że od samego koloru nic "biznesowo" nie zależy, to może to być proxy dla uszkodzeń, więc służy jako metoda diagnostyczna. Czyli jest sens testować prywatną metodę, jeśli od jej działania zależeć może działanie większego kawałka i testować nawet zgodność z założeniami wewnętrznymi, które nie mają związku z zewnętrznymi założeniami biznesowymi.

Tylko wiesz, odchodzimy teraz od tematu już. Bo jeśli mówimy o normalnej aplikacji, takiej o jaką za pewne pytał @bakunet ; to nie. Wtedy testujesz publiczny interfejs, a prywatne elementy zostawiasz w spokoju i nie dodajesz żadnych metody diagnostycznych.

Ale jeśli wejdziemy w temat programu który Ty wyciągnąłeś; to to jest trochę inne pytanie, i możnaby pewnie o nim pogadać w innym wątku. Rozumiem, że w projekcie w którym mówisz, musisz być pewny że aplikacja działa, i żeby się upewnić że ona działa to musisz znać stan wielu małych przełączników i prywatnych elementów. Jeśli jest tak w istocie, że musisz znać ich stan i nie przeszkadza Ci że znajomość tego stanu utrudni Ci refaktor; no to z definicji moim zdaniem to co nazywasz "prywatnymi elementami" w gruncie rzeczy wcale nie jest prywatne - jest publiczne. Musi być zdefiniowane jako publiczne, jeśli znajomość tych elementów jest Ci konieczna do weryfikacji czy aplikacja działa poprawnie czy nie. (i nie mówię tutaj o Javowych słowach private i public, tylko mówię o klasyfikacji oprogramowania language-agnostic).

0
Krolik napisał(a):

Sudoku jest taką łamigłówką, gdzie właśnie łatwo robić bardzo długą listę specyficznych, prostych przypadków, które wiadomo jak powinny działać, ale można nadal kompletnie nie wiedzieć jak rozwiązać problem w sposób ogólny.

Tak.

I moja tezza jest taka, że TDD w żadnym stopniu nie przybliża Cię do rozwiązania ogólnego, a wręcz przeszkadza. Przeszkadza, bo Roy skoncentrował się na jakiś duperelach związanych z obsługą planszy, zamiast ugryźć problem, wymyślić algorytm i na końcu napisać testy.

Tylko widzisz, że całkowicie odbiegamy od tematu wcześniejszego?

No ale niech będzie, odpowiem. Czyli nie podoba Ci się że autor tego przykładu obkodził rzeczy dookoła tego programu, zamiast jakby "iść po złoto" i od razu napisać algorytm. I z tego powodu twierdzisz, że TDD przeszkadza.

No to mam na to dwie odpowiedzi:

  • Po pierwsze, jeśli Ty stosowałbyś TDD, nie koniecznie musiałbyś wziąć tą samą drogę którą wziął autor. Mógłbyś od razu jechać po złoto, i od razu napisać testy pod faktyczny algorytm. To że ten gość tak do tego podszedł, nie znaczy że Ty musiałbyś.
  • Po drugie, TDD daje różne zalety, zależnie od tego od jakiego punktu wychodzisz:
    • Jeśli wiesz co czego dążysz (czyli znasz algorytm na rozwiązywanie sudoku, albo wiesz że napisałbyś taki łatwo), to śmiało, napisz - be my guest, TDD Ci w tym nie przeszkadza
    • Ale może być tak że nie wiesz, jaki jest dokładny algorytm - i wtedy podejścia autora tego postu jest jak najbardziej poprawne, bo pozwala iteratywnie móc wyewoluować taki algorytm, a potem w miarę możliwości go zrefaktorować. Także to jest by the book.
2

Ale jeśli program nie operuje na plikach, a jedynie używa ich wewnętrznie, jako cache, jak np ngrok jest tylko tunelem między dwoma HTTP, a plików używa jako cache, to wtedy już nie możesz takich plików traktować jak interfejs i nie powinieneś o nich wiedzieć.

No z tym się właśnie nie zgadzam. To że jest traktowany wewnętrznie nie oznacza, że dla developerów powinien być tylko czarną skrzynką. Nie, ten plik nadal ma zapewne jakąś strukturę, jest (powinien być) udokumentowany na wewnętrzne potrzeby projektu. A skoro tak, to dobrze mieć testy weryfikujące czy kod faktycznie produkuje odpowiednią strukturę i czy odpowiednio ją czyta.

To że Ty jako pasażer (użytkownik) nie masz wglądu w wewnętrzne bebechy samolotu nie oznacza, że takiego wglądu nie powinni mieć mechanicy / piloci. A testy piszemy z punktu widzenia developera (czyli odpowiednika producenta / serwisu samolotu) a nie użytkownika (pasażera).

0
Krolik napisał(a):

Test read powinien polegać na tym, że masz jakiś predefiniowany input.file i sprawdzasz, czy to co read wypluło jest zgodne z tym, co według ciebie wypluć powinno.
W przypadku write masz sytuację odwrotną - czyli wrzucasz jakąś strukturę danych do write, tworzy ci się jakiś output.file, i na końcu sprawdzasz, czy ten plik jest zgodny z tym, co być powinno.

No ale teraz wg wcześniejszej logiki @Riddle testujesz prywatną implementację read / write. Bo zapis do pliku to przecież szczegół implementacyjny ;)

Zarówno forma, jak i rodzaj pliku może być funkcjonalnością. Czym innym funkcjonalnie jest serializacja do JSONa, a czym innym jest serializacja do XMLa.

A widzisz, i być może tu mamy różnicę poglądów wynikającą z rodzaju oprogramowania jaki piszemy.
Ja piszę oprogramowanie, które jest na ścieżce krytycznej wielu systemów i ma dość wysoko podniesioną poprzeczkę niezawodnościową.
Monitorowanie (metryki) oraz testowanie wewnętrznego stanu systemu (zarówno offline w postaci testów jak i online w postaci asercji działających na produkcji), plus bardzo rygorystyczne założenia wewnętrzne dotyczące budowy tych szczegółów implementacyjnych to podstawa budowy niezawodnych systemów.

Wydaje mi się, że bardzo mocno schodzisz z tematu. Wypowiedziałem się na temat testów jednostkowych, natomiast ty mi tutaj dajesz wykład o monitorowaniu działania oprogramowania. Testy jednostkowe, testy integracyjne oraz monitorowanie działania systemu to są zupełnie różne rzeczy.

W przypadku monitoringu wewnętrzny stan powinien być udostępniony poza scope klasy i wtedy możesz sobie spokojnie to przecież przetestować jednostkowo. I nie ma tutaj większego znaczenia, czy owo udostępnienie odbywa się przez stworzenie jakiegoś gettera, zamianę private na public, dorzucenie jakiegoś rejestru prometheusowego czy wspomniane przez ciebie asercje.

0
wartek01 napisał(a):
Krolik napisał(a):

Test read powinien polegać na tym, że masz jakiś predefiniowany input.file i sprawdzasz, czy to co read wypluło jest zgodne z tym, co według ciebie wypluć powinno.
W przypadku write masz sytuację odwrotną - czyli wrzucasz jakąś strukturę danych do write, tworzy ci się jakiś output.file, i na końcu sprawdzasz, czy ten plik jest zgodny z tym, co być powinno.

No ale teraz wg wcześniejszej logiki @Riddle testujesz prywatną implementację read / write. Bo zapis do pliku to przecież szczegół implementacyjny ;)

Zarówno forma, jak i rodzaj pliku może być funkcjonalnością. Czym innym funkcjonalnie jest serializacja do JSONa, a czym innym jest serializacja do XMLa.

Jeden i ten sam kod może być albo szczegółem implementacyjnym albo funkcjonalnością - wszystko zależy od kontekstu w jakim się znajduje.

Krolik napisał(a):

Ale jeśli program nie operuje na plikach, a jedynie używa ich wewnętrznie, jako cache, jak np ngrok jest tylko tunelem między dwoma HTTP, a plików używa jako cache, to wtedy już nie możesz takich plików traktować jak interfejs i nie powinieneś o nich wiedzieć.

No z tym się właśnie nie zgadzam. To że jest traktowany wewnętrznie nie oznacza, że dla developerów powinien być tylko czarną skrzynką. Nie, ten plik nadal ma zapewne jakąś strukturę, jest (powinien być) udokumentowany na wewnętrzne potrzeby projektu. A skoro tak, to dobrze mieć testy.

No, tak i nie.

  • Czy Ty jako deweloper musisz znać strukturę tego pliku, żeby móc z nim pracować? Owszem.
  • Czy Twój kod musi znać dokładną strukturę tego pliku - również tak.

I teraz pytanie za milion dolarów:

  • Czy jeśli zmienię strukturę tego pliku, ale w taki sposób ze aplikacja nadal działa tak jak działała i nie ma z tego powodu żadnego buga, to czy jakikolwiek test powinien się wywalić?

Moim zdaniem żaden test nie powinien się wywalić, @Krolik jakie jest Twoje zdanie?

1

Czy jeśli zmienię strukturę tego pliku, ale w taki sposób ze aplikacja nadal działa tak jak działała i nie ma z tego powodu żadnego buga, to czy jakikolwiek test powinien się wywalić?

Jeśli zmieniłeś strukturę tego pliku tak że nie jest już zgodna z dokumentacją (nawet jedynie wewnętrzną / design-doc) i narusza jakieś wewnętrzne niezmienniki przyjęte w założeniach dla tych plików (znowu - mogą to być nawet tylko zasady wewnętrzne, których nie widzi klient) to jak najbardziej powinien się wywalić. I to nawet jeśli testy end-to-end nadal działają ok.

0
Krolik napisał(a):

Czy jeśli zmienię strukturę tego pliku, ale w taki sposób ze aplikacja nadal działa tak jak działała i nie ma z tego powodu żadnego buga, to czy jakikolwiek test powinien się wywalić?

Jeśli zmieniłeś strukturę tego pliku tak że nie jest już zgodna z dokumentacją (nawet jedynie wewnętrzną / design-doc) i narusza jakieś wewnętrzne niezmienniki przyjęte w założeniach dla tych plików (znowu - mogą to być nawet tylko zasady wewnętrzne, których nie widzi klient) to jak najbardziej powinien się wywalić. I to nawet jeśli testy end-to-end nadal działają ok.

Jaki zysk będziesz miał z tego że się wywalą w takiej sytuacji?

Owszem, wywalą się, i będziesz mógł wtedy poprawić ten plik. Ale pytanie się pojawia czemu miałbyś to zrobić, skoro aplikacja nadal działa tak jak działała? Możesz ją wdrożyć i nie będzie regresji, użytkownicy nadal będą mogli z niej korzystać tak jak korzystali, więc pojawia się pytanie, czemu testy miałyby Ci zabronić to zrobić? Nie ma programistycznego powodu żeby to zrobić - jedyne powody jakie są to jakieś formalne/organizacyjne (typu czy plik pasuje do jakiegoś tam szablonu), i to oczywiście też jest okej, tylko to nie powinno być enforce'owane przez testy. Czy dla Ciebie testy nie powinny być odpowiedzią na pytanie "czy aplikacja działa?"?

Bo tym są testy dla mnie, są odpowiedzią na pytanie:

  • czy można zdeployować aplikacje
  • czy aplikacja działa
  • czy aplikacja ma błędy

Pytanie o którym Ty mówisz, czyli "czy plik pasuje do szablonu organizacji" albo "czy dokumentacja pliku pasuje do tego co jest w tym pliku" to są ważne pytania, oczywiście, ale to powinno być ogarnięte w inny sposób. W taki, który nie przeszkadza w developmencie; a testy które wiedzą o swoich internalsach właśnie przeszkadzają.

2

Jaki zysk będziesz miał z tego że się wywalą w takiej sytuacji?

  • Mogą mnie uchronić przed potencjalną katastrofą w przyszłości. Świat nie jest idealny i testy nie są w stanie przetestować wszystkiego. Możliwe że ten wywalający się test w jakiejś implementacji tak naprawdę oznacza błąd również z punktu widzenia bizensu, ale testy wyższego poziomu go nie złapały. Przykładowo, drzewo miało być zrównoważone, okazało się że nie jest, ale poza tym działa i cała reszta jest happy. Testy end-to-end mogą tego nie wyłapać bo np. nie było dostatecznie dużo danych aby zauważyć wpływ na wydajność.

  • Ułatwiają rozwój systemu poprzez zwiększanie przewidywalności jak się zachowują poszczególne komponenty. Konkretny przykład - ostatnio natrafiłem na prywatną metodę, która realizowała w sumie dość prosty algorytm na kolekcji czegoś tam. Okazało się, że poprawnie działała tylko wtedy jeśli liczba elementów w kolekcji <= 2. Dla większej liczby był subtelny błąd. Błąd jednak nigdy nie został wyłapany, bo wszystkie dotychczasowe przypadki testowe (i biznesowe zarazem) szczęśliwie generowały tylko 0, 1 lub 2 elementy i nigdy nie było wstawiane więcej. Metoda nie miała testów, bo była prywatna. Ja dodawałem nowy ficzer w zupełnie innym miejscu, który spowodował, że nagle tam lądowały 3 elementy. I nagle kod zaczął się zachowywać dziwnie. Jakby ta metoda miała testy albo przynajmniej asercję na wejściu (co też jest formą testu), to zaoszczedziłoby mi to długiej sesji debugowania.

  • Dają jasny sygnał - zrobiłem coś niezgodnego ze specyfikacją (która stanowi pewien konsensus zespołowy - wszyscy się zgodzili że takie założenia będą i staramy się ich trzymać). Ktoś inny będzie dodawał po mnie nową funkcję i będzie polegał na tej uzgodnionej specyfikacji to będzie zaskoczony tym, że kod działa inaczej. Wniosek: albo poprawiam kod aby był zgodny z założeniami projektowymi, albo podnoszę sprawę na spotkaniu i obgadujemy czy nie trzeba zmienić specyfikacji. No ale jak ustalimy żeby zmienić, to jest to jawne i będzie mieć odzwierciedlenie np. w dokumentacji.

Bo tym są testy dla mnie, są odpowiedzią na pytanie:
czy można zdeployować aplikacje
czy aplikacja działa
czy aplikacja ma błędy

Żeby odpowiedzieć na Twoje pytania, wystarczą testy end-to-end.

Ale testy odpowiadają również na pytania:

  • jak użyć danego systemu / modułu / klasy / metody / funkcji
  • w jakim obszarze jest błąd, jeśli jest
  • czy kod działa zgodnie z przyjętymi założeniami / dokumentacją tudzież czy dokumentacja nie kłamie
0
Krolik napisał(a):

Jaki zysk będziesz miał z tego że się wywalą w takiej sytuacji?

  • Mogą mnie uchronić przed potencjalną katastrofą w przyszłości. Świat nie jest idealny i testy nie są w stanie przetestować wszystkiego. Możliwe że ten wywalający się test w jakiejś implementacji tak naprawdę oznacza błąd również z punktu widzenia bizensu, ale testy wyższego poziomu go nie złapały. Przykładowo, drzewo miało być zrównoważone, okazało się że nie jest, ale poza tym działa i cała reszta jest happy. Testy end-to-end mogą tego nie wyłapać bo np. nie było dostatecznie dużo danych aby zauważyć wpływ na wydajność.

Tylko że jeśli przyjmiesz takie założenie, to tak na prawdę nie masz enkapsulacji w projekcie - wszystko jest zawsze widoczne i testowane. Według tej definicji każdy jeden stan i każde jedno pole powinno być ustalone i widoczne jak public. Nie ma prawa być żadnego pola private i metody private.

Off-topic

Dobrze, rozumiem, że z jakiegoś powodu masz uprzedzenie do TDD. To co widzę, to również to, że wiele razy spaliłeś się na subtelnych bugach w aplikacjach, (pewnie przez słabe testy), więc teraz doszedłeś do wniosku że lepiej złamać enkapsulację (czyli żeby wszystko było jawne, przegadane i do ustalenia), niż próbować ją respektować. Ma to sens, i rozumiem to. To jest dobry krok jesli wychodzimy z miejsca gdzie aplikacje ciągle nie działają.

Ale nie jest to ostateczny krok. Tzn, byłby ostateczny, gdyby ta aplikacja się nie rozwijała dalej, tzn. gdyby nie musiała mieć żadnych zmian. Wtedy tak jak jest jest okej.

Ale jeśli chcemy żeby ta aplikacja mogła się dalej rozwijać, i być edytowana; to są sposoby jak możnaby ją usprawnić, tak żeby była wyższej klasy, ogólnie była lepsza i tańsza do utrzymania. Próbowałem Ci opowiedzieć o sposobach jak można to osiągnąć, ale widzę że nie udało mi się Ciebie przekonać. Nie sądzę że ma to dalszy sens ta rozmowa. Szkoda, że mi się nie udało, bo pamiętam że sam kiedyś wyznawałem dokładnie takie samo podejście jak Ty - żeby przetestować wszystko, najmniejszy kawałek, żeby przez maleńkie zmiany testy failowały, prywatne czy nie. Ale nauczyłem się, że takie aplikacje są cieższe do utrzymania i rozwijania, i zrozumiałem że w wielu przypadkach odpowiednie schowanie implementacji, i skupienie się jedynie na zachowaniu aplikacji jest kluczem. @Krolik Jeśli będziesz chciał kiedyś wrócić do rozmowy, to obawiam się że bez konkretnego przypadku nie da rady.

Swoją drogą, nadal mi nie odpowiedziałeś @Krolik , zerknąłeś na mój przykład projektu https://github.com/t-regx/crlf odnośnie TDD?

Także dzięki za debatę.

1

Tylko że jeśli przyjmiesz takie założenie, to tak na prawdę nie masz enkapsulacji w projekcie - wszystko jest zawsze widoczne i testowane. Według tej definicji każdy jeden stan i każde jedno pole powinno być ustalone i widoczne jak public. Nie ma prawa być żadnego pola private i metody private.

Myślę, że to zbyt daleko posunięty wniosek. Mogę mieć w kodzie rzeczy prywatne widoczne tylko dla testów, a nadal prywatne dla reszty kodu. Zresztą, te wszystkie rzeczy jak enkapsulacja nie są same w sobie wartościami a jedynie narzędziami do osiągnięcia pewnego celu. Nie wszedzie musi być enkapsulacja.

0
Krolik napisał(a):

Tylko że jeśli przyjmiesz takie założenie, to tak na prawdę nie masz enkapsulacji w projekcie - wszystko jest zawsze widoczne i testowane. Według tej definicji każdy jeden stan i każde jedno pole powinno być ustalone i widoczne jak public. Nie ma prawa być żadnego pola private i metody private.

Myślę, że to zbyt daleko posunięty wniosek. Mogę mieć w kodzie rzeczy prywatne widoczne tylko dla testów, a nadal prywatne dla reszty kodu. Zresztą, te wszystkie rzeczy jak enkapsulacja nie są same w sobie wartościami a jedynie narzędziami do osiągnięcia pewnego celu. Nie wszedzie musi być enkapsulacja.

Jasne.

Tylko widzisz, z enkapsulacją jest tak, że ona ma same zalety i praktycznie żadnych wad.

Kod z enkapsulacją:

  • jest łatwiejszy do zmiany
  • łatwiejszy do zrozumienia
  • ma mniejszą szansę że cos zepsuje
  • zmiany w małej części kodu nie propagują zmian na cały system

Natomiast wada jedyna jaka jest, to taka że nie każdy umie ją zastosować. Tzn. ktoś nie umie napisać dobrych testów które respektują enkapsulację, więc złamie ją żeby napisać test. Lepsze takie testy niż żadne, no ale jednak to nie jest najlepsze co można zrobić.

Chodzi o to że nic nie tracisz dodając enkapsulację, a możesz zyskać. Nie ma więc powodu żeby jej nie dodać - poza tym że ktoś może nie umieć tego zrobić.

2

Problem w tym, że są nie trywialne problemy, które możesz zenkapsulować ale ciężko je rozwijać bez testów (ty byś miał jeden test, a Kroolik miałby kilka, które sprawdzają implementację). Jak by trzeba było poprawić coś to Kroolik to zrobi w 15 minut a ty 3 godziny

2

@Riddle - wszystko ma wady, a enkapsulacja nie jest tu żadnym wyjątkiem.

Enkapsulacja chowa danych, ale pociąga za sobą konieczność przygotowania specyficznych metod, które pośrednio będą przetwarzać te dane, ale nie jest to za darmo:

Pierwsza rzecz to niezależnie jakie masz dane to i tak musisz pisać nowe definicje, by te dane opakować. To tak jakby każdą myśl jaka zamierzasz w kodzie wyrazić wymagała przekuwania na nową definicję. To nie tylko przeczy ponownemu wykorzystaniu kodu, ale również prowadzi do powstania nadętego i zbyt formalnego podejścia, które zamiast rozjaśniać temat to robi coś wręcz przeciwnego. Weź otwórz sobie OWU dowolnej polisy, oni tam na początku wyprowadzają definicje, dość oczywistych pojęć, nie po to by ułatwić zrozumienie tekstu, ale żeby wykluczyć ogólne znaczenie, coś co każdy zna, w ten sposób utrudniają zrozumienie tekstu, bo czytając warunki umowy trzeba ciągle wracać do tych definicji, aby zajarzyć pod jakim względem ubezpieczyciel robi Cię w bambuko.

Co więcej kod, który piszesz później utrzymujesz i jak wiesz podlega on zmianom, a rzeczy specyficzne zmienność mają wpisaną w swoją definicję. Masz metodę X (dzisiaj ona wysyła maila i generuje raport, a za miesiąc ta sama może robić coś więcej lub coś mniej). Efekt jest taki, że o ile od środka możesz łatwo wprowadzać zmiany, o tyle z zewnątrz, ze strony czytelnika, który próbuje zrozumieć kod korzystający z wywołania metody prowadzi do trudności ze zrozumieniem, ponieważ trzeba skakać i regularnie wracać do definicji klas, aby zrozumieć co kod właściwie robi od środka.

Ostatnim minusem jaki znam jest fakt, że ukrywanie jest OK w przypadku, gdy ukrywasz coś nieistotnego i marginalnego. Enkapsulacja jest OK w przypadku klienta http, klienta pocztowego, kolekcji, plików, parsera itp, ale nie w przypadku logiki biznesowej. Jeśli zaczniesz ukrywać to co istotne, to osoba, która czyta kod i która chce zrozumieć co robi aplikacja to właściwie ma problem, bo musi znowu skakać i zaglądać do wszystkich definicji powiązanych z procedurą, by posklejać fakty do kupy, ale najgorzej jest jeśli musisz rozszerzyć kod i dostać się do tej istotnej informacji, wówczas abstrakcje jakie tworzysz zaczynają przeciekać i wszystko co bazowało na tym api trzeba poprawiać. Logika biznesowa nie jest marginalna i założenia w okół logiki mogą Ci się najszybciej popsuć, tzn. tam gdzie chciałeś coś ukryć, musisz po jakimś czasie odsłonić itp.

Najlepszą puentą dotyczącą enkapsulacji, nad którą zachęcam Cię byś trochę  pomyślał, jest jest cytat Alana Perlisa: It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. Jak myślisz co ten gościu miał na myśli? Postrzeliło go czy miał rację? :D

0
znowutosamo4 napisał(a):

Pierwsza rzecz to niezależnie jakie masz dane to i tak musisz pisać nowe definicje, by te dane opakować.

Tak.

To tak jakby każdą myśl jaka zamierzasz w kodzie wyrazić wymagała przekuwania na nową definicję. To nie tylko przeczy ponownemu wykorzystaniu kodu, ale również prowadzi do powstania nadętego i zbyt formalnego podejścia, które zamiast rozjaśniać temat to robi coś wręcz przeciwnego.

Niepoprawny wniosek - nie przeczy temu w żaden sposób, wręcz przeciwnie.

Weź otwórz sobie OWU dowolnej polisy, oni tam na początku wyprowadzają definicje, dość oczywistych pojęć, nie po to by ułatwić zrozumienie tekstu, ale żeby wykluczyć ogólne znaczenie, coś co każdy zna, w ten sposób utrudniają zrozumienie tekstu, bo czytając warunki umowy trzeba ciągle wracać do tych definicji, aby zajarzyć pod jakim względem ubezpieczyciel robi Cię w bambuko.

Nie muszę chyba mówić że wytwarzanie oprogramowania to jest coś innego niż czytanie polis? Argument nieadekwatny zuepłnie.

Co więcej kod, który piszesz później utrzymujesz i jak wiesz podlega on zmianom, a rzeczy specyficzne zmienność mają wpisaną w swoją definicję. Masz metodę X (dzisiaj ona wysyła maila i generuje raport, a za miesiąc ta sama może robić coś więcej lub coś mniej). Efekt jest taki, że o ile od środka możesz łatwo wprowadzać zmiany, o tyle z zewnątrz, ze strony czytelnika, który próbuje zrozumieć kod korzystający z wywołania metody prowadzi do trudności ze zrozumieniem, ponieważ trzeba skakać i regularnie wracać do definicji klas, aby zrozumieć co kod właściwie robi od środka.

Z zewnątrz też jest to banalne do zrobienia, możesz po prostu napisać nową funkcję, a rzeczy wspólne wydzielić - jeśli już nie są wydzielone.

Ostatnim minusem jaki znam jest fakt, że ukrywanie jest OK w przypadku, gdy ukrywasz coś nieistotnego i marginalnego. Enkapsulacja jest OK w przypadku klienta http, klienta pocztowego, kolekcji, plików, parsera itp, ale nie w przypadku logiki biznesowej. Jeśli zaczniesz ukrywać to co istotne, to osoba, która czyta kod i która chce zrozumieć co robi aplikacja to właściwie ma problem, bo musi znowu skakać i zaglądać do wszystkich definicji powiązanych z procedurą, by posklejać fakty do kupy, ale najgorzej jest jeśli musisz rozszerzyć kod i dostać się do tej istotnej informacji, wówczas abstrakcje jakie tworzysz zaczynają przeciekać i wszystko co bazowało na tym api trzeba poprawiać. Logika biznesowa nie jest marginalna i założenia w okół logiki mogą Ci się najszybciej popsuć, tzn. tam gdzie chciałeś coś ukryć, musisz po jakimś czasie odsłonić itp.

To nie jest argument przeciwko enkapsulacji, tylko przeciwko niepoprawnej enkapsulacji.

W enkapsulacji chodzi o to żeby schować rzeczy nieistotne, a uwidocznić istotne - po prostu.

Najlepszą puentą dotyczącą enkapsulacji, nad którą zachęcam Cię byś trochę  pomyślał, jest jest cytat Alana Perlisa: It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. Jak myślisz co ten gościu miał na myśli? Postrzeliło go czy miał rację? :D

Oba mogą być okej, zależnie od przypadku.

0
znowutosamo4 napisał(a):

@Riddle: ale w zasadzie z Tobą nie da się dyskutować, bo Ty wszystko bronisz X ok, ale "niepoprawne X" już nie. Jak to teraz odbijesz? ;D Stwierdzeniem, że to uwaga nie na temat?

Podałeś trzy argumenty, za tym że enkapsulacja ma wady w poście: Jak przetestować metodę która zmienia prywatne pole?

Podsumowując je:

  • pierwszy, to że nowe definicje przeszkadzają reużywaniu elementów:
    • to jest po prostu nie prawda
  • drugi, to analogia do polis
    • to jest po prostu nieadekwatny argument
  • trzeci, to ilustracja tego że niepoprawnie zrobiona enkapsulacja jest szkodliwa
    • z czym się zgadzam. ale to nie jest argument za tym że enkapsulacja jest zła, tylko to że jeśli ktoś ją nieudolnie spróbuje zrobić to osiągnie słabe efekty - i to jest prawda
2

@Riddle

No okej, Ty sobie to nazywasz podejście "teoretyka", ale ja takie podejście stosuję w swoich komercyjnych projektach od lat. Więc dla mnie to nie jest nic "teoretycznego".

ale co z tego, jeżeli nadal jest to inny kontekst?

piszecie inny soft, oparty o inne założenia, w innych firmach, technologiach i teamach


Najważniejszą rzeczą w teście, wymaganiem jest: test failuje gdy software nie odzwierciedla specyfikacji, tyle.

Nie ma nic ważniejszego.

I teraz wchodzi część inżynieryjna - kompromisy oraz wymagania. Tak wymagania, bo kod piszę się pod wymagania.

Czy testy są szybkie?

Czy testy wpływają na implementacje? (to jest w ogóle ciekawe, bo co to znaczy że piszesz testowalny kod? no to, że zaprojektowałeś go w sposób testowalny, a więc testy wpłynęły już na impl :))

Jak precyzyjnie lokalizują problem?

Jak problematyczne utrzymanie jest testów?

Czy generują "false" wyniki?

Ile czasu pochłania ich pisanie

i pewnie wiele więcej...

To teraz wszystko rozchodzi się o to, jakie wymagania lub wartości chcesz uzyskać

0

Ok, wracając do mojego podjejścia i wątku, TDD nie uprawiam. Może jestem na to za cienki, nie wiem. Zabierając się za projekt od podstaw często jeszcze nie wiem na jakie ograniczenia trafię, jakie technologie wykorzystam, w jakim stopniu będę musiał docelowo ograniczyć swoje plany (czas realizacji i wydajność) oraz jakie funkcjonalności / modele danych będę chciał dorzucić.

Najczęściej zabierając się za nowy projekt od podstaw to obecnie najpierw piszę spike w bardzo wąskim zakresie funkcjonaności żeby sprawdzić czy się uda i jakich technologii użyję, później piszę słabo / średnio testowalny kod dokładając do niego funkcjonalności lub usuwając co nie ma racji bytu, z czego powstaje w serwisach spaghetti, póki wszystko rośnie. Na koniec refaktoruję całość tak, żeby w serwisach wyższego rzędu nie było logiki lub było jej mało. Ostatecznie całą logikę próbuję zepchnąć do serwisów które nie mają żadnych zależności lub mają ich mało.

Z tego wątku przykład odczytuje / przypisuje wartość prywatnemu polu. Zakłądając że w metodach Get i Set jest jakaś logika, których wynik będzie rzutował na funkcjonowanie całego programu i poprawność otrzymywanych wyników, wciąż nie jestem przekonany do tego żeby ich nie testować. Choć chyba po prostu powinienem zepchnąć logikę jeszcze niżej i tyle, lub zepchnąć niżej i mockować wartość prywatnego pola. I zasanawiam się czy i co byłoby niepoprawnego w takim podejściu?

Przykład z wątku jest trywialny i łatwo jest ocenić bez testowania czy metody działają poprawnie. Aczkolwiek są też metody obliczeniowe / algorytmiczne które potrafią namieszać w otrzymywanych wynikach i bym nie zasnął spokojnie bez pewności że wypluwają poprawne wyniki. Tak więc bez ich testowania wydaje mi się że obejść się nie może.

Odpowiedź @MarekR22 oznaczyłem jako rozwiązanie mojego problemu i problemów w przyszłości. W miarę możlwości spróbuję stosować podejście TDD. Liczę na to, że kod będzie lepiej przemyślany. Choć trochę kłócą się jego słowa Celem testów jest sprawdzanie zewnętrznie widocznej funkcjonalności, a nie detali implementacyjnych. z tym co do tej pory myślałem o testach. Czy implementacja nie wpływa na funkcjonalność?

Zgadzam się w 100% z @Riddle i innymi osobami udzielającymi się w tym wątku, że wszystko co prywatne powinno pozostac niewidoczne dla innych klas. Choć wciąż nie do końca umiem się zgodzić z tym, żeby testować najwyższy poziom tylko (funkcjonalność), bo tak rozumiem wypowiedzi. Sprostujcie mnie jak się mylę. Tak jak pisał @obscurity , Czyli potrzebujesz prawdopodobnie testu do klasy która korzysta z tej klasy, wtedy pokrywasz testami je obie , jeśli klasa wyższego poziomu korzysta z tej klasy i ją mockuję wartościami z czapy, to co mi da taki test, jeśli klasa od której jest zależna zwraca logicznie niepoprawne wyniki? Czy to co pisał @Krolik , jeśli w teście korzystam z read i write, to czy znalezienie błędu w jednym z nich nie będzie trudniejsze bez przetestowania ich z osobna?

0
bakunet napisał(a):

Z tego wątku przykład odczytuje / przypisuje wartość prywatnemu polu. Zakłądając że w metodach Get i Set jest jakaś logika, których wynik będzie rzutował na funkcjonowanie całego programu i poprawność otrzymywanych wyników, wciąż nie jestem przekonany do tego żeby ich nie testować. Choć chyba po prostu powinienem zepchnąć logikę jeszcze niżej i tyle, lub zepchnąć niżej i mockować wartość prywatnego pola. I zasanawiam się czy i co byłoby niepoprawnego w takim podejściu?

Wręcz odwrotnie.

Logikę powinieneś wynieść wyżej.

0
Riddle napisał(a):

Wręcz odwrotnie.

Logikę powinieneś wynieść wyżej.

Ale czemu? Chyba łatwiej przetestować klasę bez zależności. Nie piszę że nie, tylko próbuję zrozumieć.

0
bakunet napisał(a):

Z tego wątku przykład odczytuje / przypisuje wartość prywatnemu polu. Zakłądając że w metodach Get i Set jest jakaś logika, których wynik będzie rzutował na funkcjonowanie całego programu i poprawność otrzymywanych wyników, wciąż nie jestem przekonany do tego żeby ich nie testować. Choć chyba po prostu powinienem zepchnąć logikę jeszcze niżej i tyle, lub zepchnąć niżej i mockować wartość prywatnego pola. I zasanawiam się czy i co byłoby niepoprawnego w takim podejściu?

No skoro ta logika jest taka ważna, że rzutuje na poprawność całości, to powinna być ona wydzielona do oddzielnych klas/metod, i oddzielnie porządnie przetestowana.

2

Nie wiem, czy mi czegoś nie umknęło w tym mega wątku.

ja bym pytanie przeredagował:
testowanie klas posiadajacych stan (wszyscy wiemy, ze najlepiej immutable)

Zmiana zmiennej prywatnej - gdyby to była tylko ona - nie jest/nie musi być zmianą stanu, jeśli tego nic nie ujawnia na zewnątrz.
To "stan" czy "zmiana stanu" jest jakością podlegającą testom, a nie jego doraźna implementacja

0

@AnyKtokolwiek:

jeśli tego nic nie ujawnia na zewnątrz.

niestety nie jest to wykonalne. niezależnie od tego jakie podejście do programowania zastosujesz - oop, fp, etc., to i tak zawsze będziesz leakował

0
WeiXiao napisał(a):

@AnyKtokolwiek:

jeśli tego nic nie ujawnia na zewnątrz.

niestety nie jest to wykonalne. niezależnie od tego jakie podejście do programowania zastosujesz - oop, fp, etc., to i tak zawsze będziesz leakował

Mi Bloch na gruncie Javy uświadomił.
Object ma integera jako cache do obliczenia hasha.

I np z konkretnych klas, dziedzczący z niego String, jest w oczywisty sposób immutable, ale zmienia wewn zmienną w momencie pierwszego wywoałania hashCode() Przestaje być immutable? No nie ...

0

@AnyKtokolwiek:

Mi Bloch na gruncie Javy uświadomił.

wut

I np z konkretnych klas, dziedzczący z niego String

wut

anyway

chodziło mi o prąd, temperaturę, zużycie cpu itd.

2
WeiXiao napisał(a):

chodziło mi o prąd, temperaturę, zużycie cpu itd.

Wut...

To co wymieniasz nie ma wiele wsólnego z immutable.
Zużycie cpu itp. można traktować jak impure, ale i tak jest to mocno akademicka, niepraktyczna definicja.
(Bo co daje infotmacja, że poza pewnymi językami wszystkue funkcje są impure? ).

0
AnyKtokolwiek napisał(a):
WeiXiao napisał(a):

@AnyKtokolwiek:

jeśli tego nic nie ujawnia na zewnątrz.

niestety nie jest to wykonalne. niezależnie od tego jakie podejście do programowania zastosujesz - oop, fp, etc., to i tak zawsze będziesz leakował

Mi Bloch na gruncie Javy uświadomił.
Object ma integera jako cache do obliczenia hasha.

I np z konkretnych klas, dziedzczący z niego String, jest w oczywisty sposób immutable, ale zmienia wewn zmienną w momencie pierwszego wywoałania hashCode() Przestaje być immutable? No nie ...

No przestaje.

Np na potrzeby wątków. Jeśli masz obiekt który na prawdę jest immutable to możesz go do woli przekazywać między wątkami bez konieczności synchronizacji. Jeśli masz obiekt który "wygląda" na immutable, ale pod spodem zmienia stan, to nadal musisz mieć synchronizację tego.

0
Riddle napisał(a):

No przestaje.

Np na potrzeby wątków. Jeśli masz obiekt który na prawdę jest immutable to możesz go do woli przekazywać między wątkami bez konieczności synchronizacji. Jeśli masz obiekt który "wygląda" na immutable, ale pod spodem zmienia stan, to nadal musisz mieć synchronizację tego.

Teraz tylko jeszcze pozostaje wytłumaczyć to gościom odpowiedzialnym za String w javie. Bo ewidentnie zrypali.

Btw. tak naprawde to serio ciekawy problem, chyba teoretycznie jest możliwe dostanie złego hashCode w środowisku wielowątkowym....

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