Wątek przeniesiony 2020-07-06 03:06 z C# i .NET przez somekind.

Organizacja testów

0

Witajcie,

Piszę sobie pewną aplikację w C# i .NET Core i zastanawiam się jak podejść do tworzenia projektów zawierających testy. W procesie wytwarzania oprogramowania chciałbym wykorzystać w miarę możliwości podejście Domain-Driven Design.

Z artykułów umieszczonych w Internecie wynika, że projekt wytwarzany metodą DDD powinien być podzielony na warstwy - powinien posiadać warstwę aplikacji, domeny, infrastruktury oraz prezentacji / interfejsu użytkownika. Ponadto z tego co udało mi się dowiedzieć poszczególne warstwy powinno się testować w sposób odpowiedni dla danej warstwy. Dla przykładu warstwę domeny należy testować jednostkowo a np. warstwę infrastruktury integracyjnie.

Utworzyłem więc solucję a w niej kilka projektów (na chwilę obecną mam jeden projekt dla jednej warstwy, np. mam projekt o nazwie domain dla warstwy domeny). Teraz chciałbym stworzyć projekty, w których pisałbym testy. Ponieważ warstwy powinno się testować w sposób odpowiedni dla danej warstwy sensowne jest utworzenie projektów według typu testów czyli projektów o nazwach zbliżonych do tych: UnitTests, IntegrationTests, FunctionalTests. Innym podejściem jest organizacja ze względu na testowany projekt. W takiej sytuacji można utworzyć projekty o nazwach zbliżonych do tych: DomainTests, ApplicationTests, InfrastructureTests.

Który sposób organizacji testów jest z Waszego punktu widzenia lepszy i jakie są zalety i wady obu rozwiązań? Lepiej stosować organizację ze względu na typ testów czy organizację ze względu na testowany projekt?

1

Typ testów, jak Zrobisz zmianę, to możesz odpalić wszystkie unit testy, na przykład i zobaczyć czy nie ma regresji.

1

Ja zazwyczaj mam NazwaProjektu.Tests dla każdego projektu w solucji, bo w każdym projekcie jest coś, co warto przetestować jednostkowo. Do tego jeden projekt NazwaApi.IntegrationTests, no bo na ogół tylko API umiem przetestować integracyjnie.
Przy czym ja nie widzę różnicy między testami integracyjnymi a funkcjonalnymi. A raczej uważam, że wszystkie testy powinny być funkcjonalne, bo jak test jest niefunkcjonalny, to po co on komu?

1
somekind napisał(a):

Przy czym ja nie widzę różnicy między testami integracyjnymi a funkcjonalnymi. A raczej uważam, że wszystkie testy powinny być funkcjonalne, bo jak test jest niefunkcjonalny, to po co on komu?

Do testowania wymagań niefunkcjonalnych.

0

Wymagania mogą być funkcjonalne albo pozafunkcjonalne, jak coś jest niefunkcjonalne, to nie ma za bardzo sensu, więc tym bardziej nie powinno się tego wymagać.

0

Nie wnikam w nazewnictwo, aczkolwiek na studiach miałem wymagania niefunkcjonalne i google zna to określenie bardzo dobrze. Edycja: wiki też: https://en.wikipedia.org/wiki/Non-functional_requirement

Testowanie tych wymagań jak najbardziej ma sens, wszelkiej maści load testy pod to podchodzą, a z mniej automatycznych na przykład badania "intuicyjności" interfejsu. Na pewno mówimy o tym samym (bo nie wydaje mi się, żebyś nie stosował testów obciążeniowych)?

0
Afish napisał(a):

Nie wnikam w nazewnictwo, aczkolwiek na studiach miałem wymagania niefunkcjonalne i google zna to określenie bardzo dobrze. Edycja: wiki też: https://en.wikipedia.org/wiki/Non-functional_requirement

Ludzie mówią powszechnie mówią też "włanczać", "w każdym bąć razie" czy "dedykowane miejsce parkingowe", ale to nie znaczy, że tak jest dobrze.
Wymagania niezwiązane z funkcjonalnością to wymagania pozafunkcjonalne, tak się właśnie angielskie "non-functional" powinno tłumaczyć, bo gdy tłumaczymy jako "niefunkcjonalne" to na pewno nie jest to, co mamy wówczas na myśli, gdyż "niefukcjonalny" to nieodpowiadający potrzebom. Wymaganie nieodpowiadania potrzebom brzmi jak jakiś dziwny paradoks.
To podobny błąd jak tłumaczenie "relationship" jako "relacja".

Testowanie tych wymagań jak najbardziej ma sens, wszelkiej maści load testy pod to podchodzą, a z mniej automatycznych na przykład badania "intuicyjności" interfejsu. Na pewno mówimy o tym samym (bo nie wydaje mi się, żebyś nie stosował testów obciążeniowych)?

Ja nie przeczę, że istnieją wymagania pozafunkcjonalne, ani też nie przeczę, że ma sens ich testowanie. A testy obciążeniowe to sam w sobie inny, bardziej filozoficzny temat, jedne firmy je mają, innym wystarcza monitorowanie produkcji.

Ja po prostu nie czaję tego rozróżniania testów funkcjonalnych od integracyjnych. Mogą istnieć testy funkcjonalne, które nie są integracyjnymi? Po co właściwie takie rozróżnianie?

1
somekind napisał(a):

