Czy dobrym pomysłem jest posiadanie osobnych metod testowych dla każdego kroku?


10

Testuję interfejs API REST. Powiedzmy, że zwraca strukturę JSON. Jakie jest najlepsze podejście do testowania serwera? Każdy krok testowy może się powieść tylko wtedy, gdy wszystkie poprzednie zakończyły się powodzeniem.

Struktura A: przetestuj wszystko na raz

- Test method 1:
    - make server request
    - assert http response code was 200
    - assert returned file is not empty
    - assert returned file has valid JSON syntax
    - assert returned JSON contains key X

To wydaje się być najlepszym rozwiązaniem.

Zalety:

  • Tylko jedno żądanie serwera
  • Testuję zachowanie jako całość „Czy serwer zwraca JSON z kluczem X?”

Struktura B: stopniowo dodawaj potwierdzenia do każdego testu

 - Test method 1:
     - make server request
     - assert http response code was 200
 - Test method 2:
     - make server request
     - assert returned file is not empty
 - Test method 3:
     - make server request
     - assert returned file has valid JSON syntax
 - Test method 4:
     - make server request
     - assert returned JSON contains key X

W ten sposób zacząłem to robić i byłem przekonany, że tak powinno być, ponieważ każda metoda testuje tylko jedną rzecz, a to zapewnia lepszą separację. Ale teraz myślę, że ponieważ nie są to testy jednostkowe, moja separacja nie jest właściwa i powinienem przetestować zachowanie jako całość.

Struktura C: złóż zapytanie raz i uruchom osobne metody testowe na buforowaną odpowiedź

- make server request and cache it (allow read-only access)

 - Test method 1:
     - assert http response code was 200 on cached server request
 - Test method 2:
     - assert returned file is not empty on cached server request
 - Test method 3:
     - assert returned file has valid JSON syntax on cached server request
 - Test method 4:
     - assert returned JSON contains key X on cached server request

Zalety:

  • Brak powtarzających się (drogich) żądań serwera
  • Nadal ma metody testowe z jednym potwierdzeniem

Jakiej najbardziej sensownej struktury testowej użyć?


Przestań później zmieniać swoje pytanie w sposób, który unieważnia istniejące odpowiedzi! Dzięki.
Doc Brown,

Przepraszamy za niedogodności, ale czy zaproponowałbyś inaczej?
mrplow

Najpierw zastanów się dwa razy, czy naprawdę chcesz zmienić swoje pytanie w taki sposób. Jeśli naprawdę uważasz, że musisz dodać coś, co unieważnia niektóre odpowiedzi, możesz poinformować wszystkich autorów tych odpowiedzi, pozostawiając komentarz poniżej odpowiedzi, pytając, czy chcą zmienić lub dodać coś w tekście.
Doc Brown,

2
W rzeczywistości założyłem, że autorzy odpowiedzi są powiadamiani o zmianie pytania. Właśnie dlatego nie chciałem spamować komentarzy z oświadczeniami nie na temat. Powiadomię autorów w przyszłości. I dziękuję za udzielenie odpowiedzi na moje pytanie.
mrplow

Odpowiedzi:


3

Najlepsze praktyki zawsze mają cel, przyczynę. Zawsze dobrze jest wziąć pod uwagę te powody w swoim projekcie - szczególnie, gdy próbujesz zdecydować, w jaki sposób i jak trudno zastosować te najlepsze praktyki.

W tym przypadku głównym powodem, dla którego każdy test jest jednym, jest to, że jeśli pierwsza nie powiedzie się, druga nie zostanie przetestowana. Ponieważ zbyt wielu opiniotwórców wydaje się odnosić korzyści z rozbijania wszystkiego na możliwie najmniejsze bity i zawijania jak największej liczby wzdęć, zrodziło to pomysł, że każdy test powinien zawierać jedno stwierdzenie.

Nie śledź tego na ślepo. Nawet jeśli każdy test powinien przetestować jedną rzecz, powinieneś zastanowić się, jak duża lub mała powinna być każda „rzecz”. Aby to zrobić, musisz pamiętać, dlaczego chcesz, aby każdy test testował jedną rzecz - aby się upewnić błąd w pierwszej rzeczy nie pozostawia drugiej rzeczy niesprawdzonej.

Musisz więc zadać sobie pytanie - czy naprawdę potrzebuję tutaj tej gwarancji?

