Testy...

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?

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.

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 :)

0

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

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.

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ć.

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.

0

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

testowanie z fejkowymi bazami LUL

to się kiedykolwiek dobrze kończy?

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

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.

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.

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.

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ę?

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.

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ń.
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?

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.

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.

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ć?

0

Czekaj, jaki identity framework, bo nie czaję?

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:

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:

        [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.

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ę?

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.

1
kobi55 napisał(a):

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

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:

        [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.

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ś:

    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:

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

napiszę takie testy:

        [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 :)

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:

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ę.

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:

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:

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:

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?

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

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