Wymagania niezwiązane z funkcjonalnością to wymagania pozafunkcjonalne, tak się właśnie angielskie "non-functional" powinno tłumaczyć, bo gdy tłumaczymy jako "niefunkcjonalne" to na pewno nie jest to, co mamy wówczas na myśli, gdyż "niefukcjonalny" to nieodpowiadający potrzebom. Wymaganie nieodpowiadania potrzebom brzmi jak jakiś dziwny paradoks.

Nie wnikam, polska Wikipedia używa obu terminów wymiennie w https://pl.wikipedia.org/wiki/Wymaganie_(inżynieria)
Co ciekawe, mój słownik podkreśla "pozafunkcjonalne" ale nie podkreśla "niefunkcjonalne" :P

Mogą istnieć testy funkcjonalne, które nie są integracyjnymi?

Jak najbardziej, gdy testowane jest coś niewymagającego integracji z niczym innym. Biblioteki do parsowania CSV, left-pady, potencjalnie jakieś mniej zaawansowane grepy czy awk (te nieumiejące czytać z pliku).

Po co właściwie takie rozróżnianie?

Nie wiem, w testowaniu nawet "unit test" nie jest dobrze zdefiniowany (jest przynajmniej kilka różnych definicji pochodzących z obozów XP, TDD czy innych), więc takie dodatkowe rozróżnienia tym bardziej wprowadzają zamęt.

0
Afish napisał(a):

Nie wnikam, polska Wikipedia używa obu terminów wymiennie w https://pl.wikipedia.org/wiki/Wymaganie_(inżynieria)
Co ciekawe, mój słownik podkreśla "pozafunkcjonalne" ale nie podkreśla "niefunkcjonalne" :P

No bo słowo "niefunkcjonalne" jest znane od dawna i występuje w powszechnym słowniku w oczywistym znaczeniu, natomiast pozafunkcjonalne to neologizm branżowy. W poważnej literaturze znajdzie się takie tłumaczenie, tam gdzie ktoś uprawia kalkowanie w stylu "adresowania problemów" pojawiają się i "wymagania niefunkcjonalne".
Wiem, że to Tobie do pracy niepotrzebne, mi właściwie również, no ale skoro już piszemy po polsku, to piszmy. :)

Jak najbardziej, gdy testowane jest coś niewymagającego integracji z niczym innym. Biblioteki do parsowania CSV, left-pady, potencjalnie jakieś mniej zaawansowane grepy czy awk (te nieumiejące czytać z pliku).

Jak dla mnie to wszystko, co wymieniłeś jest do przetestowania po prostu jednostkowo.

Nie wiem, w testowaniu nawet "unit test" nie jest dobrze zdefiniowany (jest przynajmniej kilka różnych definicji pochodzących z obozów XP, TDD czy innych), więc takie dodatkowe rozróżnienia tym bardziej wprowadzają zamęt.

No ja dzielę na jednostkowe - testujące jednostki, nie mające zależności do świata zewnętrznego i szybkie, oraz integracyjne, czyli wywoływanie aplikacji z zewnątrz i sprawdzanie, czy wynik jej działania znalazł się tam, gdzie powinien.

1
[somekind napisał(a)]

No bo słowo "niefunkcjonalne" jest znane od dawna i występuje w powszechnym słowniku w oczywistym znaczeniu, natomiast pozafunkcjonalne to neologizm branżowy. W poważnej literaturze znajdzie się takie tłumaczenie, tam gdzie ktoś uprawia kalkowanie w stylu "adresowania problemów" pojawiają się i "wymagania niefunkcjonalne".

Okej, brzmi sensownie.

Jak dla mnie to wszystko, co wymieniłeś jest do przetestowania po prostu jednostkowo.

No tak, w tym wypadku test jednostkowy będzie testem funkcjonalnym.

No ja dzielę na jednostkowe - testujące jednostki, nie mające zależności do świata zewnętrznego

Tu bardziej tłumaczysz izolację niż jednostkę, to jest jeszcze jeden punkt sporny. Nie oceniam, nie wnikam, po prostu wiele osób definiuje jednostkę i izolację inaczej, więc i cała reszta piramidy testów może być zupełnie inna. Pewnie za kilka dekad branża dorobi się spójnego słownictwa.

0
Afish napisał(a):

No tak, w tym wypadku test jednostkowy będzie testem funkcjonalnym.

@Afish: dobrze, to zgodnie z Twoimi definicjami, jaki test jednostkowy nie byłby testem funkcjonalnym?
Pytam, bo na różnych obrazkach, to testy funkcjonalne bywają rysowane ze dwie warstwy wyżej niż jednostkowe, więc coś mi tu nie pasuje.

Tu bardziej tłumaczysz izolację niż jednostkę, to jest jeszcze jeden punkt sporny.

No teoretycznie można wytłumaczyć lepiej, ale definicja, że testy jednostkowy testuje jednostkę, ma tę zaletę, że jest krótka i prawdziwa. :)

Pewnie za kilka dekad branża dorobi się spójnego słownictwa.

No nie wiem, skoro przez 30 lat nie mogła, to może się nie udać. No chyba, że wreszcie przejdzie ta ustawa. ;)

1
somekind napisał(a):

@Afish: dobrze, to zgodnie z Twoimi definicjami, jaki test jednostkowy nie byłby testem funkcjonalnym?

Taki, który mockowałby jakiś fragment.

No teoretycznie można wytłumaczyć lepiej, ale definicja, że testy jednostkowy testuje jednostkę, ma tę zaletę, że jest krótka i prawdziwa. :)

