Testy...

K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0

Cześć,

Doszedłem do tego etapu w moim życiu, w którym postanowiłem zaczac pisać testy jednostkowe. Zacząłem o tym czytać, pojawiło się pojęcie TDD i tak się zastanawiam, czy w realnym projekcie jest czas na właśnie takie podejście?

Jeżeli chodzi o same testy, nie wiem czy dobrze to rozumiem, ale wydaje mi się że tych testów to w sumie wyjdzie więcej niż kodu aplikacji.

Wszędzie są takie bardzo proste przykłady (np dodawanie dwóch liczb). Pytanie jak to wygląda przy bardziej skomplikowanych zagadnieniach. Np. Mam metodę która tworzy usera w DB. Z tego co rozumiem, to test nie powinien się łączyć z bazą, tylko powinna tam być zaślepka tak?

Możecie polecić jakiegoś dobrze napisanego tutka?

UR
  • Rejestracja:prawie 5 lat
  • Ostatnio:prawie 3 lata
  • Postów:360
2

Różnie bywa.
Najczęściej to firmy wspominają o testach jedynie w ogłoszeniu o pracę :)
Potem przychodzisz do pracy i w pierwszy dzien już tłumaczenia, że oni mają zamiar zacząć pisać testy. Nawet projekt z testami mają (pusty).
Potem kończy się to zwykle i tak, że testów nikt nie pisze, bo będzie mało storypointow na retro :( albo kod to spaghetti z taką kupą zależności, że bez refaktora trzeba by bylo z pol systemu zmockowac :)
Ja osobiscie piszę testy nawet w takich firemkach. Jak ktoś mi płacze z tego powodu, to zmieniam pracę. Błędy popełniam, refaktor robię, testów potrzebuję. Nie będę robił gówna, żeby potem za miesiąc mieć za bugfixy milion storypointow zeby manager sie jaral burnout chartem.
Mogę polecić kilka książek
Unit Testing Principles, Practices, and Patterns
XUnit Test Patterns: Refactoring Test Code

Video nie mam jakiego polecić, bo zwykle to g**no na jedną modłę, gdzie piszą testy do metod dodających 2 plus.

Charles_Ray
Bez przesady, to co napisałeś o story pointach i braku testów to są jakieś skrajne patologie. Ja nie trafiłem na takie środowisko nigdy :) nie rozumiem kwestii managera i burnout charta
bakunet
  • Rejestracja:prawie 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:Polska
  • Postów:1596
4

Znajomy co pracował w korpo mówił mi, że TDD to dobra praktyka, ale wygląda to tak, że i tak najpierw pisze spike metody, albo samą metodę i zaraz do niej testy.

Jak test łączy się z DB, to już nie jest test jednostkowy, a integracyjny bodajże. Jak chcesz mieć jednostkowy, to korzystasz z interface i Mockujesz go w teście. Ew. piszesz swoje namiastki, ale to już chyba sztuka dla sztuki.

Ja dla samego siebie teraz staram się korzystać z podejścia opisanego na początku, gdzie piszę metodę, zaraz do niej test. Często pomaga mi to porządnie zrefaktoryzować kod nim się zrobi w nim bałagan. Bo jak kiedyś zakładałem w czasie pisanie, że później to zrobię, to później mi się już nie chciało :)

edytowany 1x, ostatnio: bakunet
EM
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:19
0

Czy w TDD powinno sie testować wszystkie metody czy tylko te podatne na bug ? I czy powinno sie tesotowac metody prywatne?

bakunet
  • Rejestracja:prawie 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:Polska
  • Postów:1596
4
Empek napisał(a):

Czy w TDD powinno sie testować wszystkie metody czy tylko te podatne na bug ? I czy powinno sie tesotowac metody prywatne?

Jakiś czas temu zadałem na tym forum podobne pytanie o metody prywatne. Testujesz publiczne metody, do których mają dostęp inne klasy, w tym testujące. A prywatne są ukryte przed nimi oraz testami. Publiczne korzystają z prywatnych metod w danej klasie. Cokolwiek byś nie pozmieniał w prywatnych, testy powinny wychodzić takie same. Publiczne metody reprezentują domenę, wynik jaki chcesz otrzymać, czy inaczej, wynik jaki otrzymują z niej inne klasy. Teoretycznie powinien być on niezmienny.

edytowany 1x, ostatnio: bakunet
SA
  • Rejestracja:około 12 lat
  • Ostatnio:około 5 godzin
  • Postów:1431
3
Empek napisał(a):

Czy w TDD powinno sie testować wszystkie metody czy tylko te podatne na bug ? I czy powinno sie tesotowac metody prywatne?

Nigdy nie testuje się metod prywatnych*. Niezależnie od tego czy to TDD czy nie. Powód dlaczego nie jest prosty - jeśli metoda prywatna jest gdzieś używana to istnieje przypadek użycia, który ją pokrywa i on powinien być przetestowany, wpp. jest zbędna i powinna być usunięta.

* widziałem w paru miejscach, że ludzie dla testów zmieniają widoczność prywatnych metod na publiczne, żeby dało się je testować. O tym, że to raczysko nie trzeba wspominać.

edytowany 2x, ostatnio: Saalin
bakunet
  • Rejestracja:prawie 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:Polska
  • Postów:1596
