Pytest - jak zacząć?

0

Hej!
Jestem kompletnym żółtodziobem w dziedzinie testów. Zwykle moje testy polegały na "printowaniu" lub wykorzytywaniu debuggera do śledzenia działania programu.
Chciałbym jednak pójść krok dalej i poznać narzędzie pytest. Oczywiście zrobiłem sobie wstęp przez prześledzenie biblioteki i kilku przykładów.
Jednak takie przykłady nie pokażą w jaki sposób zawierać testowanie w projekcie.
Czy byłby ktoś w stanie dać mi kilka wskazówek dotyczących pytest?

Powiedzmy, ze mam pewien skrypt - jakieś klasy, kilka różnych metod:

class A:
     def __init(self):
          .....
     def metoda_1(self, a, b):
         return a+b

     def metoda_2(self, a, b):
         return a-b

     def metoda_3(self, a,b):
         return a*b

W jaki sposób używać, jak zawierać w projekcie, jak to wygląda "profesjonalnie"?
Pisać funkcje testujące dla każdej metody oddzielnie? Dla całych klas? W oddzielnych modułach dla każdej metody/funkcji?
Bardzo proszę o podpowiedzi, dosłownie każda informacja może okazać się przydatna :)
Dzięki z góry za zaangażowanie,
Pozdrawiam!

2

W sumie to znalazłem ciekawą lekturę na dokładnie ten temat.

Co do umiejscowienia testów - jak dla mnie grunt, żeby nie były upchane razem z kodem źródłowym. Powiedzmy, że kod źródłowy aplikacji jest jakimś podkatalogu projektu src - zatem na pewno nie chciałbym zobaczyć testów w src. To gdzie w takim razie? Na przykład w sąsiednim katalogu test. Fajnie by było również, gdyby struktura testów odzwierciedlała w jakiś sposób strukturę testowanego kodu źródłowego - łatwiej się wtedy odnaleźć w tym wszystkim, wiesz co gdzie powinno być wiedząc, jak wygląda projekt, widzisz już na pierwszy rzut oka, co jest, a czego brakuje.

W kwestii rozdziału testów na integracyjne, jednostkowe etc. wydaje mi się, że sensowniej jest trzymać je osobno, a nie razem np. per-testowana-klasa. Nie zawsze chcesz odpalać wszystkie testy naraz, możesz np. mieć zarówno jakieś lekkie i szybkie testy nadające się do częstego puszczania (bo nie spowalniają za bardzo developmentu) oraz osobno te najcięższe, które będziesz chciał puszczać rzadziej, na zasadzie "próby generalnej" nim uznasz, że zadanie skończone. Taki rozdział znacznie to ułatwia, a odpalenie wszystkich naraz nadal nie będzie sprawiało problemów.

Co do samego testowania - ot, piszesz np. klasę testową TestFoo, definiujesz, co ma się dziać:

  • przy ładowaniu klasy testowej
  • przy ładowaniu pojedynczego testu
  • po zakończeniu pojedynczego testu
  • po zakończeniu testów w klasie testowej

Czyli najpierw zapewniasz przygotowanie odpowiednich warunków do testowania, a później sprzątasz po teście - w zależności od potrzeb, oczywiście. Jeśli chcesz np. testować klasę Kalkulator która dodaje i odejmuje liczby podane jako parametry metod, no to wiele do inicjalizowania i sprzątania nie ma. Ale jeśli chcesz testować już niekoniecznie jednostkowo, a bardziej integracyjnie klasę, w dodatku jakoś symulując "żywy organizm" i jakieś "realne dane" i patrząc na szerszy kontekst tj. to jak ta klasa wchodzi w interakcje z resztą aplikacji, otoczeniem etc, no to takie metody nazwijmy to roboczo beforeTest / afterTest (wybacz, nie pamiętam jak dokładnie się nazywają w pytest) będą nieocenione.