No jest, tylko czym jest "jednostka"? Bo w zależności od obozu może to być metoda, klasa, "funkcja aplikacji" czy coś innego.
Inna kwestia to izolacja - czy izolujesz testy między sobą czy izolujesz testy od efektów ubocznych. To też jest decyzja i różne środowiska różnie to określają.

No nie wiem, skoro przez 30 lat nie mogła, to może się nie udać. No chyba, że wreszcie przejdzie ta ustawa. ;)

Webówce zajęło 30 lat na zrozumienie, że ekran ma 2 wymiary i dobrze jest zrobić siatkę, więc spokojnie, jest jeszcze masa czasu ;)

0
Afish napisał(a):

Taki, który mockowałby jakiś fragment.

Mógłbyś podać jakiś przykład? Bo ja w ogóle nie widzę związku mockowania z jednostkowym testem funkcjonalnym. (Nie wiem jak to poprawnie nazwać.)
Jak dla mnie, to jeśli testy nie mockują, to są integracyjne albo jednostkowe kodu bez zmiennych zależności.
Jeśli mockują to są jednostkowe kodu ze zmiennymi zależnościami albo jednostkowe po londyńsku czyli bezsensowne.

No jest, tylko czym jest "jednostka"? Bo w zależności od obozu może to być metoda, klasa, "funkcja aplikacji" czy coś innego.

Element większej całości służący do wykonywania jednej określonej funkcji. Może to być metoda albo klasa, w praktyce raczej kilka klas i metod, jeśli chcemy się trzymać SOLID i takich tam. Ale to są w sumie szczegóły implementacji.

Inna kwestia to izolacja - czy izolujesz testy między sobą czy izolujesz testy od efektów ubocznych. To też jest decyzja i różne środowiska różnie to określają.

No jak dla mnie to też są dwa różne tematy. Testy powinny być izolowane od siebie, aby nie wpływały na siebie i nie powodowały dziwnych sytuacji, w których nie można ich uruchomić niezależnie, bo zwracają fałszywe wyniki albo za każdym razem inne.
Co do izolacji od efektów ubocznych, to jak dla mnie jeśli test nie ma efektów ubocznych, to jest jednostkowy (bo mockowany).

1
somekind napisał(a):

Mógłbyś podać jakiś przykład? Bo ja w ogóle nie widzę związku mockowania z jednostkowym testem funkcjonalnym. (Nie wiem jak to poprawnie nazwać.)

Zależy od definicji jednostki i funkcji. Jeżeli przez jednostkę uznajesz coś pokroju klasy/metody (chodzi o rozmiar, nie o dokładną strukturę kodu) a przez funkcję rozumiesz scenariusz testowy czarnej skrzynki, to test jednostkowy może być testem funkcjonalnym, gdy kod nie ma zależności. Ale jeżeli zależności są i zaczynasz je mockować, to wtedy test jednostkowy już funkcjonalny nie jest (bo znasz strukturę kodu pod spodem i ją mockujesz).

Jak dla mnie, to jeśli testy nie mockują, to są integracyjne albo jednostkowe kodu bez zmiennych zależności.

Zależy od kodu. Jak nie ma nic do integracji, to nie będą to testy integracyjne (ale ciągle nic nie mockujesz). Przykładowo test metody parseCSV (gdy produktem jest parser CSV) może być jednocześnie testem jednostkowym i funkcjonalnym, ale nie będzie integracyjnym (bo nie ma żadnej integracji).

Element większej całości służący do wykonywania jednej określonej funkcji. Może to być metoda albo klasa, w praktyce raczej kilka klas i metod, jeśli chcemy się trzymać SOLID i takich tam. Ale to są w sumie szczegóły implementacji.

O te szczegóły się rozbija, bo wiele osób definiuje jednostkę jako dobrze określony rozmiarem fragment kodu (na przykład jedna metoda lub jedna klasa). Znowu dalej piszesz:

Co do izolacji od efektów ubocznych, to jak dla mnie jeśli test nie ma efektów ubocznych, to jest jednostkowy (bo mockowany).

A tu definiujesz jednostkę jako coś ściśle związanego z efektami ubocznymi, co nie jest konieczne (a obecne trendy nawet sugerują, żeby jak najmniej efektów ubocznych mieć, więc większość kodu będzie bez nich).

No jak dla mnie to też są dwa różne tematy. Testy powinny być izolowane od siebie, aby nie wpływały na siebie i nie powodowały dziwnych sytuacji, w których nie można ich uruchomić niezależnie, bo zwracają fałszywe wyniki albo za każdym razem inne.

No i tutaj prezentujesz właśnie pogląd, że testy mają być izolowane od siebie. Niektórzy za to definiuję izolację jako izolację jednostek między sobą, niezależnie od efektów ubocznych wpływających na testy. Warto też pamiętać, że izolacja testów nie musi oznaczać mockowania. Jeżeli w milisekundę postawię stos aplikacji całkowicie od zera, ze wszystkimi zależnościami i danymi, odpalę test, posprzątam i będę znał wynik, to nie ma potrzeby mockować niczego, będzie to test izolowany (od innych testów), a będzie miał efekty uboczne.

Teraz izolacja jednostek. Weź parser CSV używany jako fragment większego fragmentu kodu. Parser będzie jednostką, nie będzie miał efektów ubocznych itp. Teraz weź metodę koncepcyjnie reprezentującą jakieś obliczenia

int sum(string input){
    CSV csv = parser.parseCSV(input);
    int result = csv["column"].sum();
    return result;
}

