Jak powinien wyglądać test w przypadku CRUD'a

Jak powinien wyglądać test w przypadku CRUD'a
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

Regularnie pojawiają się dyskusje, czy testować "integracyjnie", czy "jednostkowo" i jedyne co wiem, to to, że chyba nikt na świecie nie wie, czym się różnią oba rodzaje testów. Spróbujmy na przykładzie. Mamy sobie CRUD'a do zapisywania jakiegoś pojedynczego typu (w przykładzie UserDTO). Czy powinienem przetestować ten serwis tak jak w przykładzie, czy rozbić test na kawałki? Nie pytam o to, czy nie pominąłem jakiegoś przypadku, czy gdzieś powinienem dodać kolejną asercję, tylko czy powinienem mieć ileś tam testów na poszczególne metody, czy raczej taki pełniejszy scenariusz biznesowy, który testuje system jako ~black box'a.

Kopiuj
internal class UsersModuleKtTest {

    @Test
    fun `post put get delete test`() = testApplication {
        val client = createClient {
            install(ContentNegotiation){
                json()
            }
        }
        application {
            install(Koin){
                modules(dbModule)
            }
            usersModule()
        }

        val emptyListResponse = client.get("/users"){
            accept(ContentType.Application.Json)
            contentType(ContentType.Application.Json)
        }

        assertEquals(HttpStatusCode.OK, emptyListResponse.status)
        assertTrue(emptyListResponse.body<List<UserDto>>().isEmpty())

        val postUserResponse = client.post("/users") {
            contentType(ContentType.Application.Json)
            accept(ContentType.Application.Json)
            setBody(UserDto(null, "John", "Smith"))
        }

        val savedUser = postUserResponse.body<UserDto>()

        assertEquals(HttpStatusCode.Created, postUserResponse.status)
        assertNotNull(savedUser.userId)

        val putResponse = client.put("/users/{userId}") {
            url.parameters.set("userId", savedUser.userId.toString())
            contentType(ContentType.Application.Json)
            accept(ContentType.Application.Json)

            setBody(UserDto(savedUser.userId, "Alice", "Doe"))
        }

        assertEquals(HttpStatusCode.OK, putResponse.status)

        val getUserResponse = client.get("/users/{userId}"){
            url.parameters.set("userId", savedUser.userId.toString())
            contentType(ContentType.Application.Json)
            accept(ContentType.Application.Json)
        }

        assertEquals(HttpStatusCode.OK, getUserResponse.status)
        assertEquals(savedUser.userId, getUserResponse.body<UserDto>().userId)
        assertEquals(savedUser.firstName, getUserResponse.body<UserDto>().firstName)
        assertEquals(savedUser.lastname, getUserResponse.body<UserDto>().lastname)

        val singleUserResponse = client.get("/users"){
            accept(ContentType.Application.Json)
            contentType(ContentType.Application.Json)
        }

        assertEquals(HttpStatusCode.OK, singleUserResponse.status)
        assertEquals(1, singleUserResponse.body<List<UserDto>>().size)
    }
}
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
1

tl;dr; wiele małych testów jest lepsze niż mało dużych


No, na pewno nie powinien wyglądać tak jak ten.

Zanim odpowiem na pytanie, to muszę ogarnąć temat testy integracyjne vs jednostkowe. Nie wiem czy istnieje inny termin w programowaniu tak niezrozumiały jak te dwa słowa - bo one przez znakomitą większość programistów są rozumiane na opak. Idea testów jednostkowych miała zapewnić testowanie programu jako całość; a używa się nazwy "jednostkowy" do testów malutkich kawałków; i dochodzi do sytuacji w której "zamiast wymyślać konkretną potrzebe -> i potem dobrać do niej nazwę", dochodzi do odwrotnego zjawiska gdzie ktoś najpierw wybiera nazwę (np jednostkowy, albo integracyjny) i dopiero potem próbuje napisać test pod tą nazwę. To oczywiście nie ma żadnego sensu.

Myślę że możemy ostrożnie założyć, że te słowa są pomiędzy sobą tak wymieszane, i istniej tak ogromna ilość opinii i artykułów na ten temat, że używanie tych określeń na testy nie ma żadnego sensu, bo nawet jeśli powiesz komuś "napisałem test jednostkowy", to ten ktoś i tak nie zrozumie co masz na myśli. Jeśli się zagłębisz w historyczne początki pierwszych użyć tych nazw, to znajdziesz racjonalne ich rozróżnienie; ale ono dawno zostało zapomniane, i teraz te słowa są używane bardzo naprzemiennie. "Z grubsza" amatorzy rozumieją określenia "integracyjny"/"jednostkowy" jako testy o małym i dużym zakresie; ale niekoniecznie. Inni rozumieją te określenia jako "wolne i szybkie testy"; albo "testy stawiające apke i testy niestawiające apki"; jeszcze inni rozumieją że testy z mockami to jednostkowe, a testy bez mocków to integracyjne - także w moim rozumieniu, te określenia teraz już nie mają sensu.

Nie próbuj się wpasować w kategorię "test integracyjny" albo "test jednostkowy"; staraj się pisać dobre testy, i nie staraj się wpisać w żadną etykietkę, nic dobrego z tego nie wyjdzie.

Pytanie kolejne, to jak dobrze chciałbyś napisać te testy - czyt. jak bardzo ze swojej strefy komfortu chciałbyś wyjść, żeby napisać je odpowiednio. Jeśli mało, to pierwsze co powinieneś zrobić to podzielić ten test na mniejsze kawałki (no i drugie pytanie, czy ten UserDto to jest encja która siedzi w bazie danych? Bo jeśli tak, to należałoby się jej pozbyć z tych testów).

Poza tym powinieneś też się zdecydować czy testujesz cruda, czy interfejs http tego cruda - bo to są różne rzeczy i powinny być przetestowane osobno.

Pamiętaj też że dookoła testów panuje masa nieporozumień i błędnych przekonań, i czasem ludzie bardzo lubią dodawać do nich rzeczy, które tak na prawdę przeszkadzają.

edytowany 8x, ostatnio: Riddle
loza_prowizoryczna
miskoncepcji - człowiek niby się łudził a jednak.
ZN
ZN
  • Rejestracja:około 2 lata
  • Ostatnio:prawie 2 lata
  • Postów:65
0

Pisz tak, aby testy i sposób ich pisania Cię nie oślepiły.