2
TS
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:394
1

Temat rzeka. Na tym etapie po prostu rób testy, które robią mniej więcej to co byś zrobił ręcznie. Z czasem zauważysz jakie warto pisać, a jakie nie.

TDD fajnie brzmi, ale w praktyce trzeba do tego podchodzić z głową. Generalnie to jak wiesz jak ma wyglądać wejście i wyjście, i w którym fragmencie kodu ma powstać nowa funkcjonalność to pisz testy na początku. A jak nie to normalnie kodzisz i jak będziesz miał gotowe to dorabiasz testy.

Możesz testować z bazą wypełnioną fixturami, jednak jeśli konsekwetnie stosujecie wzorzec repository to zmockowanie krańcowych klas jest dosyć proste.

Przede wszystkim nie testuj pojedyńczych, odizolowanych klas, gdzie zmockowane jest wszystko inne ani tym bardziej nie testuj metod prywatnych, bo stworzysz wrażliwe testy, które niczego sensownego i tak nie będą sprawdzały.

WeiXiao
  • Rejestracja:około 9 lat
  • Ostatnio:około 12 godzin
  • Postów:5108
0

to jak już w temacie testów...

testowanie z fejkowymi bazami LUL

to się kiedykolwiek dobrze kończy?

edytowany 1x, ostatnio: WeiXiao
K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0
tsz napisał(a):

Temat rzeka. Na tym etapie po prostu rób testy, które robią mniej więcej to co byś zrobił ręcznie. Z czasem zauważysz jakie warto pisać, a jakie nie.

TDD fajnie brzmi, ale w praktyce trzeba do tego podchodzić z głową. Generalnie to jak wiesz jak ma wyglądać wejście i wyjście, i w którym fragmencie kodu ma powstać nowa funkcjonalność to pisz testy na początku. A jak nie to normalnie kodzisz i jak będziesz miał gotowe to dorabiasz testy.

Właśnie plus jest taki, że zaczynam w firmie nowy projekt (Moduł do systemu billingowego w .Net Framework, WebApi w ASP.Net Core 3.1 oraz aplikacja na androida w Xamarinie). Z racji tego, że wszystko będę robił sam pomyślałem, że napisze to "tak jak się powinno". Wiem jak dokłądnie ma wyglądać poszczególna część systemu, co gdzie będzie itd, dlatego podoba mi się to podejście TDD. Myślę, że spróbuje i najwyżej w trakcie stwierdzę, że jednak najpierw napiszę kod :D

renderme
  • Rejestracja:około 6 lat
  • Ostatnio:około 13 godzin
  • Postów:1461
1
kobi55 napisał(a):

Właśnie plus jest taki, że zaczynam w firmie nowy projekt (Moduł do systemu billingowego w .Net Framework, WebApi w ASP.Net Core 3.1 oraz aplikacja na androida w Xamarinie). Z racji tego, że wszystko będę robił sam pomyślałem, że napisze to "tak jak się powinno". Wiem jak dokłądnie ma wyglądać poszczególna część systemu, co gdzie będzie itd, dlatego podoba mi się to podejście TDD. Myślę, że spróbuje i najwyżej w trakcie stwierdzę, że jednak najpierw napiszę kod :D

To ja mam o 180 stopni odwrotnie. W firmie robię testy jednostkowe, bo muszę, ale jak robię jakieś zlecenie prywatnie, to zwyczajnie żal mi na to czasu. W aplikacjach, które robię sam stopień pokrycia testami jednostkowymi to zwykle z 5%. Szczerze mówiąc, to np. w backendzie więcej mi daje konfiguracja jakiegoś klienta do automatycznego odpytania aplikacji, niż testy jednostkowe.
Czasami wydaje mi się, że część osób wkręciła sobie, że robi jakiś rocket-science, robiąc zwykłe CRUDy. Efekt jest taki, że testują metody typu AddNumber, albo AddUser. Testy to taki trochę paradoks, bo proste rzeczy łatwo testować, ale jest to zbędne, a trudne niezwykle ciężko testować.
Ogólnie myślę, że testy są po to, żeby dokumentować kod dla innych i żeby nikt inny nie zepsuł czegoś w aplikacji.


Granie w gry i robienie gier ma tyle wspólnego, co uprawianie seksu z pracą ginekologa.
TS
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:394
0
renderme napisał(a):
kobi55 napisał(a):

Właśnie plus jest taki, że zaczynam w firmie nowy projekt (Moduł do systemu billingowego w .Net Framework, WebApi w ASP.Net Core 3.1 oraz aplikacja na androida w Xamarinie). Z racji tego, że wszystko będę robił sam pomyślałem, że napisze to "tak jak się powinno". Wiem jak dokłądnie ma wyglądać poszczególna część systemu, co gdzie będzie itd, dlatego podoba mi się to podejście TDD. Myślę, że spróbuje i najwyżej w trakcie stwierdzę, że jednak najpierw napiszę kod :D

