Sens testów jednostkowych

1

Mam pytanie o sens testów jednostkowych, usilnie staram się jakieś wepchnąć do moich aplikacji ale tak naprawdę nie widzę tego żadnego sensu
Z tego co czytam nie powinno się pisać testów operujących na zasobach sieciowych, na plikach, na bazach danych, nie da się też testować niczego związanego z interfejsem użytkownika, ani operującego na wątkach - co więc zostaje?
Przykładowe testy które znajduje nie mają żadnego praktycznego sensu, wszędzie spotyka się przykłady pokroju

int DwaPlusDwa()
{
    return 2+2;
}

Assert.True(4, DwaPlusDwa());

wszystkie obiekty trzeba mockować czyli tak naprawdę napisać aplikację po raz drugi tym razem zwracając udawane obiekty
w takim udawanym projekcie wszystko jest pod kontrolą i nie ma za dużo związku z rzeczywistą aplikacją
jeżeli mam fabrykę która zwraca element i dodaję ten element do listy i teraz fabryka zwraca zamockowane obiekty, czyli wpisane z palca to przecież niczego w ten sposób nie przetestuję - no chyba że mam wątpliwości czy kolekcja.Add na pewno doda element do listy, ale to bardziej testowanie frameworka niż swojej aplikacji.

Przeczytałem parę książek o testach, o TDD itp ale nie czuję tego - dla mnie wszystkie przykłady są naciągane, oderwane od rzeczywistości i pisane z "udawaniem debila" jak przykład powyżej

Rozumiem że testy przydają się w testowaniu algorytmów ale tych nie ma za dużo w normalnej aplikacji

TLDR;
Prosiłbym o przykłady rzeczywistej aplikacji z dobrze napisanymi testami jednostkowymi

1

Generalnie unit testy rzadko służą do znajdowania bugów w powstającym programie. Tj. miej też na uwadze że są różne szkoły pisania unit testów, czasami najpierw pisze się całość testów do danej klasy, a potem pisze klasę, czasami, tak jak pokazuje Uncle Bob, testy pisze się praktycznie równolegle do kodu etc. Bardziej przydają się, gdy chcesz dodać nową funkcjonalność, a starej nie zepsuć, albo szczególnie, gdy chcesz się zabrać za re-factoring :D
W niektórych przypadkach, dobrze prowadzone unit testy, stanowić mogą też pewien sposób dokumentację, z tą przewagą nad komentarzami, czy osobnym dokumentem, że komentarze nie świecą się na czerwono, gdy są już nieaktualne :)
Dodatkowo jeżeli piszesz do kodu testy, od razu myślisz o tym czy tą funkcję będzie się dało w ogóle przetestować. Prowadzi to do powstawania prostszego i bardziej czytelnego kodu.

Edit. Pominąłem część o algorytmach, które zaznaczyłeś już w swoim poście,

0

Testy jednostkowe - Z tymi sensownymi miałeś styczność, tylko o tym nie wiesz.
Powiedzmy, że piszesz wirtualny bankomat - co robisz co kilka kroków?
Kompilujesz i sprawdzasz czy działa. To jest sensowny test. Potem tylko wystarczy to zautomatyzować.

A w przypadku algorytmu? Sprawdzamy poprawne wyniki dla danych wejściowych - bum, kolejne sensowne testy, które sam i tak zrobisz - tylko nie będą automatyczne.

4
Mały Kaczor napisał(a):

Z tego co czytam nie powinno się pisać testów operujących na zasobach sieciowych, na plikach, na bazach danych, nie da się też testować niczego związanego z interfejsem użytkownika, ani operującego na wątkach - co więc zostaje?

Jak najbardziej można pisać testy operujące na bazach i plikach, tylko to są testy integracyjne, nie jednostkowe.
Testy interfejsu użytkownika również się stosuje, to są testy funkcjonalne.

wszystkie obiekty trzeba mockować czyli tak naprawdę napisać aplikację po raz drugi tym razem zwracając udawane obiekty

Nie "pisać aplikację" drugi raz, tylko użyć frameworka do mockowania w celu dostarczenia sobie pustych implementacji obiektów, których istnienie jest potrzebne do testu, ale one same nie są testowane.

Rozumiem że testy przydają się w testowaniu algorytmów ale tych nie ma za dużo w normalnej aplikacji

Sortowania bąbelkowego i mnożenia macierzy może nie, ale logika biznesowa jest.

Prosiłbym o przykłady rzeczywistej aplikacji z dobrze napisanymi testami jednostkowymi

A napisałeś kiedykolwiek rzeczywistą aplikację?

W każdej jednej są dziesiątki miejsc, które można przetestować, np.:

  1. czy metoda parsująca query string zwraca poprawnie skonwertowaną wartość parametru;
  2. czy metoda obliczająca wartość rabatu dla zamówienia zwraca poprawna wartość;
  3. czy statusy zamówienia są zmieniane w prawidłowej kolejności;
  4. czy konstruktor kontrolera rzuca wyjątek, gdy jako jeden z parametrów zostanie podany null.