Koncentrując się na obróbce jesteś w przegranej pozycji, bo możesz przez to przegapić to co najważniejsze. Co z tego, że masz testy skoro potencjalnie mogą rozwiązywać niewłaściwy problem? Choćbyś miał i 1000 testów, one i tak nie ułatwią Ci obrotu o 180'. Dlatego nie raz warto zrobić krok wstecz, ograniczyć liczbę testów, a niekiedy podważyć ich sens.

Jak wiadomo testy to żaden dowód, one nie służą po to by dać gwarancję na brak błędów. Testy są tańsze niż dowód, bo w sumie tylko wyliczasz przypadki, ale też są pewne granice:

  1. pisząc testy średnio opłaca się testować zachowań, których nie powinno być, wystarczy zmiana w apce, a test nie wykryje czego ma nie być (mimo że jest z drobną zmianą).

  2. też testować ciężko jest wyszukane rzeczy. Co z tego, że możesz tak zniwelować szanse na błąd skoro wyprodukowanie testu kosztuje 10 razy więcej niż sam kod.

Te i inne rzeczy w mojej ocenie sprawiają, że więcej błędów unikniesz jeśli zaczniesz częściej i głębiej myśleć o problemie jaki rozwiązujesz niż o samym teście.

Jak już piszę testy to rozgraniczam dwa przypadki:

  1. funkcjonalność wtedy piszę je z samej góry, jeśli mogę z selenium to właśnie to jest dla mnie góra.

  2. złożoną logikę biznesową (możliwie oddolnie, trzymając się jak najbliżej implementacji)

edytowany 1x, ostatnio: znowutosamo
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

OK, to, żeby chwilowo kod nie zaciemniał obrazu, widzę to tak:
Wiem, że mam do napisania funkcjonalność, która będzie pozwalała tworzyć, odczytywać, zmieniać i usuwać konta użytkowników. Ta funkcjonalność jest wymaganiem biznesowym, czyli jest to jakiś kawałek większej definicji "działania aplikacji".

Czy waszym zdaniem test:

Kopiuj
sprawdź, czy baza jest pusta()
utwórz konto użytkownika()
sprawdź czy konto użytkownika istnieje()
dokonaj zmian na tym koncie()
sprawdź czy zmiany się zapisały()
usuń konto()
sprawdź, czy się usunęło()

Jest właściwym podejściem? Bo plusy tego są takie, że:

  • Wiemy, że aplikacja jest w stanie zrobić to, czego się od niej oczekuje, przynajmniej raz
  • Możemy wymienić całą aplikację i tak długo jak api się nie zmienia, to testy działają i nie wymagają zmian
  • Da się taki test zgeneralizować do każdej domeny obiektowej (faktury, zamówienia, whatever), która pojawi się w projekcie.

Są też minusy

  • test nie jest czytelny
  • test jest złożony, czyli jest większe prawdopodobieństwo, że pojawią się w nim błędy
VarrComodoo
  • Rejestracja:prawie 14 lat
  • Ostatnio:dzień
  • Lokalizacja:Bk
  • Postów:480
2
piotrpo napisał(a):
Kopiuj
sprawdź, czy baza jest pusta()
utwórz konto użytkownika()
sprawdź czy konto użytkownika istnieje()
dokonaj zmian na tym koncie()
sprawdź czy zmiany się zapisały()
usuń konto()
sprawdź, czy się usunęło()

moim zdaniem można by to było rozbić na 3 testy łatwiej się połapać jeżeli cos się wykrzaczy, wydaje mi się tez, że byłoby to wygodniejsze bo dla każdego z nich znalazłoby się tez parę przypadków /TestCase/, i nie koniecznie skończy się na takiej malej ilości testów.


Sterczące kolce Pondijusa, ostre grzebienie Daktyloskopei, Trygla i latający Wieprzoryb są niczym wobec Bestii która nas gnębi...
edytowany 1x, ostatnio: VarrComodoo
W0
  • Rejestracja:ponad 12 lat
  • Ostatnio:42 minuty
  • Postów:3542
0

W teorii małe testy są lepsze od dużych bo dają większą granulację - tj. jeśli zepsujemy coś w MyAwesomeService to wywalić nam się powinien MyAwesomeServiceTest. W praktyce czasem łatwiej jest określić, jak powinien zachowywać się serwis na dużych kawałkach, niż tworzyć duże przypadki na małych kawałkach.

W przypadku CRUDów takie sytuacje jednak rzadko się zdarzają bo warstwy są mocno izolowane od siebie, a ich odpowiedzialność jest też dobrze zdefiniowana. Dodatkowym plusem jest to, że pisanie małych testów po prostu ułatwia pisanie całości - więc ogólnie w przypadku tradycyjnych CRUDów trzymałbym się małych testów.

Jeśli chodzi o test to jest on po prostu zbyt rozwlekły. Możesz spokojnie go rozdzielić na:

  • tworzenie konta w przypadku, gdy konto o takiej nazwie istnieje
  • tworzenie konta w przypadku, gdy konto o takiej nazwie nie istnieje
  • zapisywanie zmian na koncie
  • usuwanie nieistniejącego konta
  • usuwanie istniejącego konta
edytowany 1x, ostatnio: wartek01
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
piotrpo napisał(a):

OK, to, żeby chwilowo kod nie zaciemniał obrazu, widzę to tak:
Wiem, że mam do napisania funkcjonalność, która będzie pozwalała tworzyć, odczytywać, zmieniać i usuwać konta użytkowników. Ta funkcjonalność jest wymaganiem biznesowym, czyli jest to jakiś kawałek większej definicji "działania aplikacji".

Czy waszym zdaniem test:

Kopiuj
sprawdź, czy baza jest pusta()
utwórz konto użytkownika()
sprawdź czy konto użytkownika istnieje()
dokonaj zmian na tym koncie()
sprawdź czy zmiany się zapisały()
usuń konto()
sprawdź, czy się usunęło()

No tak, w skrócie tak. Pod warunkiem że "sprawdź czy baza jest pusta" oraz "sprawdź czy się usunęło" nie wołają bezpośrednio do bazy, tylko sprawdzają to pozostałymi metodami CRUD'a.

  • Wiemy, że aplikacja jest w stanie zrobić to, czego się od niej oczekuje, przynajmniej raz
  • Możemy wymienić całą aplikację i tak długo jak api się nie zmienia, to testy działają i nie wymagają zmian
  • Da się taki test zgeneralizować do każdej domeny obiektowej (faktury, zamówienia, whatever), która pojawi się w projekcie.