To ja mam o 180 stopni odwrotnie. W firmie robię testy jednostkowe, bo muszę, ale jak robię jakieś zlecenie prywatnie, to zwyczajnie żal mi na to czasu. W aplikacjach, które robię sam stopień pokrycia testami jednostkowymi to zwykle z 5%. Szczerze mówiąc, to np. w backendzie więcej mi daje konfiguracja jakiegoś klienta do automatycznego odpytania aplikacji, niż testy jednostkowe.
Czasami wydaje mi się, że część osób wkręciła sobie, że robi jakiś rocket-science, robiąc zwykłe CRUDy. Efekt jest taki, że testują metody typu AddNumber, albo AddUser. Testy to taki trochę paradoks, bo proste rzeczy łatwo testować, ale jest to zbędne, a trudne niezwykle ciężko testować.
Ogólnie myślę, że testy są po to, żeby dokumentować kod dla innych i żeby nikt inny nie zepsuł czegoś w aplikacji.

Jeśli jesteś w stanie sobie zautomatyzować wykonywanie testów to też się liczy. To jest po prostu grupa testów, która generuje się automatycznie. Jeśli to działa to spoko. Jak dla mnie to powinno być wliczane do pokrycia.

Proste testy też mają swoją wartość, bo przynajmniej powołują do życia całkiem spory kawałek kodu (inicjację kontrolerów, wszczepiane zależności i takie tam) i można w ten sposób przynajmniej wyłapać najczęściej popełniane błędy.

Ogólnie testy to jest taki dowód, który dołączasz do swojego kodu, że spełnia wymagania biznesowe. Niedoskonały, ale jednak masz coś, co z automatu będzie sprawdzać, że logika kodu się nie zepsuła przy wprowadzaniu nowych zmian.

renderme
mam płacone za godzine => robie testy mam płacone za całe zlecenie => nie robię testów ... Testy jednostkowe też mają wartość - nie przeczę, ale taki komfort kosztuje. Jeżeli dogadałem się na kasę za projekt, to każde działanie musi mi się zwrócić w formie oszczędności czasu, jeżeli mam je podjąć.
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 9 godzin
  • Lokalizacja:Wrocław
9
kobi55 napisał(a):

Doszedłem do tego etapu w moim życiu, w którym postanowiłem zaczac pisać testy jednostkowe. Zacząłem o tym czytać, pojawiło się pojęcie TDD i tak się zastanawiam, czy w realnym projekcie jest czas na właśnie takie podejście?

To jest podejście, które umiejętnie użyte sprawia, że czas się oszczędza. Umiejętnie użyte, czyli tam, gdzie jest sens pisać testy jednostkowe i przy odpowiednim zdefiniowaniu czym jest jednostka.
Niestety testy w firmach są często robione od tyłka strony, czyli:

  1. Najpierw projektowane są wszystkie klasy mające rozwiązać dany problem.
  2. Każda klasa musi mieć interfejs, żeby dało się ją mockować w testach. (W efekcie mamy nadmiar zbędnych interfejsów.)
  3. Uruchomienie tego kodu, testowanie manualne, wprowadzanie poprawek. (Czas, czas, czas, więcej czasu - oczywiście straconego.)
  4. Na koniec dopisanie testów jednostkowych do każdej klasy. (W efekcie mamy testy klas, nie jednostek.)
  5. Potem okazuje się, że zapomnieliśmy o jakimiś przypadku, którego zaimplementowanie wymaga sporej refaktoryzacji, więc trzeba połowę klas wywalić, za to dodać zupełnie inne. Razem ze starymi klasami wyrzucamy ich interfejsy, testy do nich i mocki, czyli tracimy relatywnie dużo czasu i pracy. Za to musimy zrobić nowe klasy, interfejsy, mocki i testy.

To jest strata czasu, dlatego ludzie, którzy popracowali w taki sposób uważają potem, że pisanie testów to strata czasu.
To tak jak z jazdą samochodem. Jeśli nie umiesz go prowadzić ani uruchomić i pchasz z rodzina nad morze, to zajmie Ci to znacznie więcej czasu niż pójście nawet na piechotę. Ale jak umiesz prowadzić, to osiągniesz cel znacznie szybciej.

Przy podejściu TDD:

  1. Definiujesz sobie jednostkę, czyli na początku klasę z metodą, która przyjmie jakąś strukturę danych wejściowych i zwróci jakąś strukturę danych wyjściowych.
  2. Potem piszesz przypadki testowe. Wszystkie - dla typowych poprawnych danych, dla przypadków brzegowych (dzielenie przez 0), dla danych błędnych, itd.
  3. Uruchamiasz testy, muszą być czerwone. Jeśli są zielone, to zepsułeś.
  4. Implementujesz kod produkcyjny, na początku nawet w tej jednej metodzie. Testy robią się zielone.
  5. Jak przechodzi testy, to refektoryzujesz, czyli wydzielasz metody prywatne, przenosisz część kodu do oddzielnych klas. (Ogólnie działasz zgodnie z SOLID, wzorcami projektowymi, itd.)

Zysk jest taki:

  1. Twój kod jest testowalny - z założenia, bo testy napisałeś na początku.
  2. Nie straciłeś czasu na pisanie zbędnego kodu, bo napisałeś tylko tyle kodu, ile potrzeba do przejścia testów.
  3. Nie masz śmieciowych interfejsów i mocków, czyli długu technologicznego do utrzymywania.
  4. Nie tracisz tyle czasu na testowanie manualne z debugerem.