Powiedzmy, że jest błąd w pierwszym przypadku testowym - kod odpowiedzi HTTP nie jest 200. Więc zacznij hakować kod, dowiedz się, dlaczego nie otrzymałeś kodu odpowiedzi, i powinieneś rozwiązać problem. I co teraz?

  • Jeśli ręcznie uruchomisz test ponownie, aby sprawdzić, czy poprawka rozwiązała problem, powinieneś napotkać każdy inny problem ukryty przy pierwszej awarii.
  • Jeśli nie uruchomisz go ręcznie (może dlatego, że trwa to zbyt długo?) I po prostu wepchniesz poprawkę w oczekiwaniu na uruchomienie serwera testów automatycznych, możesz chcieć umieścić różne potwierdzenia w różnych testach. Cykle w tym przypadku są bardzo długie, więc warto podjąć wysiłek, aby odkryć jak najwięcej błędów w każdym cyklu.

Jest jeszcze kilka rzeczy do rozważenia:

Zależności asercji

Wiem, że testy, które opisałeś, są tylko przykładem, a twoje rzeczywiste testy są prawdopodobnie bardziej skomplikowane - więc to, co powiem, może nie być tak ważne przy tak dużej sile w prawdziwych testach, ale nadal może być nieco skuteczne, więc może to rozważyć.

Jeśli masz usługę REST (lub dowolny inny protokół HTTP), która zwraca odpowiedzi w formacie JSON, zwykle piszesz prostą klasę klienta, która umożliwia korzystanie z metod REST, takich jak zwykłe metody zwracające zwykłe obiekty. Zakładając, że klient ma osobne testy, aby upewnić się, że działa, porzuciłbym pierwsze 3 twierdzenia i zatrzymałbym tylko 4!

Dlaczego?

  • Pierwszy aser jest redundantny - klasa klienta powinna zgłosić wyjątek, jeśli kod odpowiedzi HTTP nie ma wartości 200.
  • Drugie twierdzenie jest zbędne - jeśli odpowiedź jest pusta, wynikowy obiekt będzie miał wartość NULL lub inną reprezentację pustego obiektu i nie będziesz musiał nigdzie umieścić klucza X.
  • Trzecie twierdzenie jest zbędne - jeśli JSON jest nieprawidłowy, dostaniesz wyjątek podczas próby jego przeanalizowania.

Nie musisz więc uruchamiać wszystkich tych testów - po prostu uruchom czwarty test, a jeśli wystąpią jakiekolwiek błędy w pierwszych trzech próbach wykrycia, test zakończy się niepowodzeniem z odpowiednim wyjątkiem, zanim jeszcze uzyskasz rzeczywiste potwierdzenie.

Jak chcesz otrzymywać raporty?

Załóżmy, że nie otrzymujesz wiadomości e-mail z serwera testowego, ale zamiast tego dział kontroli jakości przeprowadza testy i powiadamia o niepowodzeniu testów.

Jack z QA puka do twoich drzwi. Mówi, że pierwsza metoda testowa zawiodła, a metoda REST zwróciła zły kod odpowiedzi. Podziękujesz mu i zaczynasz szukać przyczyny.

Potem przychodzi Jen z kontroli jakości i mówi, że trzecia metoda testowa zawiodła - metoda REST nie zwróciła poprawnego kodu JSON w treści odpowiedzi. Mówisz jej, że już patrzysz na tę metodę, i wierzysz, że to samo, co spowodowało, że zwrócił zły kod wyjścia, spowodowało również, że zwróciła coś, co nie jest prawidłowym JSON, i wygląda bardziej jak ślad stosu wyjątku.

Wracasz do pracy, ale potem przybywa Jim z kontroli jakości, mówiąc, że czwarta metoda testowa zawiodła i nie ma klucza X w odpowiedzi ...

Nie możesz nawet poszukać przyczyny, ponieważ trudno jest patrzeć na kod, gdy nie masz ekranu komputera. Gdyby Jim był wystarczająco szybki, mógłby umknąć w czasie ...

E-maile z serwera testowego są łatwiejsze do odrzucenia, ale nadal - czy nie wolałbyś po prostu POWIADOMIĆ RAZ, że coś jest nie tak z metodą testową, i sam przejrzeć odpowiednie dzienniki testów?


3