Są też minusy

  • test nie jest czytelny
  • test jest złożony, czyli jest większe prawdopodobieństwo, że pojawią się w nim błędy

Czemu test nie jest czytelny? Moim zdaniem byłby bardzo czytelny. test jest złożony Czemu miałby być bardziej złożony? Moim zdaniem nie jest.

piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

@Riddle: Spadek czytelności wynika z rozmiaru - jest dłuższy, więc siłą rzeczy będzie mniej czytelny od serii małych scenariuszy typu:

Kopiuj
upewnij się, ze Janka nie ma w serwisie(GET)
wstaw Janka(POST)
sprawdź, czy obiekt został stworzony (GET)

I oczywiście mam na myśli testowanie systemu na poziomie zewnętrznego API, to gdzie te dane są fizycznie przechowywane, to już detal implementacyjny. No i dodatkowa zaleta granularnych testów, jak coś walnie, to obszar poszukiwań jest mniejszy.
Do tego rozwlekłość testu, powoduje, że chcemy go podzielić na kawałki i pojawia się kolejne pytanie, co testuje te kawałki.

Oczywiście tak sobie gdybam i szukam dziury w całym.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
2
piotrpo napisał(a):

I oczywiście mam na myśli testowanie systemu na poziomie zewnętrznego API, to gdzie te dane są fizycznie przechowywane, to już detal implementacyjny. No i dodatkowa zaleta granularnych testów, jak coś walnie, to obszar poszukiwań jest mniejszy.
Do tego rozwlekłość testu, powoduje, że chcemy go podzielić na kawałki i pojawia się kolejne pytanie, co testuje te kawałki.

Oczywiście tak sobie gdybam i szukam dziury w całym.

Nie no, tutaj masz rację.

piotrpo napisał(a):

@Riddle: Spadek czytelności wynika z rozmiaru - jest dłuższy, więc siłą rzeczy będzie mniej czytelny od serii małych scenariuszy typu:

Kopiuj
upewnij się, ze Janka nie ma w serwisie(GET)
wstaw Janka(POST)
sprawdź, czy obiekt został stworzony (GET)

Aha, nie no to oczywiście że musisz go podzielić na jeszcze mniejsze kawałki. Myślałem że jak wypisałeś te elementy: sprawdź, czy baza jest pusta(), utwórz konto użytkownika(), sprawdź czy konto użytkownika istnieje(), dokonaj zmian na tym koncie(), sprawdź czy zmiany się zapisały(), usuń konto(), sprawdź, czy się usunęło() to mówisz o sześciu osobnych testach :D

Testy powinny być malutkie, najmniejsze jak się da. Jeśli jesteś w stanie rozbić test na dwa mniejsze - zrób to. Testy powinny wyglądać jakoś tak (kod poglądowy):

Kopiuj
# given
app = application_with_no_users
# when
response = app.get_user() # to zawoła GET /users albo GET /user/:id pod spodem
# then
assert response. jakoś tutaj sprawdź czy zwróciło Ci jakieś info o braku usera
Kopiuj
# given
app = application_with_one_user # tutaj pod spodem zawołaj POST /user żeby stworzyć jakiegoś usera
# when
response = app.get_user()
# then
assert response.user # tutaj zrób asercje która sprawdzi czy jest dodany tutaj user
Kopiuj
# when
app = application_with_no_users
# when
app.add_user("Marek") # tutaj strzel pod POST /user pod spodem
# then
user = app.get_user # tutaj strzel pod GET /user/marek jakoś
assert user.name == "Marek"
Kopiuj
# given
app = application_with_one_user
# when
app.delete_user("marek")
# then
user = app.get_user  # tutaj strzel pod GET /user/marek jakoś
assert response # zrób jakąś asercje żeby sprawdzić czy ten user co go właśnie wczytałeś nie istnieje

No i tak dalej, dla każdej metody i funkcjonalności, np:

Kopiuj
# given
app = application_with_one_user(name="Marek")
# when
app.add_user("Marek")  # duplikacja imienia
# then
users = app.get_users_by_name("Marek")
assert len(users) == 1 # upewnij się że nowy user się nie dodał
edytowany 1x, ostatnio: Riddle
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

Tak, ale z drugiej strony (wiem, że kłócę się sam z sobą i w sumie tak jest...), pełen scenariusz testowy (taki długi...), obejmuje pełen cykl życia obiektu, co też (chyba) wnosi jakąś wartość, bo to, że jesteśmy w stanie wykonać pojedyncze zmiany na różnych obiektach, nie oznacza jeszcze, że będzie możliwe wykonanie sekwencji zmiany stanów na pojedynczym obiekcie.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
2
piotrpo napisał(a):

Tak, ale z drugiej strony (wiem, że kłócę się sam z sobą i w sumie tak jest...), pełen scenariusz testowy (taki długi...), obejmuje pełen cykl życia obiektu, co też (chyba) wnosi jakąś wartość,

To jest Twoja intuicja i próba zrozumienia tego procesu jako człowiek, a nie faktyczna wiedza wynikająca z praktyk programistycznych.

piotrpo napisał(a):

bo to, że jesteśmy w stanie wykonać pojedyncze zmiany na różnych obiektach, nie oznacza jeszcze, że będzie możliwe wykonanie sekwencji zmiany stanów na pojedynczym obiekcie.

Oznacza. Jeśli napisałeś 10 malutkich testów na kawałek "scenariusza", to one znajdą wszystkie błędy które byłby w stanie znaleźć jeden duży test. Tzn. jeśli dodatkowo napiszesz ten "długi test" to on nie będzie w stanie znaleźć niczego, czego te 10 małych testów by nie znalazły. W nomenklaturze TDD, nie będziesz w stanie spowodować że ten długi test sfailuje, w momencie w którym te małe testy nie failują - Oczywiście zakładając że w tych małych testach nie ma błędów.

Pamiętaj że odpowiednie # given to gwarantuje, tzn. przed wykonaniem testu powinieneś doprowadzić aplikację do pewnego znanego stanu; i tym znanym stanem może być dokładnie ten stan w jakim apka została zostawiona przez poprzedni test; ale już bez zależności na ten poprzedni test. Jak napiszesz "duży długi test" to dodajesz tą zależność, tylko że nie jawnie.


Jeśli masz trzy pliki w katalogu, to żeby je usunąć wystarczy zrobić