To jest ogromna oszczędność czasu, tylko trzeba od początku myśleć w odpowiedni sposób.

Jeżeli chodzi o same testy, nie wiem czy dobrze to rozumiem, ale wydaje mi się że tych testów to w sumie wyjdzie więcej niż kodu aplikacji.

Więcej niż kodu testowanego. Czy więcej niż kodu aplikacji, to zależy od aplikacji.

Jest wiele rzeczy, których nie ma sensu testować jednostkowo, bo taki test nie wniesie żadnej wartości, bo będzie zbyt oderwany od rzeczywiście uruchomionej aplikacji.

Mam metodę która tworzy usera w DB. Z tego co rozumiem, to test nie powinien się łączyć z bazą, tylko powinna tam być zaślepka tak?

Taki test raczej w ogóle nie ma sensu. Co Ci da, że użyjesz zaślepki? To wcale nie uchroni Cię przed tym, że w bazie nie będzie jakiejś kolumny i dane się nie zapiszą. Albo przed tym, że użytkownik z danym emailem już istnieje.
W takim teście możesz jedynie sprawdzić, czy poprawnie wywołujesz swój mock. Żadnej wartości dla jakości aplikacji.

Empek napisał(a):

Czy w TDD powinno sie testować wszystkie metody czy tylko te podatne na bug ? I czy powinno sie tesotowac metody prywatne?

Jeśli metoda nie jest podatna na błędy, to znaczy, że nie jest nigdzie używana, więc należy ją usunąć.
A w testach nie należy testować żadnych metod ani publicznych ani prywatnych, tylko jednostki. To, że w tym celu trzeba wywołać jakąś metodę wynika z tego, że w wielu językach programowania w ogóle kod umieszcza się w metodach, ale to jest efekt języka, a nie cel sam w sobie. Jednostką może być zarówno jedna metoda, jak i wiele metod w wielu klasach.

BC
Przy tdd 1 z 2 powinny być w odwrotnej kolejności i pisze się jeden test, a później pisze kod. Niekompilujący się kod bo test wymaga jakiejś klasy czy metody też jest traktowane jako red. Zawsze polecam https://www.youtube.com/watch?v=qkblc5WRn-U
somekind
Tak, masz rację. Dlatego ja nie robię TDD, ja po prostu piszę testy przed implementacją, cele osiągam dokładnie takie same. Bo jak dla mnie, to jeśli chce się wywołać tę nieistniejącą metodę, to i tak trzeba wiedzieć, czego się od niej chce, więc można ją było zdefiniować wcześniej.
BC
Jak najbardziej się zgadzam, ale osobiście wolę pisać 1 test i do niego implementację i tak po kolei po wymaganiach, aż kod będzie gotowy.
somekind
Ja wolę mieć więcej testów od razu, bo wtedy od razu widzę, ile pracy mi jeszcze zostało, ale to też nie zawsze się udaje, czasem robię tak jak Ty.
xxx_xx_x
@somekind I to jest najlepsza odpowiedź w tym tamacie
K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0

@somekind Możesz wyjaśnić jeszcze to pojęcie jednostki (najlepiej na jakimś przykładzie)? Ja zawsze byłem przekonany, że testy pisze się per metoda. Jeżeli tak nie jest to co określić jako jednostkę?

edytowany 2x, ostatnio: kobi55
K5
Literówka ;) tak to jest jak się z telefonu pisze
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 9 godzin
  • Lokalizacja:Wrocław
1

No jednostka to takie coś, co dostarcza Ci jakiejś użytecznej wartości samo w siebie. Może to być jedna metoda, a może to być kilka metod albo klas realizujących jakiś nieco bardziej złożony proces, ale istnienie żadnej z tych klas nie ma sensu oddzielnie. Metody/klasy pomocnicze to nie są jednostki.

renderme
  • Rejestracja:około 6 lat
  • Ostatnio:około 13 godzin
  • Postów:1461
0

Ogólnie trzymajmy się pojęć. Czym innym jest TDD, a czym innym stosowanie różnego rodzaju testów przy pracy. Chyba nie ma osoby, która pisze zupełnie bez testów, np. manualnych, tj. napisze kod i sruuu... deploy bez zbudowania lokalnie. Testy są potrzebne, szczególnie automatyczne, w tym jednostkowe - TAM GDZIE SĄ POTRZEBNE.
TDD to jednak ściśla metodologia i w mojej opinii przestrzeganie jej ma sens, tylko przy spełnieniu 3 z 4 poniższych założeń:

  • program dotyczy istotnego zagadnienia, np. płatności, danych wrażliwych itp. Jak się robi np. API do wyświetlania ciekawych filmów, to potencjalny błąd nie jest tak bolesny.
  • jest to duży program nad którą pracuje wiele osób
  • program będzie miał długi czas życia
  • są na to pieniądze.
    Np. według TDD najpierw pisany jest test, a później kod według zasady czerwony-zielony-refaktoryzacja, 100% kody ma być pokryte testem i wiele innych założeń.

Granie w gry i robienie gier ma tyle wspólnego, co uprawianie seksu z pracą ginekologa.
BC
Jak nie spełnisz 3. To żadnego innego też. Druga sprawa - wróć do aplikacji po kilku miesiącach i sprawdź ile czasu marnujesz na zrozumienie co się dzieje w kodzie. TDD pisze się szybko i drastycznie spadają koszty utrzymania czy rozwoju.
K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0
somekind napisał(a):