Jeśli możesz bezpiecznie założyć, że żądanie serwera z tymi samymi parametrami zachowa się zawsze tak samo, metoda B jest prawie bezcelowa - dlaczego miałbyś wywoływać czterokrotnie tę samą metodę, aby uzyskać te same dane odpowiedzi cztery razy, gdy jedno wywołanie wystarczy?

A jeśli nie możesz tego bezpiecznie założyć i chcesz włączyć go do testu, możesz lepiej uruchomić test A wiele razy.

Jedyną hipotetyczną sytuacją, w której widzę, gdzie B może przynieść korzyść, jest to, że środowisko testowania pozwala na włączanie i wyłączanie tylko jawnych metod testowania, a Ty oczekujesz konieczności wykonania tego na poszczególnych etapach testu.

Alternatywa C wydaje się łączyć A z jedną korzyścią, o której wspomniałem powyżej dla B. Jeśli twoja platforma testowa pozwala na taką strukturę kodu, bez większego obciążenia powyżej B, jest to wykonalne podejście. Dodaje to jednak dodatkową złożoność A, więc użyłbym jej tylko wtedy, gdybym chciał kiedykolwiek włączać i wyłączać poszczególne testy, w przeciwnym razie stosuję zasadę YAGNI i trzymam się najprostszego rozwiązania (A).

TLDR: zacznij od A, jeśli masz pewność, że zawsze chcesz, aby wszystkie aserty były uruchamiane w jednym teście, przejdź do C, jeśli zauważysz, że potrzebujesz łatwiejszej kontroli z zewnątrz w odniesieniu do poszczególnych asercji.


0

Jak każdy kod, unikaj przedwczesnej optymalizacji. Najpierw napisz swoje testy, aby były łatwe do odczytania i utrzymania. Gdy testy zaczną być zbyt wolne, zoptymalizuj je. W dość prostym przykładzie zarówno A, jak i B będą łatwe do odczytania i utrzymania, więc wybierz, który z nich chcesz, dopóki sprawy nie staną się zbyt wolne (struktura B) lub zbyt skomplikowane (struktura A).

Jeśli Twój serwer jest bezstanowy, możesz zoptymalizować, porównując rzeczywistą odpowiedź z oczekiwaną odpowiedzią, aby sprawdzić całą wiadomość za jednym razem. Oczywiście będzie to kosztem czytelności.

Jeśli twój serwer jest w stanie i musisz wykonać wiele powolnych wywołań interfejsu API, aby ustawić serwer w stanie do testu, wówczas podejmij inne podejście, inaczej uruchomienie testów może potrwać kilka minut. Na przykład możesz uruchomić aktualizację bazy danych, aby wstrzyknąć dane do testowej bazy danych, aby szybko uzyskać obiekt w odpowiednim stanie do testowania. Test jest szybki i czytelny, ale trudniejszy w utrzymaniu. Alternatywnie możesz być w stanie napisać fasadę przed interfejsem API, więc wiele wywołań interfejsu API stanie się pojedynczymi wywołaniami interfejsu API, które bardziej pasują do testowanego procesu biznesowego.


0

Testy nie powinny się dzielić - od zera unikasz wpływu jednego testu na drugi. Umożliwia to także uruchamianie testów w losowej kolejności.
Dlatego droga C nie powinna być akceptowana.


Pisząc dowolny kod (a może nawet tworząc cokolwiek innego), zawsze zadawaj sobie pytanie: „po co taka praktyka?”
Dlaczego mówimy, że powinny istnieć różne testy na wszystko?

Są dwa przypadki, gdy potrzebujesz tego:

  1. gdy nie można polegać na „każdy etap testu może się powieść tylko wtedy, gdy wszystkie poprzednie zakończyły się powodzeniem”
  2. gdy twoje testy nie zawierają opisowych komunikatów potwierdzających

Są dwa powody, dla których napotykasz te przypadki:

  1. „Każdy etap testu może się powieść tylko wtedy, gdy wszystkie poprzednie zakończyły się pomyślnie”, tak naprawdę nie ma zastosowania do funkcji produktu
  2. nie masz wystarczającej wiedzy na temat produktu z powodu braku doświadczenia lub czasu lub nadmiernej złożoności produktu

Jeśli z jakiegoś powodu nie może zadeklarować co najmniej jeden z tych powodów, aby mieć miejsce tylko ślepo wziąć struktura B .


W przeciwnym razie (mam nadzieję, że tutaj) wybrać A .


Możesz również zadać to pytanie na stronie Stackexchange zapewniania i testowania jakości oprogramowania .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.