Kopiuj
rm one.txt
rm two.txt
rm three.txt

Możesz dodatkowo uruchomić jeszcze

Kopiuj
rm *

ale on już jest niepotrzebny (chyba że tak na prawdę w katalogu było więcej plików, ale wtedy te pierwsze rm powinny być zaktualizowane).

edytowany 4x, ostatnio: Riddle
Schadoow
  • Rejestracja:około 13 lat
  • Ostatnio:około godziny
  • Postów:1065
1

a scenariusz biznesowy to nie będzie już e2e :p ?

Ja podchodzę do tego od strony praktycznej tj szybki i dłuższe testy. W klasach które stawiają context apki czy uruchamiają kontenery do testów daje na koncu suffix IT ale ogólnie potrzebne mi to jest tylko do selektorow aby łatwo uruchamiać „szybkie” testy które uruchamiam lokalnie podczas pracy i długie które zazwyczaj uruchamiam po dłuższym dewelopowaniu lub wgl zostawiam to do uruchomienia na gitlabie.

Btw mam aplikacje w której testy e2e chodzą ok 3h. Chyba by mnie powaliło jakbym miał to uruchamiać lokalnie xD

edytowany 1x, ostatnio: Schadoow
Riddle
To określenie e2e to nic niewnosząca kategoria, tak samo jak "integracyjny" vs "jednostkowy". Autor chce po prostu napisać dobre testy CRUD'ów i nie bawić się w puste wymyślanie nazw czy etykietek.
Schadoow
Jasne, z tym się nie kłócę. Po prostu testy mogą testować implementacje albo wymagania biznesowe. I tutaj bym to delikatnie rozróżniał :)
Riddle
Moim zdaniem powinny testować tylko i wyłącznie wymagania biznesowe. Ale to nie jest powód żeby pisać długie testy zamiast małych.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
Schadoow napisał(a):

a scenariusz biznesowy to nie będzie już e2e :p ?

Ja podchodzę do tego od strony praktycznej tj szybki i dłuższe testy. W klasach które stawiają context apki czy uruchamiają kontenery do testów daje na koncu suffix IT ale ogólnie potrzebne mi to jest tylko do selektorow aby łatwo uruchamiać „szybkie” testy które uruchamiam lokalnie podczas pracy i długie które zazwyczaj uruchamiam po dłuższym dewelopowaniu lub wgl zostawiam to do uruchomienia na gitlabie.

Moim zdaniem nie powinieneś używać do tego suffixów, tylko elementów runnera testów, np w pytest jest @mark, w jUnit jest @group, możesz pooznaczać testy tagami "slow" i "fast", i potem uruchamiać albo szybkie, albo wolne testy według konieczności. Możesz też napisać fixture który najpierw odpala wszystkie szybkie, a na samym końcu wszystkie wolne - krótszy feedback loop. Możesz też zrobić, że test sam z siebie dodaje tag "slow", jeśli wykonuje operacje które są ciężkie (typu, jeśli wywołasz metodę SetupContextApp(), to "dodaj tag slow z automatu).

Schadoow napisał(a):

Btw mam aplikacje w której testy e2e chodzą ok 3h. Chyba by mnie powaliło jakbym miał to uruchamiać lokalnie xD

Kogoś powaliło że napisał testy które trwają 3h ;| To jest bardzo słabe że jest na to zgoda w zespole, i faktycznie przed deployem trzeba czekać 3h na pełen suite. Przecież tak się nie da pracować. I bardzo fajna konsekwencja - nie odpalasz testów lokalnie, tylko się odpalają na gitlabie - czyli nie widzisz rezultatu z testów bezpośrednio, tylko dopiero jak zostało to odroczone.

Mam nadzieję że się nie obrazisz, jeśli powiem że rady od kogoś kto ma w projekcie testy które się wykonują 3h i który nie widzi w tym nic dziwnego; jako mniej godne zaufania? :>

edytowany 1x, ostatnio: Riddle
Schadoow
  • Rejestracja:około 13 lat
  • Ostatnio:około godziny
  • Postów:1065
1

@Riddle: nie obrazę się. Aczkolwiek jestem z nich zadowolony bo testują mi cała logikę biznesowa i od 2016 nie mieliśmy zgłoszeń produkcyjnych. Co prawda nie jest to apka której uzywa dużo osób ok 500 osób dziennie.

Przy czym średnio widze szanse aby to przyspieszyć xD. Bo to nie chodzi o wielkość testów tylko liczbę use casow i ścieżek użytkowników. Dla przykładu dla bazy oracla kiedyś czytałem ze testy chodzą dwa dni xD

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
Schadoow napisał(a):

@Riddle: nie obrazę się. Aczkolwiek jestem z nich zadowolony bo testują mi cała logikę biznesowa i od 2016 nie mieliśmy zgłoszeń produkcyjnych. Co prawda nie jest to apka której uzywa dużo osób ok 500 osób dziennie.

No, to bardzo fajnie. Ale na prawdę dobre testy to takie które również testują całą logikę biznesową, i takie dzięki którym nie ma wyjątków produkcyjnych oraz wykonują się bardzo szybko, powiedzmy w ciągu max. 5-10 minut.

Robimy offtop, @piotrpo pytał o coś innego.

edytowany 1x, ostatnio: Riddle
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

Z tym czasem trwania testów, to trochę zależy od tego ile razy kontekst aplikacji jest stawiany, jak ciężki jest ten kontekst. To wyżej, to jakieś tam moje wprawki z pet project, gdzie przy okazji rozpoznaję działanie ktor i jego narzędzi wspomagających pisanie testów. Tutaj jest to "niby-klient" http. Ten test wyżej, przy aktualnie zamokowanej w pamięci bazie wykonuje się w kilkadziesiąt ms, więc teoretycznie można mieć tych scenariuszy dużo.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
piotrpo napisał(a):

Z tym czasem trwania testów, to trochę zależy od tego ile razy kontekst aplikacji jest stawiany, jak ciężki jest ten kontekst. To wyżej, to jakieś tam moje wprawki z pet project, gdzie przy okazji rozpoznaję działanie ktor i jego narzędzi wspomagających pisanie testów. Tutaj jest to "niby-klient" http. Ten test wyżej, przy aktualnie zamokowanej w pamięci bazie wykonuje się w kilkadziesiąt ms, więc teoretycznie można mieć tych scenariuszy dużo.

Testy które się wykonują długo (powiedzmy dłużej niż 10 minut) są całkowicie nieakceptowalne. Każdy kto mówi że takie testy są spoko

No bo patrz, jeśli testy trwają długo to po prostu ludzie ich nie będą odpalać. Zostawią to na koniec dnia, albo odroczą do builda w gitlabie (tak jak @Schadoow). Ale żeby dobrze developować software, to musisz odpalać testy często, najczęściej jak się da, żeby pomogły w developmencie. Niestety test których odpalenie zostawia się na koniec dnia albo odpala w gitlabie nie pomagają w tym; więc stają się dużo gorzej i przynoszą dużo mniejszą wartość.

Z resztą, jakby to miało wyglądać. Odpalasz testy, czekasz 3h, jeden z ostatnich testów failuje; dodajesz poprawkę, znowu odpalasz testy; i za 3h dowiadujesz sie że gdzieś brakuje nawiasu albo spacji. Tak się nie da pracować. Równie dobrze mógłbyś w ogóle nie mieć testów.

Dla mnie, jedyny przypadek gdzie testy miałyby trwać 3h, to jest wtedy gdyby było około 300tys. przypadków testowych - ale to jest bardzo dużo, i nie sądzę że jakieś aplikacje mają coś takiego. Jeśli jest ich np 10tys. czyli taki standard mniej więcej, to 10 minut to jest max.

piotrpo napisał(a):

Z tym czasem trwania testów, to trochę zależy od tego ile razy kontekst aplikacji jest stawiany, jak ciężki jest ten kontekst.

Ten "kontekst aplikacji" to jest szczegół implementacyjny, należałoby go podmienić jeśli sprawia takie problemy.

edytowany 2x, ostatnio: Riddle
Schadoow
  • Rejestracja:około 13 lat
  • Ostatnio:około godziny
  • Postów:1065
0

To może temat na inny wątek ale osobiście w testach testujących „cała aplikacje”. Nie znalazłem rozwiązania aby osiągnąć separacje testów i jednocześnie benefit z tego ze stawiam cała infre. Przez to mogę testować zachowanie aplikacji i łatwo podbijać zależności np migracja wersji bazy danych czy update innych 3rd-part serwisów.

Bo jasne mogę testować in memory ale wtedy musiałbym do każdego serwisu zewnetrznego którego używam dopisać testy kontraktu co np w przypadku bazy danych może być trudne :)