No jednostka to takie coś, co dostarcza Ci jakiejś użytecznej wartości samo w siebie. Może to być jedna metoda, a może to być kilka metod albo klas realizujących jakiś nieco bardziej złożony proces, ale istnienie żadnej z tych klas nie ma sensu oddzielnie. Metody/klasy pomocnicze to nie są jednostki.

hmm... w takim razie na przykładzie Web Api i generowania tokenu JWT. Po wysłaniu requesta do endpointa wykonuję następujące czynności:

  1. Sprawdzenie czy user jest w bazie, sprawdzenie poprawności danych
  2. Jeżeli usera nie ma w DB lub dane przez niego przekazane są błędne to zwracam 401
  3. Tworzę token JWT
  4. Dodaję do DB informację o utworzonym tokenie
  5. Zwracam token userowi

I teraz jeżeli dobrze rozumiem też tą zasadę, że testy powinny być atomowe, to powinienem mieć osobne testy dla:

  • pkt 1-2 (to chyba już będzie test integracyjny, a nie jednostkowy)
  • 3 i 5
  • 4 (ponownie test integracyjny).

Dobrze to rozumiem czy coś pokręciłem?

bakunet
  • Rejestracja:prawie 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:Polska
  • Postów:1596
1
kobi55 napisał(a):

(...)
Dobrze to rozumiem czy coś pokręciłem?

Wg mnie dobrze. Jedynie w p.4, możesz DB mockować i sprawdzić jedynie, czy jeśli jest user w bazie, to czy zostanie wywołana metoda namiastki DB (w xUnit Verify). Test nie musi się łączyć z bazą. Też nie wiem jak tworzysz token, jeśli masz wydzieloną metodę, warto też ją wrzucić do serwisu i mockować, ją też w serwisie możesz osobno przetestować, albo mieć ją wyizolowaną jedynie, jeśli nie ma logiki żadnej.

EDIT a co do pkt 1-2, to nie musi być test integracyjny, jeśli mockujesz bazę, to sam decydujesz co Ci mock zwróci (przy udawanym wywołaniu bazy). I testujesz różne przypadki, czy wprowadzony user się odnajdzie w tym co mock zwróci czy nie. Jeśli wynik będzie negatywny, to sprawdzasz, czy Ci API zwróci błąd, jeśli pozytywny, to status 200.

edytowany 4x, ostatnio: bakunet
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 9 godzin
  • Lokalizacja:Wrocław
1

@kobi55: no ja tu w ogóle nie widzę miejsca na testy jednostkowe, które nie będą stratą czasu. Jeśli operujesz na endpointach i bazie, to robisz testy integracyjne, funkcjonalne, akceptacyjne czy jak to tam kto zwie, nie jednostkowe.

bakunet napisał(a):

EDIT a co do pkt 1-2, to nie musi być test integracyjny, jeśli mockujesz bazę, to sam decydujesz co Ci mock zwróci (przy udawanym wywołaniu bazy). I testujesz różne przypadki, czy wprowadzony user się odnajdzie w tym co mock zwróci czy nie. Jeśli wynik będzie negatywny, to sprawdzasz, czy Ci API zwróci błąd, jeśli pozytywny, to status 200.

Ten sam efekt można uzyskać testem integracyjnym, który wyśle request do API, które to następnie odczyta (bądź nie) dane z bazy.
I to wszystko bez tracenia czasu na mockowanie bazy i utrzymywanie kodu z tym związanego.

bakunet
  • Rejestracja:prawie 8 lat
  • Ostatnio:około godziny
  • Lokalizacja:Polska
  • Postów:1596
0
somekind napisał(a):

@kobi55: no ja tu w ogóle nie widzę miejsca na testy jednostkowe, które nie będą stratą czasu. (...)

W sensie, że identity framework powinien wszystko ogarnąć i nie powinno się go testować?

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 9 godzin
  • Lokalizacja:Wrocław
0

Czekaj, jaki identity framework, bo nie czaję?

edytowany 1x, ostatnio: somekind
bakunet
A, ok, teraz już rozumiem jak on to robi. Nie było tematu :)
K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0
somekind napisał(a):

@kobi55: no ja tu w ogóle nie widzę miejsca na testy jednostkowe, które nie będą stratą czasu. Jeśli operujesz na endpointach i bazie, to robisz testy integracyjne, funkcjonalne, akceptacyjne czy jak to tam kto zwie, nie jednostkowe.

Rozumiem, to może zapytam jeszcze :D. Czy jeżeli w celu przetestowania metody:

Kopiuj
public User GetFromDb(AccessVM access, ConnectionStrings con)
        {
            using var c = new SqlConnection(con.GetConnectionString());
            
            return c.QueryFirstOrDefault<User>(Sql.GetUserByIndentity, access, commandTimeout: 3000);
        }

napiszę takie testy:

Kopiuj
        [Test]
        [TestCase("testFalse", "5560", "ABC", "123", null)]
        [TestCase("test", "5566", "1252", "5258", null)]
        [TestCase("test22", "5585", "1252", "a", null)]
        public void GetFromDb_IncorrectData(string user, string pin, string sn, string imei, Type expectedResult)
        {
            var result = new User().GetFromDb(
                new AccessVM(user, pin, sn, imei),
                new ConnectionStrings() { TST = "Server=tsql;Database=dm_logic_gci;User Id=tech_teren;Password=***;" });

            Assert.AreEqual(result, expectedResult);
        }

        [Test]
        [TestCase("test", "5560", "ABC", "123", typeof(User))]
        public void GetFromDb_CorrectData(string user, string pin, string sn, string imei, Type expectedResult)
        {
            var result = new User().GetFromDb(
                new AccessVM(user, pin, sn, imei),
                new ConnectionStrings() { TST = "Server=tsql;Database=dm_logic_gci;User Id=tech_teren;Password=***;" });

            Assert.AreEqual(result.GetType(), expectedResult);
        }

To zbliżę się chociaż do prawidłowego tworzenia tych testów (zdaję sobie sprawę, że to już jest test integracyjny).

Czy za użycie NUnita do napisania testu który nie jest jednostkowy będę się smażył w piekle :D?

PS. Wybaczcie proszę za te (pewnie dla Was głupie) pytania. Chciałbym po prostu dobrze ogarnąć podstawy.

edytowany 2x, ostatnio: kobi55
UR
  • Rejestracja:prawie 5 lat
  • Ostatnio:prawie 3 lata
  • Postów:360
1

Jaki sens ma test sprawdzajcy, czy coś co zawsze zwraca typ User, zwraca typ User?

Ogólnie testowanie prostych selektów to pomyłka, bo co wtedy testujesz tak na dobrą sprawę?

K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0
urke napisał(a):

Jaki sens ma test sprawdzajcy, czy coś co zawsze zwraca typ User, zwraca typ User?

Jeżeli select nie zwróci nic to obiekt będzie nullem i result.GetType() wyrzuci NullReferenceException.

edytowany 2x, ostatnio: kobi55
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 9 godzin
  • Lokalizacja:Wrocław
1
kobi55 napisał(a):

Rozumiem, to może zapytam jeszcze :D. Czy jeżeli w celu przetestowania metody:

Kopiuj
public User GetFromDb(AccessVM access, ConnectionStrings con)
        {
            using var c = new SqlConnection(con.GetConnectionString());
            
            return c.QueryFirstOrDefault<User>(Sql.GetUserByIndentity, access, commandTimeout: 3000);
        }

No tu by należało sobie zadać przede wszystkim pytanie czemu ta metoda nie jest statyczna, albo czemu w ogóle jest w klasie User, czy VM w AccessVM oznacza view model, a jeśli tak, to co to robi w klasie User, czemu SQLConnection jest tworzone w takim miejscu i nigdy nie zamykane? Jeśli to miał być active record to nie wyszedł, ale to nawet lepiej, bo nawet jak wyjdzie, to będzie szyszką w tyłku.

napiszę takie testy:

Kopiuj
        [Test]
        [TestCase("testFalse", "5560", "ABC", "123", null)]
        [TestCase("test", "5566", "1252", "5258", null)]
        [TestCase("test22", "5585", "1252", "a", null)]
        public void GetFromDb_IncorrectData(string user, string pin, string sn, string imei, Type expectedResult)
        {
            var result = new User().GetFromDb(
                new AccessVM(user, pin, sn, imei),
                new ConnectionStrings() { TST = "Server=tsql;Database=dm_logic_gci;User Id=tech_teren;Password=***;" });

            Assert.AreEqual(result, expectedResult);
        }

        [Test]
        [TestCase("test", "5560", "ABC", "123", typeof(User))]
        public void GetFromDb_CorrectData(string user, string pin, string sn, string imei, Type expectedResult)
        {
            var result = new User().GetFromDb(
                new AccessVM(user, pin, sn, imei),
                new ConnectionStrings() { TST = "Server=tsql;Database=dm_logic_gci;User Id=tech_teren;Password=***;" });

            Assert.AreEqual(result.GetType(), expectedResult);
        }

To zbliżę się chociaż do prawidłowego tworzenia tych testów (zdaję sobie sprawę, że to już jest test integracyjny).

A co takie testy dadzą? Czy uchronią przed jakimś błędem? Czy sprawią, że refaktoryzacja będzie możliwa? Czy pokażą, że kod działa?
Jak dla mnie, to żadne z tych trzech.

Umiem sobie wyobrazić test integracyjny dla warstwy dostępu do danych, w którym najpierw zapisujesz coś do bazy przez swoją klasę, a potem odczytujesz, i sprawdzasz, czy dane odczytane są zgodne z zapisanymi. W ten sposób sprawdzasz, czy niczego nie popsułeś np. w mapowaniu z obiektów na tabele albo odwrotnie. Taki test potencjalnie coś mógłby dać, ale w tym Twoim, to naprawdę nie widzę jak. Co da sprawdzenie typu zwróconego obiektu?

Czy za użycie NUnita do napisania testu który nie jest jednostkowy będę się smażył w piekle :D?

Trochę nie rozumiem pytania. Testy w C# pisze się na ogół w NUnit albo XUnit, ewentualnie MSTest. Chyba nawet nie znam innych frameworków. Różnica między testem integracyjnym, a jednostkowym leży w tym, co on robi, a nie w jakim frameworku powstał.