Parser nie ma efektów ubocznych, jest osobną jednostką. Mockujesz go tutaj? Podejrzewam, że nie (bo mówisz, że testy mają nie wpływać na siebie, więc tutaj nie ma potrzeby), ale wiele osób właśnie zamockuje, bo izolują jednostki, czyli chcą uniezależnić jednostkę liczącą sumę od jednostki parsującej CSV. I można to traktować jako patologię (bo na mockach działa, a potem na prawdziwym kodzie się sypie), ale można też traktować jako sensowną separację na poziomie kontraktu (bo parser może strzelać po sieci albo robić inne dziwy, ale mamy kontrakt, więc sobie na tym kontrakcie mockujemy). Tu pewnie dojdą jeszcze kwestie, że jak parser jest "w cudzym kodzie", to możemy go mockować (bo nie my go kontrolujemy), ale jak jest "w naszym", to go nie mockujemy.

Jednostka i izolacja to są dwa różne obszary testów, można je różnorako definiować i przez to mieć różne kombinacje.

0
Afish napisał(a):

Zależy od definicji jednostki i funkcji. Jeżeli przez jednostkę uznajesz coś pokroju klasy/metody (chodzi o rozmiar, nie o dokładną strukturę kodu) a przez funkcję rozumiesz scenariusz testowy czarnej skrzynki, to test jednostkowy może być testem funkcjonalnym, gdy kod nie ma zależności. Ale jeżeli zależności są i zaczynasz je mockować, to wtedy test jednostkowy już funkcjonalny nie jest (bo znasz strukturę kodu pod spodem i ją mockujesz).

Nie znam struktury kodu, znam jedynie zewnętrzne zależności. Może będą używane bezpośrednio przez punkt wejścia jednostki, a może przez jakąś klasę pomocniczą.
Jaka jest właściwie definicja testów funkcjonalnych?

Zależy od kodu. Jak nie ma nic do integracji, to nie będą to testy integracyjne (ale ciągle nic nie mockujesz).

No i to określiłem jako jednostkowe kodu bez zmiennych zależności. Skoro nie ma zmiennych (zewnętrznych jeśli wolisz) zależności, to nie ma czego mockować.

O te szczegóły się rozbija, bo wiele osób definiuje jednostkę jako dobrze określony rozmiarem fragment kodu (na przykład jedna metoda lub jedna klasa).

Wiele osób jest po prostu w błędzie. Jednostka sama z siebie jest kompletna, potrafi wykonać określone zadanie. Nie każda klasa ma tę cechę, niektóre klasy istnieją tylko po to, aby wykonać kawałek zadania. To nie są jednostki. Człowiek jest jednostką, noga nią nie jest.

A tu definiujesz jednostkę jako coś ściśle związanego z efektami ubocznymi, co nie jest konieczne (a obecne trendy nawet sugerują, żeby jak najmniej efektów ubocznych mieć, więc większość kodu będzie bez nich).

Więc większość kodu będzie się wreszcie dało skutecznie testować jednostkowo. :)
Ale to nie jest definicja, ile sposób rozpoznania na podstawie sposobu działania. Test jednostkowy nie może mieć efektów ubocznych, integracyjny może mieć, może nie mieć, zależy co testuje. Gdyby test jednostkowy miał efekty uboczne, to znaczy, że z czymś by się integrował, więc byłby już integracyjny (może nieumyślnie, ale nadal).

No i tutaj prezentujesz właśnie pogląd, że testy mają być izolowane od siebie. Niektórzy za to definiuję izolację jako izolację jednostek między sobą, niezależnie od efektów ubocznych wpływających na testy.

Chodzi mi po prostu o to, że jeden test nie powinien pracować na wynikach drugiego testu, ani w żaden sposób dzielić stanu znajdującego się gdziekolwiek, bo to prosta droga do strzelenia sobie w stopę.

Warto też pamiętać, że izolacja testów nie musi oznaczać mockowania.

Oczywiście, że nie. Zresztą w testach jednostkowych izolacja zazwyczaj nie jest problemem, to np. w testach integracyjnych piszących coś do bazy zdarza się jakieś zamieszanie z danymi i niespodziewanie psujące się testy.

Jeżeli w milisekundę postawię stos aplikacji całkowicie od zera, ze wszystkimi zależnościami i danymi, odpalę test, posprzątam i będę znał wynik, to nie ma potrzeby mockować niczego, będzie to test izolowany (od innych testów), a będzie miał efekty uboczne.

I będzie integracyjny.

Parser nie ma efektów ubocznych, jest osobną jednostką. Mockujesz go tutaj? Podejrzewam, że nie (bo mówisz, że testy mają nie wpływać na siebie, więc tutaj nie ma potrzeby)

Do testu tego kodu napisałbym test integracyjny, bo jednostkowy z zamockowanym parserem nie będzie miał wartości.

bo izolują jednostki, czyli chcą uniezależnić jednostkę liczącą sumę od jednostki parsującej CSV.

No tak w realnym życiu też bym tak zrobił, ale najpierw bym wyniósł sumowanie do oddzielnej jednostki. Miałbym dwie jednostki: parser i sumator, a do tego orkiestrator (Twoją obecną funkcję sum), który je wywołuje. Tego orkiestratora już bym nie testował jednostkowo, bo nie miałoby to wartości. (No, a ponieważ nie testuję jednostkowo, to nie ma sensu nazywanie go jednostką.)

Można mieć jednostki będące jednostkami, realną izolację ich testów, i brak mockowania utrudniającego życie. Tylko trzeba się zastanowić nad układem kodu.