Liczba przykładów jest nieograniczona.

0

żeby sprawdzić czy algorytm działa dobrze muszę wiedzieć co ma zwrócić dla danych testowych. Jeśli mam wynik "policzyć" na piechotę na kartce to na ch** mi takie testy - ile jestem podać danych przykładowych i obliczyć dla nich wynika? A co z całą resztą? Jeśli natomiast test ma sprawdzać poprawność algorytmu na losowych danych to sam test też musi dla losowych danych policzyć jaki ma być wynik - czyli trzeba TEN SAM algorytm napisać drugi raz - próbował ktoś z Was napisać dwa kody dla tego samego algorytmu jeden po drugim lub wręcz równolegle?

Już kiedyś poruszałem tą kwestię ale nie dostałem żadnego przykładu, który by udowodnił, że to ma głębszy sens.
Odniosę się do 2 przykładu z postu @somekind - jeśli napiszę algorytm raz, potem napiszę do niego test i wyjdzie mi że jest ok to fajnie. Zmarnowałem jakieś 1,5 raza więcej czasu niż musiałem (te 0,5 to liczenie dla przykładowych danych wejściowych co ma być na wyjściu lub pisanie drugi raz algorytmu (btw. pisze się też testy do testów??)). Teraz wg @Zellus "Bardziej przydają się, gdy chcesz dodać nową funkcjonalność, a starej nie zepsuć" nie wiem co bym musiał robić w innym miejscu kodu aby zepsuł mi się moduł wyliczający rabaty. Jeśli natomiast "pogrzebię" wewnątrz tego modułu to będę miał ku temu powód - albo zmieni się sposób naliczania rabatu albo został znaleziony błąd. W pierwszym wypadku testy trzeba i tak napisać od nowa w drugim widać gdzie sobie można takie testy wsadzić.

somekind napisał(a):

Liczba przykładów jest nieograniczona.

proszę Cię zatem podaj jeden konkretny z aplikacji, którą np. teraz piszesz. Jakiś najprostszy

0

Ja się nie znam i może się mylę (jeśli tak, proszę o sprostowanie), ale jeśli mam na przykład jakąś metodę, która zwraca coś w stylu return y/policzCos(x,y,policzInne(a,b)) , a wiem, że np. a i b to jakie wspolczynniki, x i y to np. kilogramy i cisnienie to napisałbym sobie prostą metodę/funkcję/klasę, która sprawdziłaby mi , czy dla wszystkich a i b od 0 do 1 co 0.001 i dla wszystkich x i y od 0 do ilustam (sensownej wartosci *300) czasem policzCos nie daje zero. A to chyba byłby test, może nawet jednostkowy?

3

Testy jednostkowe docenia się, gdy trzeba coś zmienić, poprawić dodać i przez przypadek psuje się coś innego.
Jest to szczególnie cenne, gdy kod przechodzi z rąk do rak i ktoś niezaznajomiony z projektem łatwo może coś zepsuć.
Test automatyczny pozwala na szybkie wykrycie takich problemów zanim przeniesie poważne szkody, a test jednostkowy pozwala na dokładne i szybkie zlokalizowanie problemu (inne testy zwykle wymagają debugowania).

Im większy projekt, więcej ludzi nad nim pracuje lub im czas życia projektu jest dłuższy, tym bardziej niezbędne są wszelkie automatyczne testy.

1
abrakadaber napisał(a):

żeby sprawdzić czy algorytm działa dobrze muszę wiedzieć co ma zwrócić dla danych testowych. Jeśli mam wynik "policzyć" na piechotę na kartce to na ch** mi takie testy

Testy pisze się raczej do aplikacji które musisz potem utrzymywać, nie w programie na uczelnie, który ma coś raz czy dwa razy policzyć.

abrakadaber napisał(a):

Jeśli natomiast test ma sprawdzać poprawność algorytmu na losowych danych to sam test też musi dla losowych danych policzyć jaki ma być wynik - czyli trzeba TEN SAM algorytm napisać drugi raz

fourfour napisał(a):

czy dla wszystkich a i b od 0 do 1 co 0.001 i dla wszystkich x i y od 0 do ilustam

Raczej testuje się dla wartości skrajnych, min, max i wartości w pobliżu ifów. Ważne jest też żeby testy uruchamiały się szybko

1
abrakadaber napisał(a):

żeby sprawdzić czy algorytm działa dobrze muszę wiedzieć co ma zwrócić dla danych testowych. Jeśli mam wynik "policzyć" na piechotę na kartce to na ch** mi takie testy - ile jestem podać danych przykładowych i obliczyć dla nich wynika?

Test jest dowodem, że dla danych wejściowych funkcja zwraca poprawny wynik. Jeśli testy obejmą kilka wartości standardowych oraz warunki brzegowe, to test jest dowodem poprawności działania funkcji. Nie daje on formalnej gwarancji, ale jest wystarczająco dobry.
Bez testu nie ma żadnego dowodu, że naklepany kod w ogóle działa.

