Jak szczegółowe winny być testy?

Jak szczegółowe winny być testy?
KM
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 4 lata
  • Postów:473
1

Moich problemów z testami ciąg dalszy. (Nienawidzę ich...)

Spotkałem się z co najmniej dwoma podejściami:

  1. Testy muszą być w opór dokładne, mają sprawdzać każdy możliwy edge case, mają ogarniać całą funkcjonalność aplikacji. Albowiem: (a) testy same w sobie stanowią specyfikację aplikacji; (b) to testy informują o tym, czy aplikacja jest poprawna, zatem każdy nieprzetestowany obszar czy edge case z definicji nie działa. (Z tego wynika także, że unit testy nie wystarczają: potrzebne są tylko / a może wręcz jedynie / testy na poziomie modułu i testy end to end; co oznacza, że aplikacja GUI nie działa, dopóki nie postawimy bota klikającego w nią)

  2. Testy pisane według punktu 1. wymagają gigantycznych ilości czasu i wysiłku. Zazwyczaj nie jest to opłacalne. W większości wypadków straty przedsiębiorstwa nie będą dotkliwe, jeśli poprawność zejdzie z 99.9% na 99%, natomiast poświęcenie 2x (4x, 10x) więcej roboczogodzin na pisanie testów już będzie dotkliwą stratą. Dlatego testy powinny sprawdzać tylko "big picture", czy aplikacja w ogólności działa, bez wchodzenia w multum szczegółów.

Podejścia drugiego nie rozumiem: wydaje mi się, że zamiast dwóch pieczeni (gwarancja poprawności i rozsądne ilości czasu i pracy) osiąga dwa trupy. To, czy aplikacja w ogólności działa, to po prostu widać. Nie trzeba pisac testów, by to sprawdzic. (Niezależnie od tego, czy piszemy testy czy nie, i tak nie unikniemy ręcznego uruchomienia naszej apki prędzej czy później!) To właśnie rozmaite edge case'y to jest to, co jest trudne do ręcznego sprawdzania i z tego powodu wartość dodaną zdaje się wnosić właśnie sprawdzanie edge case'ów. Sensem i celem pisania testów automatycznych jest właśnie gwarancja poprawności: chcemy wiedzieć, że jeśli testy przechodzą, to apka działa, i nie trzeba więcej nic sprawdzać, można puszczać na produkcję. Ale testy pisane według punktu 2. nie mogą dać takiej gwarancji. Zatem takie testy nic nie wnoszą ale nadal wymagają poświęcenia czasu na ich pisanie i utrzymywanie.

Z drugiej strony pozostaje faktem, ze podejście pierwsze wymaga gigantycznych ilości roboczogodzin. Możliwych kombinacji, które mogą cos popsuć (w zależności od zastosowania) może być przecież wykładniczo wiele. Co więcej, trudno zoptymalizować takie testy (unit testy juz łatwiej się optymalizuje), a skoro z założenia w tym podejściu testów ma być wiele, to łatwo o problemy wydajnościowe. I chociaż w internecie wielu gorliwie optuje za testami według punktu 1., to jednak nie można zaprzeczyć, że dwukrotne wydłużenie czasu pisania aplikacji rzeczywiście jest dotkliwą stratą dla firmy i nie można także zaprzeczyć, że w wielu zastosowaniach jest kompletnie nieistotne, czy apka będzie się crashować raz na rok czy raz na miesiąc.

Oczywiście, testy według punktu 1. są niezbędne, jeśli jakikolwiek błąd w działaniu apki będzie miał dramatyczne konsekwencje. Ale nie każdy program to sterownik rozrusznika serca albo komputer pokładowy statku kosmicznego.

Z punktem 1. jest jeszcze jeden problem: z definicji edge case'y sa trudne do zauważenia, a jeśli nie widzimy, że jakiś problem może występować, to trudno pisać test gwarantujący, że ten problem nie wystąpi. Zatem nawet najbardziej szczegółowe testy nie gwarantują poprawności. Niektóre problemy mają niemiły zwyczaj objawiać się dopiero podczas używania aplikacji. (M.in. problemy związane ze współbieżnością, problemy występujące przy pisaniu programów próbujących synchronizować jakoś działanie niezależnych od siebie zewnętrznych serwisów / aplikacji, gdzie dziwactwa w działaniu tych aplikacji mogą stwarzac problemy, etc etc).