Jednostka i izolacja to są dwa różne obszary testów, można je różnorako definiować i przez to mieć różne kombinacje.

Nie można różnie definiować, można jedynie uznawać błędne definicje, ale ziemniak pomalowany czerwoną farbą nigdy nie będzie pomidorem.

1
somekind napisał(a):

Jaka jest właściwie definicja testów funkcjonalnych?

Testy funkcjonalne nie znają struktury kodu, testują jedynie "funkcjonalność" czyli to, co "kod robi". Po ludzku jakieś wymaganie biznesowe czy coś, przynajmniej dla mnie, bo o ile można testować funkcjonalnie malutkie fragmenty, to dla mnie nie one są celem.

Wiele osób jest po prostu w błędzie. Jednostka sama z siebie jest kompletna, potrafi wykonać określone zadanie. Nie każda klasa ma tę cechę, niektóre klasy istnieją tylko po to, aby wykonać kawałek zadania. To nie są jednostki. Człowiek jest jednostką, noga nią nie jest.

Czy w błędzie, czy nie, to nie wiem, nie mnie kłócić się z Fowlerami czy innymi. Po prostu definicji jest wiele, o to mi chodzi. Nie oceniam też Twoich definicji, jedynie staram się patrzeć szerzej.

Ale to nie jest definicja, ile sposób rozpoznania na podstawie sposobu działania. Test jednostkowy nie może mieć efektów ubocznych, integracyjny może mieć, może nie mieć, zależy co testuje. Gdyby test jednostkowy miał efekty uboczne, to znaczy, że z czymś by się integrował, więc byłby już integracyjny (może nieumyślnie, ale nadal).

Może jak najbardziej, ale nie jest to pożądane. Powodów jest wiele, automatyzacja, izolacja itp, ale sam test jednostkowy ma przetestować jednostkę, a jak on to zrobi, to już osobna kwestia.

I będzie integracyjny.

Pewnie tak, ale niekoniecznie, zależy od tego, czego zewnętrznego używam i jak sobie definiuję integrację. Technicznie dwie "jednostki" z unit testu już będą tworzyły razem test integracyjny. Inny przykład: jeżeli czytam dobrze znany plik (hosts na przykład), to technicznie dalej jest to integracja (bo system plików), ale praktycznie niekoniecznie.

Do testu tego kodu napisałbym test integracyjny, bo jednostkowy z zamockowanym parserem nie będzie miał wartości.

To osobna kwestia (z którą się zgadzam, ale specjalnie napisałem kod z takim pomieszaniem). Moim meritum było pokazanie, że jednostkowy z mockowanym parserem może nie być przez niektórych uznawany za "sensowny", chociaż wpisuje się w definicje testu jednostkowego.

Nie można różnie definiować, można jedynie uznawać błędne definicje, ale ziemniak pomalowany czerwoną farbą nigdy nie będzie pomidorem.

Można definiować, jak najbardziej. Mogę też sobie zdefiniować liczby parzyste jako liczby podzielne przez 3. To, że takie definicje "są nieużyteczne", to już osobna kwestia, ale definicja sama z siebie nie może być niepoprawna. O to w sumie rozchodzi się od kilku wiadomości, że różni ludzie mają różne definicje, Ty uznajesz je za "błędne", ale różne obozy (TDD, XP, BDD) mogą mieć inne zdanie.

2
Afish napisał(a):

Testy funkcjonalne nie znają struktury kodu, testują jedynie "funkcjonalność" czyli to, co "kod robi". Po ludzku jakieś wymaganie biznesowe czy coś, przynajmniej dla mnie, bo o ile można testować funkcjonalnie malutkie fragmenty, to dla mnie nie one są celem.

No to jak dla mnie testy funkcjonalne muszą operować na uruchomionej aplikacji, nie mogą to być testy jednostkowe. Testy jednostkowe zawsze znają strukturę kodu, bo wiedzą przynajmniej jakiej klasy metodę wywołać, i ewentualnie jakie ma zewnętrzne zależności.

Czy w błędzie, czy nie, to nie wiem, nie mnie kłócić się z Fowlerami czy innymi.

Fowler uważa, że każda klasa jest jednostką?

Po prostu definicji jest wiele, o to mi chodzi. Nie oceniam też Twoich definicji, jedynie staram się patrzeć szerzej.

Ja patrzę na tyle szeroko, aby widzieć, które podejścia się nie sprawdzają, i to nawet nie tylko w moim przypadku. Dziwnym trafem ludzie, którzy uważają, że każda klasa musi być testowana to zazwyczaj ci sami, którzy twierdzą, że pisanie testów spowalnia pracę, wiąże kod produkcyjny z testowym uniemożliwiając refaktoryzację i w ogóle najlepiej z nich zrezygnować.
A może ich życie byłoby lepsze, gdyby czasem spojrzeli nieco węziej? ;)

Może jak najbardziej, ale nie jest to pożądane. Powodów jest wiele, automatyzacja, izolacja itp, ale sam test jednostkowy ma przetestować jednostkę, a jak on to zrobi, to już osobna kwestia.

Jeżeli test nie jest odizolowany od świata zewnętrznego, to jak dla mnie nie jest on testem jednostkowym. Bo on nie testuje już tylko jednostki kodu, ale też jej połączenie z bazą, systemem plików, innym API.

Pewnie tak, ale niekoniecznie, zależy od tego, czego zewnętrznego używam i jak sobie definiuję integrację. Technicznie dwie "jednostki" z unit testu już będą tworzyły razem test integracyjny.