Riddle
Nie musiałbyś.
Schadoow
No dobra to gdzie będę miał informacje ze aplikacja dalej działa ? I nie wysypie się bo np jakaś firma zrobiła breaking changes w zachowaniu api swojego serwisu.
Riddle
@Schadoow: załóż osobny wątek pod to: "Jak skrócić wywołania testów z 3h do 10 minut, tak żeby nadal testowały całą aplikacje".
Schadoow
Ok z chęcią jak będę miał chwile to przygotuje jakiś przykłady aby nie rozmawiać tylko hipotetycznie.
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

A to nie zależy trochę od rozmiaru testowanej aplikacji? Bo taki crud, to pójdzie szybko, ale texty black box systemu, który składa się z iluśtam dziesięciu mikrousług komunikujących się przez inne usługi (np. chmurowe), już lekki i szybki nie będzie.

Riddle
No tak; ja mówiłem o testach jednej aplikacji. Chociaż to też nie jest do końca prawda, bo można tak uruchamiać mikroserwisy na jednej maszynie, żeby podmienić sposób w jaki się komunikują żeby ich testy również były szybkie - np żeby ich komunikacja nie nawiązywała połączenia sieciowego, tylko żeby serializowały i deserializowały calle HTTP i komunikowały się między-procesowo zamiast przez kartę sieciową.
Schadoow
Ale na produkcji aplikacja komunikuje się przez połączenie sieciowe a nie między-procesowo. Podam przykład z życia, złe wybrałem implementacje z biblioteki javy do czytania plików przez co skipy zamiast natywnych wywołań robiły read i pomijały wynik czego pierwszym objawem było zapchanie się buforów na karcie sieciowej i wywalenie całego networku na clustrze. Czy twoje testy „między-procesowe” wylapaly by coś takiego ?
Schadoow
Dla wyjaśnienia bo to ważne dane byly trzymane na zewnątrz i komunikacja niżej była tłumaczona prze sambe.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
1
Schadoow napisał(a):

Ale na produkcji aplikacja komunikuje się przez połączenie sieciowe a nie między-procesowo. Podam przykład z życia, złe wybrałem implementacje z biblioteki javy do czytania plików przez co skipy zamiast natywnych wywołań robiły read i pomijały wynik czego pierwszym objawem było zapchanie się buforów na karcie sieciowej i wywalenie całego networku na clustrze. Czy twoje testy „między-procesowe” wylapaly by coś takiego ?

Mylisz dwie rzeczy jednocześnie, czyli testy zachowań biznesowych oraz testy niskopoziomowych mechanizmów - to są dwie różne rzeczy i powinny być przetestowane osobno. To że próbujesz je wrzucić do jednego wora, to najpewniej jeden z powodów czemu testy trwają 3h.

Zróbmy krok wstecz i obadajmy temat na spokojnie.

  • Masz powiedzmy 1000 przypadków testowych. Jeśli uruchomisz je tak, że każdy z nich wykona request przez kartę sieciową, to każdy z nich potrwa między 100ms-1000s; około można liczyć że potrwa to od 100-1000s, czyli około półtorej minuty do 16 minut. To już jest sporo za dużo. Można by ten czas skrócić, jeśli wyeliminować by z tych requestów overhead, czyli właśnie ruch między sieciowy.
  • Ale, jest pewien aspekt sieciowy który musisz przetestować, mianowicie różne niskopoziomowe elementy które pojawiają się tylko przy faktycznych requestach sieciowych; więc również musisz je przetestować;

No i jak tu pogodzić te dwie rzeczy? Wbrew pozorom bardzo prosto, tylko trzeba się oderwać od miskoncepcji i błędnych przekonań które nosimy w głowie. Napisz 1000 testów Twoich przypadków biznesowych na "lekkiej" podstawie, czyli takiej gdzie komunikacja jest najszybsza jak się da, np komunikatami w systemie operacyjnym albo inny sposób komunikacji między procesami. A potem, dodatkowo, napisz testy które nie sprawdzają przypadków testowych (więc nie musi być ich 1000), ale sprawdzają tylko czy ruch po siedzi działa tak jak powinien.