Jeśli natomiast test ma sprawdzać poprawność algorytmu na losowych danych to sam test też musi dla losowych danych policzyć jaki ma być wynik - czyli trzeba TEN SAM algorytm napisać drugi raz - próbował ktoś z Was napisać dwa kody dla tego samego algorytmu jeden po drugim lub wręcz równolegle?

No to już Twój pomysł.

Odniosę się do 2 przykładu z postu @somekind - jeśli napiszę algorytm raz, potem napiszę do niego test i wyjdzie mi że jest ok to fajnie. Zmarnowałem jakieś 1,5 raza więcej czasu niż musiałem (te 0,5 to liczenie dla przykładowych danych wejściowych co ma być na wyjściu lub pisanie drugi raz algorytmu (btw. pisze się też testy do testów??)).

Uważasz, że wielokrotne uruchamianie aplikacji, logowanie się do niej, wybranie formularza w menu, i wypełnienie w nim kilkudziesięciu pól jest szybsze niż napisanie testów?

Teraz wg @Zellus "Bardziej przydają się, gdy chcesz dodać nową funkcjonalność, a starej nie zepsuć" nie wiem co bym musiał robić w innym miejscu kodu aby zepsuł mi się moduł wyliczający rabaty. Jeśli natomiast "pogrzebię" wewnątrz tego modułu to będę miał ku temu powód - albo zmieni się sposób naliczania rabatu albo został znaleziony błąd. W pierwszym wypadku testy trzeba i tak napisać od nowa w drugim widać gdzie sobie można takie testy wsadzić.

No, jeśli dla Ciebie aplikacja to zestaw oddzielnych modułów, to może tak. Zazwyczaj jednak aplikacje dzielą między sobą jakąś część logiki. I wystarczy, że np. dodawanie nowej funkcji w module A wymaga zmiany logiki we wspólnym repozytorium albo kodzie mapującym encje na DTO, albo jakimkolwiek helperze. Taka zmiana może spowodować błędne zachowanie innych modułów aplikacji. Jeśli mamy testy jednostkowe, to szybko się o tym dowiemy. Jeśli nie, to wykryją to dopiero testerzy, albo użytkownicy.

Problem taki oczywiście nie dotyczy aplikacji powstających metodą kopiuj-wklej, bo tam nie ma żadnego kodu wspólnego. Ale jak ktoś próbował kiedyś pisać sensownie zaprojektowaną aplikację, to się z tym zetknął.

Inny przykład - upgrade wersji jakiejś biblioteki używanej przez aplikację, w nowej wersji jest błąd. W przypadku braku testów nieprędko się o tym dowiemy.

proszę Cię zatem podaj jeden konkretny z aplikacji, którą np. teraz piszesz. Jakiś najprostszy

Przykłady, które podałem odnoszą się rzeczywistej aplikacji, którą tworzę w pracy. Nie mogę wkleić jej kodu tutaj, ale wkleję za to z jednego swojego starego projektu:

    [TestFixture]
    public class ObjectHelperTests
    {
        internal sealed class TestViewModel
        {
            [Display(Name = "Tytuł", Description = "Coś jakiegoś")]
            public string Title { get; set; }
            [Display(Name = "Typ", Description = "Coś")]
            public string TypeName { get; set; }

            public string SuperMethod(int x, string y, double z)
            {
                return null;
            }
        }

        [Test]
        public void Test__GetPropertyAttributeValues__Working()
        {
            var name = ObjectHelper.GetPropertyAttributeValue<TestViewModel, DisplayAttribute, string>(x => x.Title, a => a.Name);
            var description = ObjectHelper.GetPropertyAttributeValue<TestViewModel, DisplayAttribute, string>(x => x.TypeName, a => a.Description);

            Assert.That(name, Is.EqualTo("Tytuł"));
            Assert.That(description, Is.EqualTo("Coś"));
        }
    }
  [Test]
        public void GetAll__SortByPaymentInfoState__Success()
        {
            var searchSettings = GetSearchSettings();
            var request = new GridDataRequest<VisitGridModel>(1, 8, new SortSettings<VisitGridModel>(x => x.PaymentInfoState, SortMode.Descending), searchSettings);

            var listViewModel = this.service.GetAll(request);

            Assert.That(listViewModel.Items.Count(), Is.EqualTo(8));
        }

        [Test]
        public void GetAll__SortByPatientFirstName__Success()
        {
            var searchSettings = GetSearchSettings();
            var request = new GridDataRequest<VisitGridModel>(1, 8, new SortSettings<VisitGridModel>(x => x.PatientFirstName, SortMode.Descending), searchSettings);

            var listViewModel = this.service.GetAll(request);

            Assert.That(listViewModel.Items.Count(), Is.EqualTo(8));
        }

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