Technicznie według jakiegoś modnego podejścia? Bo jak dla mnie, to integracja jest wtedy, gdy pojawia się jakiś element zewnętrzny, spoza naszego kodu.

O to w sumie rozchodzi się od kilku wiadomości, że różni ludzie mają różne definicje, Ty uznajesz je za "błędne", ale różne obozy (TDD, XP, BDD) mogą mieć inne zdanie.

OK, nie chcę walczyć o jakieś jedną konkretną nazwę, ja dzielę testy na dwie kategorie:

  • użyteczne;
  • bezużyteczne.

Użyteczne dzielę na trzy kategorie:

  • jednostkowe - testują jednostki, czyli porcję kodu dostarczającą jakąś sensowną funkcję, może to być coś biznesowego (np. pobranie listy kont bankowych klienta i zwrócenie ich wartości w jednej walucie) albo technicznego (np. konwerter reguł FluentValidation na dokumentację Swaggera albo metoda zamieniająca wszystkie pierwsze litery w zdaniu na wielkie);
  • integracyjne - wywołują punkt wejścia aplikacji (np. akcję kontrolera), i robią coś gdzieś, zazwyczaj
  • e2e i wydajnościowe, ale tymi się na ogół nie zajmuję, a przynajmniej nie robię tego codziennie, więc pomijam w ogólnych rozważaniach.

Testy bezużyteczne, to np.:

  • testy klas - czyli te pisane przez ludzi uważających, że każda klasa jest jednostką i betonujących kod produkcyjny z testami;
  • testy mocków - zazwyczaj powiązane z pierwszą grupą, bo jeśli każda klasa jest jednostką, to zazwyczaj każda dostaje też swój interfejs, który trzeba mockować wszędzie;
  • testy interakcji między klasami - czyli to co wyżej, ale bez mocków;
  • testy wywoływań zewnętrznych na poziomie klas - czyli wywoływana jest losowa warstwa aplikacji, realnie wywołując interakcję z bazą, albo jakimś zewnętrznym API. Taki półtest półintegracyjny.
  • testy konfiguracji kontenera IoC - no bo jak się ma tysiące klas i mocków, wszystko wstrzykiwane przez jakiś kontener, na dodatek bez użycia konwencji, to dopisanie testów nie jest już dużą inwestycją. A nuż kontener jest zepsuty i się nie wywali, gdy nie znajdzie jakiejś zależności?
  • testy konstruktorów;
  • testy kontrolerów;
  • testy czerwone, zielone, zwracające losowe wyniki oraz Schrödingera .
1
somekind napisał(a):

No to jak dla mnie testy funkcjonalne muszą operować na uruchomionej aplikacji, nie mogą to być testy jednostkowe. Testy jednostkowe zawsze znają strukturę kodu, bo wiedzą przynajmniej jakiej klasy metodę wywołać, i ewentualnie jakie ma zewnętrzne zależności.

No jak mówisz o aplikacji, to tak. Ale co jeżeli piszesz bibliotekę do parsowania CSV przyjmującą strumień bajtów i zwracającą obiekt?

Fowler uważa, że każda klasa jest jednostką?

"Fowleramy czy innymi" w sensie z różnymi podejściami. Co Fowler twierdzi(ł) nie jest istotne, chodzi mi o to, że są różnice. Na przykład https://wiki.c2.com/?XpVsStandardDefinitionOfUnitTest mówi za jedną definicją UnitTesting (as defined in the software testing literature) requires that we test the unit in isolation. That is, we want to be able to say, to a very high degree of confidence, that any actual results obtained from the execution of test cases are purely the result of the unit under test. The introduction of other units may color our results.

Tu widać, że test jednostkowy izoluje między jednostkami (wcześniej jest też podane, że jednostką jest klasa). Znowu Response: ExtremeProgramming does not require UnitTests to test a class in isolation from all other classes, czyli tu izolacja nie jest na poziomie jednostek a efektów ubocznych między testami.

Ja patrzę na tyle szeroko, aby widzieć, które podejścia się nie sprawdzają

A swoją pewność opierasz o ile lat doświadczenia i jakie platformy? Bo już między Javą a C# są wojny, a gdzie tam do podejść z innych języków (to tyczy się też SOLID-a, DI, struktury kodu, dziedziczenia, kompozycji itp).

Jeżeli test nie jest odizolowany od świata zewnętrznego, to jak dla mnie nie jest on testem jednostkowym. Bo on nie testuje już tylko jednostki kodu, ale też jej połączenie z bazą, systemem plików, innym API.

Okej.

Technicznie według jakiegoś modnego podejścia? Bo jak dla mnie, to integracja jest wtedy, gdy pojawia się jakiś element zewnętrzny, spoza naszego kodu.

Wiki w https://en.wikipedia.org/wiki/Integration_testing twierdzi, że individual software modules are combined and tested as a group, co nie wymaga, żeby jakikolwiek element był spoza naszego kodu. Chyba, że masz na myśli "spoza kodu, który aktualnie testuję", ale wtedy to się staje śliskie, bo jak testujesz jednostkę A i wołasz jednostkę B (bez jej mockowania), to masz tę integrację, o której mówiłem wcześniej. Chyba, że przyjmiesz, że chodzi o jakiś "odpowiednio duży element zewnętrzny" i tak dalej…

OK, nie chcę walczyć o jakieś jedną konkretną nazwę, ja dzielę testy na dwie kategorie:

No i spoko, masz swoje definicje, sprawdzają Ci się, wszystko okej, za to inni mają inne definicje. Dlatego całe opowieści o piramidzie testów itp są śliskie, bo ludzie wojują na poziomie "chcę mieć unit testy zamiast bla testów", ale różnią się na poziomie definicji jednostki i bla.

1
Afish napisał(a):

No jak mówisz o aplikacji, to tak. Ale co jeżeli piszesz bibliotekę do parsowania CSV przyjmującą strumień bajtów i zwracającą obiekt?

Czyli mówisz, że jeśli w teście jednostkowym wywołam sobie metodę swojej biblioteki, to będzie funkcjonalny? No może.

Tu widać, że test jednostkowy izoluje między jednostkami (wcześniej jest też podane, że jednostką jest klasa).

No tak, czyli wynika z tego, że gdzieś kiedyś ktoś faktycznie podał taką definicję. Niestety strona do której się odwołują nie działa, więc nie jestem w stanie zweryfikować o kogo dokładnie chodzi. Cóż, taka definicja jak dla mnie nie jest zgodna nawet z intuicyjnym rozumieniem tego słowa w jakimkolwiek znanym mi języku, więc nie mogę uznawać jej za rozsądną.

Znowu Response: ExtremeProgramming does not require UnitTests to test a class in isolation from all other classes, czyli tu izolacja nie jest na poziomie jednostek a efektów ubocznych między testami.

Ja w tym zdaniu widzę po prostu stwierdzenie, że testy jednostkowe nie muszą być testami klas. I to jest generalnie też to, z czym się zgadzam, i o co mi cały czas chodzi.

A swoją pewność opierasz o ile lat doświadczenia i jakie platformy?

Z 10-15, zależy jak liczyć. Widziałem wystarczająco dużo projektów, w których podejście, że każda klasa musi mieć interfejs i oddzielne testy sprawiło, że kod był bardzo trudny w utrzymaniu, a przy okazji wcale dobrze nie przetestowany.

Bo już między Javą a C# są wojny

Wojny są z różnych dziecinnych przyczyn, natomiast jeśli chodzi o podejście do testowania, to problemy i patologie w tych językach są dokładnie te same.
Nie sądzę też, żeby w tak podstawowych sprawach język w ogóle miał jakiekolwiek znaczenie. Po prostu jednostka to coś, co samodzielnie jest w stanie wykonać pracę, a nie określenie jakiegoś kawałka kodu w danym paradygmacie.

Wiki w https://en.wikipedia.org/wiki/Integration_testing twierdzi, że individual software modules are combined and tested as a group, co nie wymaga, żeby jakikolwiek element był spoza naszego kodu. Chyba, że masz na myśli "spoza kodu, który aktualnie testuję", ale wtedy to się staje śliskie, bo jak testujesz jednostkę A i wołasz jednostkę B (bez jej mockowania), to masz tę integrację, o której mówiłem wcześniej. Chyba, że przyjmiesz, że chodzi o jakiś "odpowiednio duży element zewnętrzny" i tak dalej…

Nie, mam na myśli jakikolwiek element spoza kodu danego projektu, spoza kodu w sensie bycia zależnością zewnętrzną: API, baza, bus, plik, itd. Test, w którym jedna jednostka używa innej, to dla mnie wcale nie jest test integracyjny - bo z niczym zewnętrznym się nie integruję. Stosując tę definicję mogę się dogadać z wieloma osobami, na pewno więcej niż mówiąc o testach jednostkowych.

No i spoko, masz swoje definicje, sprawdzają Ci się, wszystko okej, za to inni mają inne definicje. Dlatego całe opowieści o piramidzie testów itp są śliskie, bo ludzie wojują na poziomie "chcę mieć unit testy zamiast bla testów", ale różnią się na poziomie definicji jednostki i bla.

Tak, tu masz rację.
Dlatego ja dzielę na sensowne i bezsensowne. I to też nie jest tak, że "mi się sprawdza". Po prostu zbyt często widzę, że odmienne podejście się nie sprawdza.

0
somekind napisał(a):

Czyli mówisz, że jeśli w teście jednostkowym wywołam sobie metodę swojej biblioteki, to będzie funkcjonalny? No może.

No jak produktem jest biblioteka i testem jednostkowym testujesz całą bibliotekę, to jak inaczej ten test określisz? Albo inaczej, jak wyglądałby test funkcjonalny do parsowania CSV?

Z 10-15, zależy jak liczyć. Widziałem wystarczająco dużo projektów, w których podejście, że każda klasa musi mieć interfejs i oddzielne testy sprawiło, że kod był bardzo trudny w utrzymaniu, a przy okazji wcale dobrze nie przetestowany.

Nikt tu nie mówi o interfejsach (a przynajmniej ja nie mówię). Temat jest o wiele szerszy (i moje pytanie o platformy nie było przypadkowe bo w innych technologiach w ogóle nie ma interfejsów, a jakoś ludzie mockują i robią testy jednostkowe).

Wojny są z różnych dziecinnych przyczyn, natomiast jeśli chodzi o podejście do testowania, to problemy i patologie w tych językach są dokładnie te same.

Nie, są inne. Javowcy o wiele częściej stosują (stosowali?) partial mocki, bo w javce jest to łatwiejsze. Tak samo z DI, w Javie ten mechanizm jest inaczej używany, bo cglib i inne biblioteki do modyfikowania kodu w locie robią cuda, których dotnet nie potrafi. Nawet głupiego lomboka nie da się przenieść do C#.