Ale jeśli punkt 1. odrzucamy jako nieopłacalny, a punkt 2. odrzucamy jako bezsensowny, to co nam pozostaje? Brak testów?

A może raczej pisać testy przeciwko tym edge case'om, od których się odbijemy już po napisaniu aplikacji? Jeśli jakiś problem został przeoczony podczas pisania aplikacji, to wtedy może jest sens zagwarantować, że nie zostanie on przeoczony nigdy więcej? Takie testy oczywiście nie będą mogły dać gwarancji, że program działana (bo z definicji testujemy tylko pojedyncze problemy, a nie całość oczekiwanego działania), ale w przeciwieństwie do testów według punktu 2. zdają się jednak wnosić jakąś wartość dodaną.

A może to ja się mylę? Są szefowie, którzy wymagają właśnie testów takich, jakie opisałem w punkcie 2. Byc może punkt 2 jednak nie jest aż tak bezsensowny, jak mi się zdawało: jeśli aplikacja ma liczne ficzery, to (choćby tylko powierzchowne) sprawdzenie ich wszystkich nadal jednak zwalnia nas z klikania. Tak, będą nieprzetestowane edge case'y, ale nie przed tym się bronimy, a tylko przed takimi ewentualnościami, że wprowadzenie jakieś pozornie niewinnej zmiany w jednym ficzerze całkowicie wali inny ficzer. Oczywiście problemy wykryte przez tak powierzchowne testy bardzo szybko objawiłyby się i bez tych testów (bo z definicji, jeśli takie testy nie przechodzą, to program w ogóle nie działa), ale o to właśnie chodzi, żeby przez przypadek nie zwalić całkowicie produkcji. Błędy, które będą wykryte przez takie testy, są bardzo groźne właśnie przez swą grubość. Wbrew temu, co pisałem wyżej, takie testy jednak wnoszą wartość dodaną: mamy gwarancję, że nie położymy wszystkiego bez konieczności przeklikiwania się przez całość aplikacji. (W zasadzie chyba jedyna sytuacja, przed którą takie testy by broniły, to taka sytuacja, że - jeśli np. piszemy Worda - zmiana w funkcji liczącej słowa powoduje wyjątki, gdy chcemy pogrubić tekst -- ale moze tego rodzaju sytuacje rzeczywiście się zdarzają, więc bronienie się przed nimi może i wnosi wartość dodaną?)

Jednak znowu: nawet wtedy unit testy wydają się być bezsensowne. Wartość dodaną wniesie tylko bot klikający w naszą apkę. Bo tylko to zagwarantuje to, co takie testy mają za zadanie zagwarantować - że w ogólności program działa i nie jest całkowicie zwalony.

edytowany 3x, ostatnio: kmph
V-2
"Do jasnych dążąc głębin - nie mógł trafić w sedno | Śledź pewien, obdarzony naturą wybredną. | Dokądkolwiek wędrował, zawsze nadaremno: | Tu jasno, ale płytko - tam głębia, lecz ciemno" (T. Kotarbiński)
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
4

Ja sugeruje zacząć od:

  • Testy akceptacyjne, czyli każdy use-case ma swój zestaw testów, tak że po puszczeniu ich wiesz ze dany ficzer działa, przynajmniej w podstawowej wersji
  • Testy regresji, czyli jak trafiliście na edge case to dopisujesz testy które go sprawdzają

Dalej to już według potrzeb - jak jest kawałek złożone logiki, to moze warto napisać do niego więcej testów.

Wartość dodaną wniesie tylko bot klikający w naszą apkę

A jak na poziomie testów wysyłasz takie same requesty do apki jak ten klikajacy bot? ;) Koszt dużo niższy, a pewność że działa nadal dość wysoka.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
edytowany 2x, ostatnio: Shalom
MA
  • Rejestracja:prawie 17 lat
  • Ostatnio:6 dni
  • Postów:644
5

Tests (1).png
Na czerwono - Testujemy według pkt. 2 (pobieżnie)
Na niebiesko - Według pkt. 1 (rygorystycznie)

edytowany 3x, ostatnio: Markuz
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:około 5 godzin
  • Lokalizacja:Wrocław
4
kmph napisał(a):

Ale jeśli punkt 1. odrzucamy jako nieopłacalny, a punkt 2. odrzucamy jako bezsensowny, to co nam pozostaje? Brak testów?