Wynikiem będzie 1000 testów które sprawdzają zachowania biznesoweg, oraz 10-20 testów które owszem są wolne, ale za to sprawdzają niskopoziomowe mechanizmy. Tych drugich testów nie musi być 1000, musi ich być tyle żeby wykazać że ruch po siedzi idzie sprawnie; będzie ich mało więc wykonają się szybko. Wynikiem będzie bardzo dobry test suite który wykonuje się szybko, a dodatkowo testuje niskopoziomowe mechanizmy.

Twoim błędem jest przekonanie, że jeśli mają być jakieś testy niskopoziomowe (a mają, bo chcesz to przetestować), to to oznacza że wszystkie testy muszą takie być - a nie muszą.

edytowany 2x, ostatnio: Riddle
Zobacz pozostałe 3 komentarze
Schadoow
Nie tyle dziwnego a nowego :). Trochę tłumaczenie się ale zazwyczaj jestem odpowiedzialny od gadania z biznesem, przez accesibility na froncie po infrastrukturę na backendzie i musze priorytetyzowac zadania mocno a jak sam tego nie zrobię to nie spotkałem się aby ktoś to zrobił xD. Wiec nie widziałem nigdy testów z boostowanym performancem. PS robię w całkiem sporych firmach xD i nie są to Janusz softy.
Riddle
@Schadoow: Dla mnie są, jeśli test suite trwa 3h i nikt z tym nic nie robi.
Schadoow
Wydaje mi się ze masz trochę zbyt idealistyczne podejście. Taki jetbrains do odpalania testów ma całe clustry setek malinek, nie mam pod ręka linku ale na blogu czytałem ze w oraclu testy trwają dni. Wiec gdzie szukać firm w których trwają minuty :p
Riddle
@Schadoow: Cóż. Moje projekty mają dużo testów które również testują całą aplikacje, a jednak wykonują się 10 minut. Co mam teraz zrobić, specjalnie je wydłużyć do 3h, żeby nie być okrzyknięty idealistą na 4p?
Schadoow
Nie to miałem na myśli :). Dobra nvm przyjmuje uwagi dzięki za rady.
crejk
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 23 godziny
  • Postów:46
1
piotrpo napisał(a):

Czy powinienem przetestować ten serwis tak jak w przykładzie, czy rozbić test na kawałki? Nie pytam o to, czy nie pominąłem jakiegoś przypadku, czy gdzieś powinienem dodać kolejną asercję, tylko czy powinienem mieć ileś tam testów na poszczególne metody, czy raczej taki pełniejszy scenariusz biznesowy, który testuje system jako ~black box'a.

Możesz zastosować oba podejścia. Taki test akceptacyjny jest spoko, bo mówi nam czy nasza aplikacja robi, to co miała robić. Ale oprócz niego potrzebujemy dodatkowych, mniejszych testów, które będą też pokrywać inne przypadki.

TS
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 2 godziny
  • Postów:853
0

Czy to jest unit test? Nie, bo testujesz wiele unitów jeden po drugim. To czy robisz to przez warstwę http według mnie nie ma żadnego znaczenia. Gdybyś przetestował tylko i wyłącznie endpoint z getem to byłby to unit test.

Czy to jest integration test? https://martinfowler.com/articles/practical-test-pyramid.html#IntegrationTests - ta definicja nie zabrania wielu asercji w jednym integration teście.

Czy to jest acceptance test? Ktoś mógłby powiedzieć, że brakuje tutaj podpiętego scenariusza bdd.

Czy lepsze jest dużo unit testów czy jeden acceptance test? Według mnie to zależy od jakości kodu i wydatków na testy. Jeżeli klasy nie mają odpowiednio wydzielonych odpowiedzialności i api to unit testy będą ciągle się zmieniały przez co będziesz wolniej dostarczał nowe funkcjonalności. Jeżeli jesteś w stanie wydzielić niezależne moduły, które nie mają side effektów, które musisz mockować na różne dziwne sposoby to warto inwestować w unit testy. W przeciwnym wypadku według mnie lepiej inwestować w acceptance/bdd testy. Ewentualnie testy integracyjne.

piotrpo
No to jak ten test ma się nazywać i czy będzie TDD, BDD, czy inne DD jest dla mnie najmniej istotne.
Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0

Kolejną miskoncepcją z którą warto się zmierzyć jest "Proste rzeczy łatwo się testuje". Tą miskoncepcję (mit) bardzo trudno wykorzenić, bo większość programistów uczy się pisać aplikacje od aplikacji konsolowych - hello worldy; i w danym języku własnie od tego z reguły zaczynami: print()/input() w pythonie, czy System.out.println()/Scanner(System.in) w Javie, etc. Czyli już na starcie piszemy aplikacje które mają zależności - na wyjście i wejście standardowe procesu w którym uruchamiamy naszą aplikację.

Pisząc dobre testy, chcemy przetestować takie rzeczy poprawnie; ale bardzo często język ani biblioteka standardowa nie udostępnia nam łatwych narzędzi żeby to przetestować - musimy to więc jakoś ogarnąć sami; i niestety napisanie testu który poprawnie sprawdza stdin/stdout - czyli najbardziej podstawowe rzeczy od których się zaczyna jest względnie trudne. Osoba która zaczyna programować nie będzie umiała tego zrobić; a osoby które już programują długo często nie decydują się tego robić; mimo że powinny.

Ale pamiętajmy - to żę w jakimś języku lub frameworku nie ma łatwych narzędzi do przetestowania tego; nie znaczy że my nie powinniśmy sami tego przetestować - jeśli zajdzie potrzeba, trzeba opisać takie testy samemu, niestety.

twoj_stary_pijany napisał(a):

Czy to jest unit test? Nie, bo testujesz wiele unitów jeden po drugim. To czy robisz to przez warstwę http według mnie nie ma żadnego znaczenia. Gdybyś przetestował tylko i wyłącznie endpoint z getem to byłby to unit test.

Czy to jest integration test? https://martinfowler.com/articles/practical-test-pyramid.html#IntegrationTests - ta definicja nie zabrania wielu asercji w jednym integration teście.

Czy to jest acceptance test? Ktoś mógłby powiedzieć, że brakuje tutaj podpiętego scenariusza bdd.