Nie sądzę też, żeby w tak podstawowych sprawach język w ogóle miał jakiekolwiek znaczenie. Po prostu jednostka to coś, co samodzielnie jest w stanie wykonać pracę, a nie określenie jakiegoś kawałka kodu w danym paradygmacie.

Masz swoją definicję i jej się trzymasz, to spoko, ale definicji jest dużo. Oczywiście można to zbyć stwierdzeniem, że widziało się różne problemy, ale to nie rozwiązuje sprawy.

Nie, mam na myśli jakikolwiek element spoza kodu danego projektu, spoza kodu w sensie bycia zależnością zewnętrzną: API, baza, bus, plik, itd. Test, w którym jedna jednostka używa innej, to dla mnie wcale nie jest test integracyjny - bo z niczym zewnętrznym się nie integruję. Stosując tę definicję mogę się dogadać z wieloma osobami, na pewno więcej niż mówiąc o testach jednostkowych.

Pewnie tak.

Dlatego ja dzielę na sensowne i bezsensowne. I to też nie jest tak, że "mi się sprawdza". Po prostu zbyt często widzę, że odmienne podejście się nie sprawdza.

No i to jest meritum. Ty dzielisz na X i Y, inni dzielą na A, B, C, jeszcze inni wykłócają się, że mockowanie do zło. I tak dalej. A sama definicja bez wyjaśnienia, dlaczego inne "są bezsensowne" sprawia, że dyskusja nie posunie się do przodu, bo jeden będzie mówił, że on nigdy nie mockuje, tylko zawsze odpala testy na bazie, ale potem okaże się, że ta baza jest inna od produkcyjnej, a osobnego stosu do testowania integracji niekoniecznie się używa.

1
Afish napisał(a):

No jak produktem jest biblioteka i testem jednostkowym testujesz całą bibliotekę, to jak inaczej ten test określisz? Albo inaczej, jak wyglądałby test funkcjonalny do parsowania CSV?

Nie wiem jak by wyglądał, w tej materii ufam Tobie. :) Ja po prostu nie mówię o testach funkcjonalnych, bo jak dla mnie ta nazwa niewiele wnosi. Prawodopodobnie nieświadomie takie tworzę, ale dla mnie to nie ma znaczenia.

Nikt tu nie mówi o interfejsach (a przynajmniej ja nie mówię).

Ale to się bardzo ze sobą bardzo wiąże, zły design to złe testy i odwrotnie. Niepoprawna identyfikacja jednostki oznacza powstawanie testów klas zamiast testów jednostkowych, w tych testach inne klasy danej jednostki są mockowane (często dzięki interfejsom).

Temat jest o wiele szerszy (i moje pytanie o platformy nie było przypadkowe bo w innych technologiach w ogóle nie ma interfejsów, a jakoś ludzie mockują i robią testy jednostkowe).

W C# też się da mockować bez interfejsów. Byłem nieprecyzyjny, ale chodzi o samą ideę izolacji wewnątrz jednostki, co prowadzi do powstania szkodliwych testów.

Nie, są inne. Javowcy o wiele częściej stosują (stosowali?) partial mocki, bo w javce jest to łatwiejsze.

To nie jest znowu aż tak bardzo łatwiejsze w Javie. W C# niektórzy też lubują się w partial mockach, w tym języku jest to bardziej widoczne, bo musisz bardziej zmienić kod produkcyjny pod testy. Z drugiej strony Javowcy też powszechnie stosują antywzorzec interface/class pair.

Tak samo z DI, w Javie ten mechanizm jest inaczej używany, bo cglib i inne biblioteki do modyfikowania kodu w locie robią cuda, których dotnet nie potrafi. Nawet głupiego lomboka nie da się przenieść do C#.

Tak, wiem Java lepsza, ale to nie ma znaczenia na poziomie projektu struktury kodu. Jeśli każda klasa jest błędnie uznawana za mockowalną jednostkę, to każda musi być być wstrzykiwana, co tylko tworzy jeszcze więcej bałaganu. I to też jest problem powszechnie przewijający się powszechnie u Javowców, inaczej nie mówili by o tym na konferencjach, nie pisali na blogach, ani na tym forum.
W innych językach to będzie wyglądało inaczej, ale praktyczne efekty będą te same: kupa zbędnych testów, kupa zbędnego kodu, i ogólnie nadmierna komplikacja.

Masz swoją definicję i jej się trzymasz, to spoko, ale definicji jest dużo. Oczywiście można to zbyć stwierdzeniem, że widziało się różne problemy, ale to nie rozwiązuje sprawy.

Żeby była jasność - z problemów, które widziałem nie wynika moja definicja tylko mój pragmatyzm.

No i to jest meritum. Ty dzielisz na X i Y, inni dzielą na A, B, C, jeszcze inni wykłócają się, że mockowanie do zło. I tak dalej. A sama definicja bez wyjaśnienia, dlaczego inne "są bezsensowne" sprawia, że dyskusja nie posunie się do przodu, bo jeden będzie mówił, że on nigdy nie mockuje, tylko zawsze odpala testy na bazie, ale potem okaże się, że ta baza jest inna od produkcyjnej, a osobnego stosu do testowania integracji niekoniecznie się używa.

No to jest akurat druga skrajność. I z tego co zauważam, to wpadają w nią ludzie, którzy się przejechali na opętanych mockami testach klas - czyli dokładnie na tym, co ja neguję i unikam.
Ja staram się być w tym środku, który działa i nie wymaga tworzenia zbędnego kodu. Jestem zbyt leniwy, żeby sobie dokładać roboty, i zbyt sprytny, żeby dać się budzić w nocy. ;)

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