kobi55 napisał(a):

Jeżeli select nie zwróci nic to obiekt będzie nullem i result.GetType() wyrzuci NullReferenceException.

A więc wystarczy sprawdzić, czyresult jest null.

edytowany 1x, ostatnio: somekind
BC
czemu SQLConnection jest tworzone w takim miejscu i nigdy nie zamykane na końcu metody będzie wywołany dispose - zmiana w C# 8.0
somekind
A, faktycznie nie dojrzałem tego using. Tylko nadal nie widzę sensu tworzenia SQLConnection w takim miejscu.
BC
to już inna sprawa i raczej kiepska decyzja osoby, która projektowała aplikację
K5
To może powiedzcie w jakim miejscu powinno być tworzone SqlConnection :)?
K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0

Hej,

bardzo dziękuję za to, że chciało Ci się napisać te wszystkie uwagi. Bardzo dużo mi to daje :)

somekind napisał(a):

No tu by należało sobie zadać przede wszystkim pytanie czemu ta metoda nie jest statyczna, albo czemu w ogóle jest w klasie User

Zrobiłem tak, że metody do kontaktu z DB odpowiedzialne za Usera są w User itd. Rozumiem, że to jest złe rozwiązanie i powinienem wydzielić metody bazodanowe do osobnej klasy? Jeżeli dobrze zrozumiałem, to te metody powinny być też statyczne, tak?

, czy VM w AccessVM oznacza view model, a jeśli tak, to co to robi w klasie User,

AccessVM to takie coś:

Kopiuj
    public class AccessVM
    {
        /// <summary>
        /// Login
        /// </summary>
        [Required]
        [JsonProperty(Required = Required.DisallowNull)]
        public string Login { get; set; }

        /// <summary>
        /// Hasło
        /// </summary>
        [Required]
        [JsonProperty(Required = Required.DisallowNull)]
        public string Pin { get; set; }

        /// <summary>
        /// Imei urządzenia
        /// </summary>
        [Required]
        [JsonProperty(Required = Required.DisallowNull)]
        public string Imei { get; set; }

        /// <summary>
        /// SerialNumber urządzenia
        /// </summary>
        [Required]
        [JsonProperty(Required = Required.DisallowNull)]
        public string Sn { get; set; }

        public AccessVM()
        {
        }

        public AccessVM(string login, string pin, string sn, string imei)
        {
            Login = login;
            Pin = pin;
            Sn = sn;
            Imei = imei;
        }
    }

jest to model na który rzutowane są dane przekazane przez Usera do Endpointu odpowiedzialnego za generowanie tokeny:

Kopiuj
[HttpPost]
        [Route("token")]
        public AccessToken GenerateToken([FromBody]AccessVM access)

napiszę takie testy:

Kopiuj
        [Test]
        [TestCase("testFalse", "5560", "ABC", "123", null)]
        [TestCase("test", "5566", "1252", "5258", null)]
        [TestCase("test22", "5585", "1252", "a", null)]
        public void GetFromDb_IncorrectData(string user, string pin, string sn, string imei, Type expectedResult)
        {
            var result = new User().GetFromDb(
                new AccessVM(user, pin, sn, imei),
                new ConnectionStrings() { TST = "Server=tsql;Database=dm_logic_gci;User Id=tech_teren;Password=***;" });

            Assert.AreEqual(result, expectedResult);
        }

        [Test]
        [TestCase("test", "5560", "ABC", "123", typeof(User))]
        public void GetFromDb_CorrectData(string user, string pin, string sn, string imei, Type expectedResult)
        {
            var result = new User().GetFromDb(
                new AccessVM(user, pin, sn, imei),
                new ConnectionStrings() { TST = "Server=tsql;Database=dm_logic_gci;User Id=tech_teren;Password=***;" });

            Assert.AreEqual(result.GetType(), expectedResult);
        }

To zbliżę się chociaż do prawidłowego tworzenia tych testów (zdaję sobie sprawę, że to już jest test integracyjny).

A co takie testy dadzą? Czy uchronią przed jakimś błędem? Czy sprawią, że refaktoryzacja będzie możliwa? Czy pokażą, że kod działa?
Jak dla mnie, to żadne z tych trzech.

w sumie to pokażą tylko, że połączenie z DB działa poprawnie. Po tej uwadze widzę, że to za mało :)

kobi55 napisał(a):

Jeżeli select nie zwróci nic to obiekt będzie nullem i result.GetType() wyrzuci NullReferenceException.

A więc wystarczy sprawdzić, czyresult jest null.

Rzeczywiście :)

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 9 godzin
  • Lokalizacja:Wrocław
1
kobi55 napisał(a):

Zrobiłem tak, że metody do kontaktu z DB odpowiedzialne za Usera są w User itd. Rozumiem, że to jest złe rozwiązanie i powinienem wydzielić metody bazodanowe do osobnej klasy? Jeżeli dobrze zrozumiałem, to te metody powinny być też statyczne, tak?

Nie.
Po prostu metoda statyczna zwracająca obiekt typu User znajdująca się w klasie User jest bez sensu. Żeby jej użyć musisz napisać przecież taki kod:

Kopiuj
var userBezSensu = new User();
var user = userBezSensu.GetFromDb(/*jakieś dane*/);

Po co tworzyć jakiś bezsensowny obiekt użytkownika konstruktorem, żeby potem utworzyć prawidłowy obiekt metodą? W takiej sytuacji taka metoda powinna być statyczna.
Generalnie jeśli jest metoda zwracająca X w klasie X, to rzadko kiedy ma sens, aby nie była ona statyczna. Wyjątkiem jest wzorzec builder albo chęć posiadania niezmiennych obiektów, ale u Ciebie nie jest to żaden z tych przypadków.
Niemniej jednak, jeśli wydzielisz tę metodę do jakiegoś DAO, to w tym DAO raczej nie będzie sensu, aby była ona statyczna.

jest to model na który rzutowane są dane przekazane przez Usera do Endpointu odpowiedzialnego za generowanie tokeny:

No czyli DTO, które nazywasz view modelem. To co to robi w obiekcie odczytywanym z bazy? Pomieszałeś wszystkie warstwy, powiązałeś widok z bazą danych, łamiesz Single Responsiblity Principle i efekt będzie taki, że jak coś usuniesz z kontraktu, to Ci baza wybuchnie i na odwrót.

w sumie to pokażą tylko, że połączenie z DB działa poprawnie. Po tej uwadze widzę, że to za mało :)

Czyli sprawdzają czy konfiguracja jest prawidłowa. A testy mają testować kod, nie konfigurację.

K5
  • Rejestracja:ponad 10 lat
  • Ostatnio:11 miesięcy
  • Postów:141
0
somekind napisał(a):

Nie.
Po prostu metoda statyczna zwracająca obiekt typu User znajdująca się w klasie User jest bez sensu. Żeby jej użyć musisz napisać przecież taki kod:

Kopiuj
var userBezSensu = new User();
var user = userBezSensu.GetFromDb(/*jakieś dane*/);

Po co tworzyć jakiś bezsensowny obiekt użytkownika konstruktorem, żeby potem utworzyć prawidłowy obiekt metodą? W takiej sytuacji taka metoda powinna być statyczna.
Generalnie jeśli jest metoda zwracająca X w klasie X, to rzadko kiedy ma sens, aby nie była ona statyczna. Wyjątkiem jest wzorzec builder albo chęć posiadania niezmiennych obiektów, ale u Ciebie nie jest to żaden z tych przypadków.
Niemniej jednak, jeśli wydzielisz tę metodę do jakiegoś DAO, to w tym DAO raczej nie będzie sensu, aby była ona statyczna.

Rozumiem, dziękuję za wyjaśnienie. Wydzielę takie metody do osobnego DAO.

jest to model na który rzutowane są dane przekazane przez Usera do Endpointu odpowiedzialnego za generowanie tokeny:
No czyli DTO, które nazywasz view modelem. To co to robi w obiekcie odczytywanym z bazy? Pomieszałeś wszystkie warstwy, powiązałeś widok z bazą danych, łamiesz Single Responsiblity Principle i efekt będzie taki, że jak coś usuniesz z kontraktu, to Ci baza wybuchnie i na odwrót.

Hmm... no w w tym (źle nazwanym) obiekcie AccessVM mam to co mi przekazał ktoś w requeście do API, w tym przypadku, 4 dane: login, pin, SerialNumber urządzenia i IMEI.

Po stworzeniu DAO dla Usera mam coś takiego:

Kopiuj
public class UserDAO 
    {
        private readonly string connectionString;
        public UserDAO(ConnectionStrings con) => connectionString = con.GetConnectionString();

        public User GetFromDb(AccessDTO access)
        {
            using var c = new SqlConnection(connectionString);

            return c.QueryFirstOrDefault<User>(Sql.GetUserByIndentity, access, commandTimeout: 3000);
        }
    }

w Sql.GetUserByIndentity mam taką SQL:

Kopiuj
SELECT tt_id AS Id, 
       tt_imie AS Name, 
       tt_nazwisko AS Surname, 
       tt_login AS Login
FROM [dm_data_gci].[dbo].nteren_terenowi
     JOIN [dm_data_gci].[dbo].nteren_terenowy_urzadzenie ON ttu_tt_id = tt_id
                                        AND ttu_data_do > GETDATE()
     JOIN [dm_data_gci].[dbo].nteren_urzadzenia ON tu_id = ttu_tu_id
                               AND tu_imei = @Imei
                               AND tu_sn = @Sn
                               AND tu_czy_aktywny = 1
WHERE tt_login = @Login
      AND tt_pin = @Pin
      AND tt_czy_aktywny = 1;

prawdopodobnie coś mi umyka, ale w jaki inny sposób mam przekazać te dane?

WeiXiao
  • Rejestracja:około 9 lat
  • Ostatnio:około 12 godzin
  • Postów:5108
0
somekind napisał(a):

w sumie to pokażą tylko, że połączenie z DB działa poprawnie. Po tej uwadze widzę, że to za mało :)

Czyli sprawdzają czy konfiguracja jest prawidłowa. A testy mają testować kod, nie konfigurację.

za naszych czasów kod testuje aplikacje, a kod to tylko szczegół xD

edytowany 1x, ostatnio: WeiXiao
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)