Czy lepsze jest dużo unit testów czy jeden acceptance test? Według mnie to zależy od jakości kodu i wydatków na testy. Jeżeli klasy nie mają odpowiednio wydzielonych odpowiedzialności i api to unit testy będą ciągle się zmieniały przez co będziesz wolniej dostarczał nowe funkcjonalności. Jeżeli jesteś w stanie wydzielić niezależne moduły, które nie mają side effektów, które musisz mockować na różne dziwne sposoby to warto inwestować w unit testy. W przeciwnym wypadku według mnie lepiej inwestować w acceptance/bdd testy. Ewentualnie testy integracyjne.

Moim zdaniem używanie takich kategorii nie ma sensu; i lepiej mówić o cechach testów: czy jest szybki/wolny, czy ma duży scope czy mały; czy ma dużo zależności czy nie; czy failuje jeśli brakuje mu pewnych zależności; czy zna szczegóły implementacyjne kodu czy nie; czy polega na frameworku czy nie.

Używając takich kategorii jak jednostkowy/integracyjny/akceptacyjny/e2e dojdzie tylko do nieporozumienia bo różne osoby inaczej rozumieją te testy; te określenia polegają na założeniach które właściwie nie istnieją.

TS
Zalinkowałem do literatury, wystarczy ją przeczytać, żeby mieć podobne rozumienie tych testów.
Riddle
@twoj_stary_pijany: To się nie uda. Te określenia zbyt głęboko weszły w całą naszą branżę, i zakomunikowanie jej teraz "hej, słuchajcie: tutaj jest literatura, teraz tymi definicjami się posługujemy" jest już niemożliwa. Lepiej w ogóle nie posługiwać się tymi terminami, i skupić się na samych kryteriach.
TS
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 2 godziny
  • Postów:853
0
Riddle napisał(a):

Kolejną miskoncepcją z którą warto się zmierzyć jest "Proste rzeczy łatwo się testuje". Tą miskoncepcję (mit) bardzo trudno wykorzenić, bo większość programistów uczy się pisać aplikacje od aplikacji konsolowych - hello worldy; i w danym języku własnie od tego z reguły zaczynami: print()/input() w pythonie, czy System.out.println()/Scanner(System.in) w Javie, etc. Czyli już na starcie piszemy aplikacje które mają zależności - na wyjście i wejście standardowe procesu w którym uruchamiamy naszą aplikację.

@twoj_stary_pijany: To się nie uda. Te określenia zbyt głęboko weszły w całą naszą branżę, i zakomunikowanie jej teraz "hej, słuchajcie: tutaj jest literatura, teraz tymi definicjami się posługujemy" jest już niemożliwa. Lepiej w ogóle nie posługiwać się tymi terminami, i skupić się na samych kryteriach. — Riddle 2 sekundy temu

Nie rozumiem dlaczego mielibyśmy teraz obniżać standardy do ludzi, którzy piszą kod produkcyjny na zasadzie produkowania side effektów i zrzucania winy na QA, że nie dają rady tego otestować. Jak mam coś otestować i widzę źle zaprojektowany interface to idę do developera i zadaję mu pytanie czy zastanawiał się jakie skutki niesie za sobą projektowanie takiego interface'u. Skutki, które sprawiają, że i on, i inni będziemy zarabiali mniej pieniędzy, a co najmniej odwlecze nam się podwyżka bo zarząd nie będzie widział przyrostu. I zakładam, że jeżeli developer ma dobre intencje w stosunku do projektu to weźmie i przeczyta literaturę i się poprawi. Najlepiej przyjść do projektu, zobaczyć gówniany kod i później płakać na forum, że się zwalniam bo nikt mnie nie rozumie.

I ja się w pełni zgadzam ze stwierdzeniem, że proste rzeczy łatwo się testuje. Z tym, że proste rzeczy niełatwo się pisze, trzeba do tego kompetencji. Napisanie prostego interface'u wymaga lat doświadczeń. Kiedy Messi strzela bramkę to wszystko wydaje się proste, ale nie jest łatwe. Właśnie po to czytamy literaturę, żeby się dokształcać i po to właśnie jest ten temat.

piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

@crejk: Jasne, tylko pytanie co to są te inne przypadki. Bo jeżeli aplikacja robi, to co jest opisane w wymaganiach, ten zakres jest pokryty przypadkami testowymi, to powstaje pytanie co robią linijki, które tymi przypadkami pokryte nie są? Chyba, że mówisz o przypadkach negatywnych typu "co się stanie jak poproszę o dane użytkownika, który nie istnieje" itd.

@twoj_stary_pijany: Tu nie chodzi o obniżanie standardów. Obniżenie standardów, to jest korpo nakaz zwiększenia pokrycia testami, które następnie jest realizowane przez gromady przypadków testowych na niskim poziomie (pojedynczej klasy), obudowane mockami.
Podszedłem do tego co napisałem tak, że skoro opisuję testem całe wymagania, dopisuję do tego kod, który te wymagania spełnia, to w wyniku tego działania, przetestowane zostaje zarówno, czy aplikacja robi to co miała robić, jak i to, czy klasy wewnątrz faktycznie robią to, co leży w ich odpowiedzialnościach, oraz czy "dogadują" się ze sobą.

Nie zostaje przetestowana w wyniku tego testu warstwa sieciowa (akurat Ktor pozwala na wpięcie się bezpośrednio do kontrolera, widoczne w teście endpointy można traktować jako "nazwy metod"), oraz połączenie z bazą danych (bo szybkość działania testu i moje lenistwo).

TS
  • Rejestracja:ponad 5 lat
  • Ostatnio:około 2 godziny
  • Postów:853
0
piotrpo napisał(a):

@twoj_stary_pijany: Tu nie chodzi o obniżanie standardów. Obniżenie standardów, to jest korpo nakaz zwiększenia pokrycia testami, które następnie jest realizowane przez gromady przypadków testowych na niskim poziomie (pojedynczej klasy), obudowane mockami.
Podszedłem do tego co napisałem tak, że skoro opisuję testem całe wymagania, dopisuję do tego kod, który te wymagania spełnia, to w wyniku tego działania, przetestowane zostaje zarówno, czy aplikacja robi to co miała robić, jak i to, czy klasy wewnątrz faktycznie robią to, co leży w ich odpowiedzialnościach, oraz czy "dogadują" się ze sobą.

Coś co określasz korpo nakazem to są konwencje kultu cargo wymuszone przez konkretnych ludzi, którzy nie czytają literatury. Takich ludzi spotykam u każdego klienta i zwykle, żeby naprawić projekt to trzeba zacząć od zidentyfikowania tej osoby (trudne), a następnie pogadania z nią (rzadko przynosi skutek) lub też jej usunięcia z projektu (nie zawsze możliwe). Ale nie mów mi, że to są korpo nakazy. Bo korpo nie myśli, a ktoś personalnie te nakazy stworzył.