Co do tego jak "mapować" testy np. na metody klasy itp. to wydaje mi się, że reguła jedna metoda - jeden test jest kiepska. Dostajesz w sumie dwa możliwe główne przypadki:

  • Twoje testy będą pokrywać jedynie jeden przypadek wykorzystania danej metody, prawdopodobnie najbardziej typowy (czyli ten, który prawdopodobnie i tak się nie wysypie). Czyli tak naprawdę niby będą, ale nie będą prawie niczego testować i przydadzą się jedynie po to, by móc powiedzieć, że jakieś tam są.
  • Twoje testy będą pokrywać wiele przypadków testowych naraz, co rodzi dodatkowe problemy. Po pierwsze, gdy któryś taki pod-test w teście się wysypie, no to siłą rzeczy pozostałe przypadki już się nie odpalą i nawet nie będziesz wiedział, czy są ok czy nie. Po drugie, pal licho testy jednostkowe, które niby nie powinno niczego tykać, ale w momencie gdy testujesz już jakąś imitację "żywego organizmu", to zmiana stanu spowodowana wykonaniem pierwszego przypadku może wpływać na działanie drugiego. Robi się z tego bajzel i z takiego testu co najwyżej się dowiesz, że B zadziała, jeśli najpierw wykonasz A.

Dlatego uważam, że jeden test powinien testować dokładnie jeden przypadek - przy czym dla każdej metody testowanej klasy (choć równie dobrze możesz i powinieneś chcieć testować czyste funkcje, jeśli masz jakieś zdefiniowane) takich przypadków testowych może (a nawet powinno) być wiele. Szczególnie oprócz jakichś przypadków "typowych" dobrze jest przetestować wszelkiej maści znane corner-case'y, zarówno pozytywne (prawidłowy input -> prawidłowy output) jak i negatywne (nieprawidłowy input -> pożądane zachowanie). Dobrze mieć zarówno testy jednostkowe (by mieć pewność, że pojedyncze bloczki spełniają swoją rolę) i integracyjne (by mieć pewność, że większe kawałki spięte z tych mniejszych bloczków też spełniają swoją rolę)

Jest jeszcze kwestia podejścia do testowania, np. Test Driven Development i tego, czy powinno się pisać testy do kodu czy kod do testów. Wydaje mi się, że dopóki obracasz się w obrębie jakiegoś swojego feature-brancha, odizolowanego od reszty świata, który dopiero w przyszłości będziesz chciał wypchnąć do master brancha czy tam developa, to różnicę widzisz przede wszystkim Ty - koniec końców w pull requeście znajdą się przecież i testy, i kod. Nie jestem żadnym ekspertem od testowania, TDD itd. ale widzę jedną zaletę - zaczynając zadanie od pisania testów wymuszasz na sobie to, by napisany później kod był jakkolwiek testowalny. W drugą stronę może być słabo, jeśli mając prawie gotowy moduł uświadomisz sobie, że strasznie nababrałeś i musisz przepisać wszystko od nowa, by móc w ogóle napisać porządne testy. A to się zdarza :D Niemniej jednak nawet, jeśli zastosujesz TDD, prawdopodobnie rzadko (albo nigdy) będzie tak, że najpierw napiszesz sobie testy które wszystko pokrywają i wszystko przewidują, potem napiszesz cacy kod, który najpierw niby będzie świecił cały na czerwony, ale potem dumnie zabłyśnie zielenią i już, już będzie można pchać prosto do mastera nawet bez review. To tak nie działa. Prawdopodobnie już pisząc kod uświadomisz sobie, że istnieją jeszcze jakieś inne przypadki, których nie przewidziałeś siadając do testów, zatem wrócisz do testów by je uwzględnić, wówczas okaże się, że kod wymaga poprawienia bo faktycznie niektóre testy zaświeciły się na czerwono. I tak dalej - byleby nie trwało to w nieskończoność ;)

Tu masz jakiś wątek o tym, kiedy właściwie pisze się kod w TDD.

0

Nie spodziewałem się tak rozbudowanej odpowiedzi :)
Cóż, nie pozostaje mi nic poza wzięciem się do pracy i próby wdrożenia tego, o czym napisałeś.
Zapewne z czasem przyjdą własne nawyki.

0

Hej, ja polecam rozszerzenie pytest-describe (https://github.com/ropez/pytest-describe). Pozwala ono na całkiem przyjemną organizację testów.

class Dupa():
    def foo(self):
        print('

    def bar(self):
        print('bar’)

Dla powyższego kodu napisalibyśmy coś takiego

def describe_Dupa():

    def describe_foo():
        def it_prints_foo():
            .....
        def it_does_magic():
            ....
    
    def describe_bar():
        def it_prints_bar():
            .....
        def it_throws_xyz_exception():
            ....

Według mnie jest to bardziej ‚naturalny’ podział, masz ładnie podzielone testy poszczególnych metod i konkretne przypadki.

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