Raczej zdrowy rozsądek.
Podejście z punktu 1 zastosować do testów jednostkowych, w nich pokryć "każdy" przypadek brzegowy i typowe scenariusze użycia.
Podejście z punktu 2 zastosować do testów integracyjnych czy tam e2e, w nich sprawdzić, czy dany przypadek użycia w ogóle się uruchamia i działa dla optymistycznych scenariuszy.

Przykład dla 1, to np. kalkulator kwoty na fakturze - jednostka za to odpowiadająca musi sprawdzić, czy lista pozycji faktury nie jest pusta, czy wszędzie podano kwoty, i czy są nieujemne, czy stawki VAT istnieją, itd, i do tego wszystkiego da się napisać łatwo i szybko testy pokrywające wszystkie przypadki.
Przykład dla 2 to endpoint zwracający PDFa z fakturą. Tu wystarczy sprawdzić, czy:

  1. dla istniejącego ID faktury i zalogowanego użytkownika dostaniemy 200 i response zawierający PDF;
  2. dla istniejącego ID faktury i niezalogowanego użytkownika dostaniemy 401;
  3. dla nieistniejącego ID faktury i zalogowanego użytkownika dostaniemy 404.

A może raczej pisać testy przeciwko tym edge case'om, od których się odbijemy już po napisaniu aplikacji? Jeśli jakiś problem został przeoczony podczas pisania aplikacji, to wtedy może jest sens zagwarantować, że nie zostanie on przeoczony nigdy więcej?

Oczywiście - pierwszym krokiem naprawy buga jest napisanie testu, który ten błąd pokazuje. Potem można naprawić właściwy kod, no i dopóki nie usuniemy testu, to nie ma szans, że dokładnie ten sam błąd się powtórzy.

Zobacz pozostałe 8 komentarzy
Silv
Hm, jedyne, na co wpływa w sensie szeroko rozumianego wyjścia funkcji, to że funkcja wykonuje się zawsze maks. kilka razy więcej (to rekurencja).
somekind
Ok, no to brzmi jako coś do potencjalnej refaktoryzacji. Jeśli funkcja jest pokryta testami, to można ją poprawić, aby działała "na logikę" lepiej.
Silv
No tak, pokryta; właśnie znalazłem tego, hm, "buga" trochę przypadkiem, szukając przyczyn nieprzechodzenia testów innej funkcji. :) Dzięki za radę. Pomyślę. Osobiście nie lubię robić "na logikę", wolę wyraźne specyfikacje: w tym i w tym miejscu ma być to i to. No ale czy implementacja powinna mieć jakąkolwiek specyfikację? Na, ekhm, logikę – powinna…
somekind
No w pewnym sensie specyfikacją implementacji mogą być testy.
AreQrm
  • Rejestracja:prawie 11 lat
  • Ostatnio:24 dni
  • Lokalizacja:Londyn
  • Postów:873
3

Oprócz tego co zostało napisane, pomyślałbym o TDD.

Jasne, że jest kod super hiper ważny, np dotyczący transakcji, pieniędzy, kluczowych algorytmów, gdzie chcesz przetestować go na 130%, i kod który jak się coś popsuje nic się nie stanie (przycisk będzie zielony zamiast niebieskiego). Trzeba do tego podejść z rozsądkiem.

Jeżeli masz logikę do testowania, to zwykle lepiej i wydajniej jest ją testować jednostkowo (czy to testując klasę czy grupę klas - jednostkę kodu). W systemie, który ma dużo logiki klasyczna piramida testów sprawdza się znakomicie. Jak masz logikę aplikacji, mapery, przelotki to lepiej to testować End to end i/lub integracyjnie. W systemie readonly, albo bez logiki sprawdzi się raczej odwrócona piramida z mnóstwem testów e2e i małą liczbą jednostkowych.

Myślę, że największy problem to aplikowanie tego rozsądku i ludzie, którzy nauczyli się testować na jednym przypadku w danym kontekście i później mówią że albo testy jednostkowe są złe i nie potrzebne i tylko testy integracyjne dają im pewność ze system działa albo że testy integracyjne są złe, nie można im ufać i tylko jednostkowo warto testować. A tak naprawdę potrzebujemy wszystkich rodzajów testów, aplikowanych w każdym przypadku w innych proporcjach. Plus dziś zwłaszcza w świecie chmury i micro serwisów równie ważny jest monitoring i testy na produkcji ( które nie polegają na odpalaniu testów na produkcji i braku testów wcześniej, a raczej monitorowaniu jej i testach typu A/B testing, canary releases, feature toggles itp praktykach).


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)