Żeby przetestować unit nie potrzebujesz wszystkiego mockować. Możesz po prostu użyć stubów. Czyli masz moduł, który ma zależność na jakiś obiekt. Tworzysz uproszczony obiekt implementujący interface tych zależności, a następnie wstrzykujesz ten obiekt. Jeżeli dobrze zaprojektujesz warstę domeny biznesowej to możesz w prosty sposób usunąć zależność na bazę danych. To podejście też jest opisane w tym artykule, który podlinkowałem. Oczywiście ten artykuł też nie jest idealny bo wspomina o class testach. Żeby mieć dobry obraz unit testów trzeba przeczytać kapkę więcej.

piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 14 godzin
  • Postów:3277
0

No dobra, przeczytałem ten artykuł, tylko się z nim nie zgadzam.

  1. Trzymanie się uparcie rozumienia Unit, jako "klasy"
  2. Przecenianie znaczenia unit testów

O ile punk 1 to w sumie przedmiot dyskusji w tym wątku i jakaś tam moja opinia, to spróbuję uzasadnić punkt 2 na podstawie tego co w tym artykule widzę:

Kopiuj
public class ExampleControllerTest {

    private ExampleController subject;

    @Mock
    private PersonRepository personRepo;

    @Before
    public void setUp() throws Exception {
        initMocks(this);
        subject = new ExampleController(personRepo);
    }

    @Test
    public void shouldReturnFullNameOfAPerson() throws Exception {
        Person peter = new Person("Peter", "Pan");
        given(personRepo.findByLastName("Pan"))
            .willReturn(Optional.of(peter));

        String greeting = subject.hello("Pan");

        assertThat(greeting, is("Hello Peter Pan!"));
    }

    @Test
    public void shouldTellIfPersonIsUnknown() throws Exception {
        given(personRepo.findByLastName(anyString()))
            .willReturn(Optional.empty());

        String greeting = subject.hello("Pan");

        assertThat(greeting, is("Who is this 'Pan' you're talking about?"));
    }
}

Testowana klasa ma odpowiedzialność polegającą na:

  • Odebraniu parametru identyfikującego użytkownika

  • Wyszukaniu konta w repozytorium odpowiadającego temu parametrowi

  • Zwróceniu tekstu powitania na podstawie tych danych.

    Test, który wkleiłem sprawdza wyłącznie ostatnią część tej odpowiedzialności. Ok, możemy mieć jeszcze jakieś testowanie samego repozytorium, ale załóżmy, że ktoś zamiast zwrócić się do repozytorium przez .findByLastName() postanowi pobrać wszystkie konta i przeszukać je lokalnie persons.stream().filter(). Test wysypie się, bo to co sprawdzają pierwsze 2 punkty, to prawidłowa konfiguracja mocka, a nie działanie SuT.

Do napisania tego testu trzeba nie tylko wiedzieć z jakich zależności korzysta klasa, ale też w jaki sposób z nich korzysta i to jest moim zdaniem coś, co mocno obniża wartość tego typu testu.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:3 dni
  • Lokalizacja:Wrocław
0
piotrpo napisał(a):

Regularnie pojawiają się dyskusje, czy testować "integracyjnie", czy "jednostkowo" i jedyne co wiem, to to, że chyba nikt na świecie nie wie, czym się różnią oba rodzaje testów.

Ja wiem.

Czy powinienem przetestować ten serwis tak jak w przykładzie, czy rozbić test na kawałki?

Ja u siebie zazwyczaj rozbijam - wtedy od razu widać, który endpoint padł.

Riddle
Administrator
  • Rejestracja:prawie 15 lat
  • Ostatnio:2 minuty
  • Lokalizacja:Laska, z Polski
  • Postów:10056
0
twoj_stary_pijany napisał(a):

I ja się w pełni zgadzam ze stwierdzeniem, że proste rzeczy łatwo się testuje.

Ale no właśnie często łatwe rzeczy trudno przetestować, niestety.

edytowany 1x, ostatnio: Riddle
Riddle
@twoj_stary_pijany: Czytałeś post który napisałem? Np print() i input() w pythonie to jest bardzo prosta rzecz, ale nie jest tak łatwo ją przetestować dobrze.
TS
Czytałeś moją krytykę?
TS
A poza tym, nie jest to tak trudno przetestować. Jak wspomniałeś, są do tego różne utilsy.
Riddle
@twoj_stary_pijany: Ale użycie tych utilsów do napisania dobrych testów jest trudniejsze niż po prostu użycie print() i input(). Odczytanie pliku to jest with open(), ale przetestowanie czytania plików to już dziesiątki linijek.
TS
Ok, ale co dokładnie testujemy? Jakiś customowy kod czy stricte funkcję open?
TS
I dlaczego zakładasz, że test nie może być trudniejszy niż implementacja? Z reguły udowodnienie prawdziwości wzoru matematycznego jest dużo trudniejsze niż jego wymyślenie. Tak samo przetestowanie samochodu brzmi trudniej niż jego wyprodukowanie.
Riddle
No przecież ja dokładnie o tym mówię. że test jest trudniejszy niż implementacja.
TS
Ok, to z takim rozumieniem się zgadzam.
Riddle
No przecież dokładnie to napisałem w poście: Ale no właśnie często łatwe rzeczy trudno przetestować, niestety.
TS
To zdanie jest jak dla mnie sporym uproszczeniem. Używasz wymiennie słów proste oraz łatwe. Coś może być proste, a nie być łatwe. Tak samo coś łatwego może być trudne do przetestowania bo jest zbyt skomplikowane. Dobra inżynieria polega na tym, że przy projektowaniu już myślisz o tym jak będzie testowana bo musisz umieć wykazać poprawność swojego kodu. Czy to testami automatycznymi czy manualnymi.
TS
Np. projektując samochód już myślisz o tym jak będzie wyglądała strefa zgniotu w crash testach.
TS
Ale tu odbiegamy od tematu bo akurat open w Pythonie łatwo przetestować. Tak samo jak składnię with. Tak samo, żeby przetestować jakieś inputy potrzebujesz prostych utilsów, więc nie popadajmy tutaj w astronautykę. To jest łatwe i proste. Dlatego prosiłem o te przykłady.
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)