Korzystam z instrukcji asercji Python w celu dopasowania rzeczywistego i oczekiwanego zachowania. Nie mam nad nimi kontroli, tak jakby przypadki testowania błędów przerywały się. Chcę przejąć kontrolę nad błędem asercji i określić, czy chcę przerwać testowanie w przypadku potwierdzenia błędu, czy nie.
Chcę również dodać coś takiego, jeśli wystąpi błąd asercji, to przypadek testowy powinien zostać wstrzymany, a użytkownik może wznowić w dowolnym momencie.
Nie mam pojęcia, jak to zrobić
Przykład kodu, używamy tutaj zapytania
import pytest
def test_abc():
a = 10
assert a == 10, "some error message"
Below is my expectation
Gdy assert zgłasza błąd assertionError, powinienem mieć możliwość wstrzymania testu i mogę debugować, a następnie wznowić. Do pauzy i wznowienia użyję tkinter
modułu. Zrobię funkcję aser, jak poniżej
import tkinter
import tkinter.messagebox
top = tkinter.Tk()
def _assertCustom(assert_statement, pause_on_fail = 0):
#assert_statement will be something like: assert a == 10, "Some error"
#pause_on_fail will be derived from global file where I can change it on runtime
if pause_on_fail == 1:
try:
eval(assert_statement)
except AssertionError as e:
tkinter.messagebox.showinfo(e)
eval (assert_statement)
#Above is to raise the assertion error again to fail the testcase
else:
eval (assert_statement)
Idąc dalej, muszę zmienić każdą instrukcję aser z tą funkcją jako
import pytest
def test_abc():
a = 10
# Suppose some code and below is the assert statement
_assertCustom("assert a == 10, 'error message'")
To dla mnie za duży wysiłek, ponieważ muszę dokonać zmian w tysiącach miejsc, w których korzystałem. Czy jest na to jakiś prosty sposóbpytest
Summary:
Potrzebuję czegoś, w którym mogę wstrzymać testowanie w przypadku niepowodzenia, a następnie wznowić po debugowaniu. wiem otkinter
tym i dlatego go wykorzystałem. Wszelkie inne pomysły będą mile widziane
Note
: Powyższy kod nie został jeszcze przetestowany. Mogą występować również małe błędy składniowe
Edycja: Dzięki za odpowiedzi. Rozszerzając to pytanie nieco teraz. Co jeśli chcę zmienić zachowanie aser. Obecnie, gdy występuje błąd asercji, testcase kończy pracę. Co jeśli chcę wybrać, czy potrzebuję wyjść z testu w przypadku konkretnego błędu, czy nie. Nie chcę pisać niestandardowej funkcji potwierdzenia, jak wspomniano powyżej, ponieważ w ten sposób muszę zmieniać liczbę miejsc
assert
ale pisz własne funkcje sprawdzające, które robią, co chcesz.pytest
do testowania przypadków. Obsługuje stosowanie testów aser i pomijanie, a także wiele innych funkcji ułatwiających pisanie zestawów testów.assert cond, "msg"
w twoim kodzie_assertCustom("assert cond, 'msg'")
? Prawdopodobniesed
może to zrobić jeden liniowiec.Odpowiedzi:
Używasz
pytest
, co daje wiele możliwości interakcji z testami zakończonymi niepowodzeniem. Daje ci opcje wiersza poleceń i kilka zaczepów, aby to umożliwić. Wyjaśnię, jak korzystać z każdego z nich i gdzie można dokonać dostosowań, aby dopasować je do konkretnych potrzeb debugowania.Przejdę również do bardziej egzotycznych opcji, które pozwolą ci całkowicie pominąć określone twierdzenia, jeśli naprawdę czujesz, że musisz.
Obsługuj wyjątki, a nie stwierdzaj
Zauważ, że test zakończony niepowodzeniem zwykle nie zatrzymuje zapytania; tylko jeśli włączysz jawnie polecenie zakończenia po określonej liczbie awarii . Ponadto testy nie udają się, ponieważ zgłoszony jest wyjątek;
assert
podnosi,AssertionError
ale to nie jedyny wyjątek, który spowoduje niepowodzenie testu! Chcesz kontrolować sposób obsługi wyjątków, a nie zmieniaćassert
.Jednak w przypadku braku assert będzie zakończyć indywidualny test. Dzieje się tak, ponieważ po zgłoszeniu wyjątku poza
try...except
blok, Python odwija bieżącą ramkę funkcji i nie ma już do niej powrotu.Nie sądzę, że tego właśnie chcesz, sądząc po twoim opisie
_assertCustom()
prób wznowienia twierdzenia, ale omówię twoje opcje dalej.Debugowanie pośmiertne w pytest z pdb
Aby wyświetlić różne opcje obsługi błędów w debuggerze, zacznę od
--pdb
przełącznika wiersza polecenia , który otwiera standardowe okno debugowania, gdy test się nie powiedzie (dane wyjściowe są pobierane dla zwięzłości):Za pomocą tego przełącznika, gdy test się nie powiedzie, pytest rozpoczyna sesję debugowania pośmiertnego . To jest dokładnie to, czego chciałeś; aby zatrzymać kod w momencie nieudanego testu i otworzyć debugger, aby sprawdzić stan testu. Możesz wchodzić w interakcje z lokalnymi zmiennymi testu, globalsami oraz locals i globalsami każdej ramki w stosie.
Tutaj pytest daje pełną kontrolę nad tym, czy wyjść po tym punkcie: jeśli użyjesz
q
komendy quit, pytest również opuści bieg, użycie komendyc
kontynuuj spowoduje zwrócenie kontroli do pytest i wykonanie następnego testu.Korzystanie z alternatywnego debugera
Nie jesteś do tego związany z
pdb
debuggerem; za pomocą--pdbcls
przełącznika możesz ustawić inny debugger . Działa dowolnapdb.Pdb()
kompatybilna implementacja, w tym implementacja debugera IPython lub większość innych debuggerów Python ( debugger pudb wymaga użycia-s
przełącznika lub specjalnej wtyczki ). Przełącznik bierze moduł i klasę, np. Do użyciapudb
można użyć:Możesz użyć tej funkcji do napisania własnej klasy opakowania,
Pdb
która po prostu zwraca natychmiast, jeśli konkretna awaria nie jest czymś, co Cię interesuje.pytest
UżywaPdb()
dokładnie tak, jakpdb.post_mortem()
robi :Tutaj
t
jest obiekt śledzenia . Pop.interaction(None, t)
powrociepytest
przechodzi do następnego testu, chyba żep.quitting
jest ustawiony naTrue
(w którym momencie pytest następnie kończy działanie).Oto przykładowa implementacja, która drukuje, że odmawiamy debugowania i natychmiast wraca, chyba że test został zgłoszony
ValueError
, zapisany jakodemo/custom_pdb.py
:Kiedy używam tego z powyższą wersją demonstracyjną, jest to wyjście (ponownie, chcąc zwięzłości):
Powyższe introspekty
sys.last_type
pozwalają ustalić, czy awaria jest „interesująca”.Jednak nie mogę naprawdę polecić tej opcji, chyba że chcesz napisać własny debugger za pomocą tkInter lub czegoś podobnego. Pamiętaj, że to duże przedsięwzięcie.
Błędy filtrowania; wybierz i wybierz, kiedy otworzyć debugger
Poziom Następną opcją jest pytest debugowania i interakcji haczyków ; są to punkty zaczepienia do dostosowywania zachowania, które zastępują lub poprawiają sposób, w jaki pytest zwykle obsługuje takie rzeczy, jak obsługa wyjątku lub wejście do debuggera za pomocą
pdb.set_trace()
lubbreakpoint()
(Python 3.7 lub nowszy).Wewnętrzna implementacja tego haka jest również odpowiedzialna za wydrukowanie
>>> entering PDB >>>
banera powyżej, więc użycie tego haka, aby zapobiec uruchomieniu debuggera, oznacza, że nie zobaczysz w ogóle tego wyniku. Możesz mieć własny hak, a następnie delegować go do oryginalnego haka, gdy błąd testu jest „interesujący”, a więc błędy testu filtra są niezależne od używanego debugera! Możesz uzyskać dostęp do wewnętrznej implementacji, otwierając ją według nazwy ; wewnętrzna wtyczka haka do tego jest nazwanapdbinvoke
. Aby uniemożliwić jego uruchomienie, musisz go wyrejestrować , ale zapisz referencję, czy możemy wywołać ją bezpośrednio w razie potrzeby.Oto przykładowa implementacja takiego haka; możesz umieścić to w dowolnej lokalizacji, z której ładowane są wtyczki ; Wkładam to
demo/conftest.py
:Powyższa wtyczka używa wewnętrznej
TerminalReporter
wtyczki do wypisywania linii do terminala; To sprawia, że dane wyjściowe są czystsze, gdy używany jest domyślny kompaktowy format statusu testu, i pozwala zapisywać rzeczy na terminalu nawet przy włączonym przechwytywaniu danych wyjściowych.Przykład rejestruje obiekt wtyczki za
pytest_exception_interact
pomocą haka za pomocą innego haka,pytest_configure()
ale upewniając się, że działa wystarczająco późno (za pomocą@pytest.hookimpl(trylast=True)
), aby móc wyrejestrować wewnętrznąpdbinvoke
wtyczkę. Po wywołaniu haka przykład testuje nacall.exceptinfo
obiekcie ; możesz również sprawdzić węzeł lub raport .Z powyższego kodu próbki w miejscu, w
demo/conftest.py
Thetest_ham
niepowodzenie testu jest ignorowany, tylkotest_spam
niepowodzenia testu, który podnosiValueError
, prowadzi do debugowania szybkiego otwarcia:Aby ponownie wykonać iterację, powyższe podejście ma tę dodatkową zaletę, że można połączyć to z dowolnym debuggerem, który działa z pytestem , w tym z pakietem Pudb lub debugerem IPython:
Ma również znacznie szerszy kontekst na temat tego, jaki test był uruchamiany (za pośrednictwem
node
argumentu) i bezpośredni dostęp do zgłoszonego wyjątku (za pośrednictwemcall.excinfo
ExceptionInfo
instancji).Zauważ, że określone wtyczki debuggera (takie jak
pytest-pudb
lubpytest-pycharm
) rejestrują własnepytest_exception_interact
punkty zaczepienia. Bardziej kompletna implementacja musiałaby zapętlać wszystkie wtyczki w menedżerze wtyczek, aby automatycznie zastępować dowolne wtyczki przy użyciuconfig.pluginmanager.list_name_plugin
ihasattr()
testować każdą wtyczkę.Sprawianie, że niepowodzenia całkowicie znikają
Chociaż daje to pełną kontrolę nad nieudanym debugowaniem testu, nadal pozostawia to test jako nieudany, nawet jeśli zdecydujesz się nie otwierać debugera dla danego testu. Jeśli chcesz, aby awarie odejść całkowicie, można wykorzystać inny haczyk:
pytest_runtest_call()
.Kiedy pytest uruchamia testy, przeprowadzi test poprzez powyższy zaczep, który powinien zwrócić
None
lub zgłosić wyjątek. Na podstawie tego tworzony jest raport, opcjonalnie tworzony jest wpis dziennika, a jeśli test się nie powiedzie,pytest_exception_interact()
wywoływany jest wspomniany wyżej hook. Więc wszystko, co musisz zrobić, to zmienić wynik tego haka; zamiast wyjątku nie powinien po prostu niczego zwracać.Najlepszym sposobem na to jest użycie owijarki hakowej . Owijarki haczyków nie muszą wykonywać rzeczywistej pracy, ale zamiast tego mają szansę zmienić to, co dzieje się z wynikiem haka. Wszystko, co musisz zrobić, to dodać wiersz:
w implementacji otoki haka i uzyskujesz dostęp do wyniku haka , w tym wyjątku testowego przez
outcome.excinfo
. Ten atrybut jest ustawiony na krotkę (typ, instancja, traceback), jeśli w teście został zgłoszony wyjątek. Możesz też zadzwonićoutcome.get_result()
i skorzystać ze standardowejtry...except
obsługi.Jak więc zaliczyć nieudany test? Masz 3 podstawowe opcje:
pytest.xfail()
opakowanie.pytest.skip()
.outcome.force_result()
metody ; ustaw tutaj wynik na pustą listę (co oznacza, że zarejestrowany hak nie wygenerował nic opróczNone
), a wyjątek zostanie całkowicie usunięty.To, czego używasz, zależy od Ciebie. Najpierw sprawdź wynik pominiętych i oczekiwanych testów, ponieważ nie musisz zajmować się tymi przypadkami, jakby test się nie powiódł. Możesz uzyskać dostęp do specjalnych wyjątków zgłaszanych przez te opcje za pośrednictwem
pytest.skip.Exception
ipytest.xfail.Exception
.Oto przykładowa implementacja, która oznacza, że testy zakończone niepowodzeniem nie zostały podniesione
ValueError
, ponieważ zostały pominięte :Po umieszczeniu w
conftest.py
wyjściu staje się:Użyłem
-r a
flagi, aby wyjaśnić, żetest_ham
teraz została pominięta.Jeśli wymienisz
pytest.skip()
połączeniepytest.xfail("[XFAIL] ignoring everything but ValueError")
, test zostanie oznaczony jako oczekiwany błąd:i za pomocą
outcome.force_result([])
oznacza to jako przekazane:Od Ciebie zależy, który z nich najlepiej pasuje do Twojego przypadku użycia. Dla
skip()
ixfail()
naśladowałem standardowy format wiadomości (z prefiksem[NOTRUN]
lub[XFAIL]
), ale możesz swobodnie używać dowolnego innego formatu wiadomości.We wszystkich trzech przypadkach pytest nie otworzy debugera dla testów, których wynik został zmieniony za pomocą tej metody.
Zmiana poszczególnych instrukcji asercji
Jeśli chcesz zmienić
assert
testy w teście , musisz przygotować się na znacznie więcej pracy. Tak, jest to technicznie możliwe, ale tylko przez przepisanie samego kodu, który Python zamierza wykonać w czasie kompilacji .Kiedy używasz
pytest
, jest to już zrobione . Pytest przepisujeassert
instrukcje, aby dać ci większy kontekst, gdy twoje twierdzenia zawiodą ; zobacz ten post na blogu, aby uzyskać dobry przegląd tego, co jest robione, a także_pytest/assertion/rewrite.py
kod źródłowy . Zauważ, że ten moduł ma ponad 1 000 linii i wymaga zrozumienia, jak działają abstrakcyjne drzewa składniowe Pythona . Jeśli tak, to mógłby monkeypatch ten moduł aby dodać własne modyfikacje tam, w tym otaczającejassert
ztry...except AssertionError:
obsługi.Jednak nie można po prostu wyłączać lub ignorować twierdzeń wybiórczo, ponieważ kolejne instrukcje mogą łatwo zależeć od stanu (specyficzne rozmieszczenie obiektów, zestaw zmiennych itp.), Przed którym miało zabezpieczyć pominięte twierdzenie. Jeśli test potwierdzający, który
foo
nie jest spełnionyNone
, to późniejsze stwierdzenie opiera sięfoo.bar
na istnieniu, po prostu natkniesz się naAttributeError
tam, itp. Trzymaj się ponownie zgłaszając wyjątek, jeśli musisz iść tą drogą.Nie będę się
asserts
tutaj zajmował bardziej szczegółowymi informacjami na temat przepisywania , ponieważ nie sądzę, że warto to kontynuować, nie biorąc pod uwagę nakładu pracy, a debugowanie pośmiertne daje dostęp do stanu testu na i tak brak dowodu .Zauważ, że jeśli chcesz to zrobić, nie musisz używać
eval()
(co i tak by nie działało,assert
jest instrukcją, więc musisz użyćexec()
zamiast tego), ani nie musiałbyś uruchamiać asercji dwa razy (co może prowadzić do problemów, jeśli wyrażenie użyte w asercji zmieniło stan). Zamiast tego należy osadzićast.Assert
węzeł wast.Try
węźle i dołączyć moduł obsługi wyjątków, który używa pustegoast.Raise
węzła, aby ponownie zgłosić przechwycony wyjątek.Używanie debugera do pomijania instrukcji asercji.
Debuger w języku Python umożliwia pomijanie instrukcji za pomocą polecenia
j
/jump
. Jeśli wiesz z góry, że określone twierdzenie się nie powiedzie, możesz użyć tego, aby je ominąć. Możesz uruchomić swoje testy--trace
, które otwierają debugger na początku każdego testu , a następnie wydaćj <line after assert>
polecenie pominięcia go, gdy debuger zostanie wstrzymany tuż przed potwierdzeniem.Możesz to nawet zautomatyzować. Korzystając z powyższych technik, możesz zbudować niestandardową wtyczkę debugera
pytest_testrun_call()
haka, aby złapaćAssertionError
wyjątekPdb
podklasy, która ustawia punkt przerwania na linii przed potwierdzeniem i automatycznie wykonuje skok do drugiej po trafieniu punktu przerwania, a następniec
kontynuuje.Lub zamiast czekać na niepowodzenie twierdzenia, możesz zautomatyzować ustawianie punktów przerwania dla każdego
assert
znalezionego w teście (ponownie używając analizy kodu źródłowego, możesz w prosty sposób wyodrębnić numery linii dlaast.Assert
węzłów w AST testu), wykonaj potwierdzony test za pomocą poleceń skryptowych debugera i użyjjump
polecenia, aby pominąć samo potwierdzenie. Musisz dokonać kompromisu; uruchom wszystkie testy w debuggerze (co jest wolne, ponieważ interpreter musi wywoływać funkcję śledzenia dla każdej instrukcji) lub zastosuj to tylko do testów zakończonych niepowodzeniem i zapłać cenę za ponowne uruchomienie tych testów od zera.Taka wtyczka byłaby bardzo pracochłonna do stworzenia, nie zamierzam tutaj pisać przykładu, częściowo dlatego, że i tak nie zmieściłby się w odpowiedzi, a częściowo dlatego , że nie uważam, że warto . Po prostu otworzyłem debugger i wykonałem skok ręcznie. Niepowodzenie twierdzenia oznacza błąd w samym teście lub w testowanym kodzie, więc równie dobrze możesz po prostu skupić się na debugowaniu problemu.
źródło
Możesz osiągnąć dokładnie to, co chcesz bez absolutnie żadnej modyfikacji kodu dzięki pytest --pdb .
Na przykład:
Uruchom z --pdb:
Gdy tylko test się nie powiedzie, można go debugować za pomocą wbudowanego debugera python. Po
continue
zakończeniu debugowania możesz wykonać resztę testów.źródło
Jeśli używasz PyCharm, możesz dodać punkt przerwania wyjątku, aby wstrzymać wykonanie, gdy nie powiedzie się potwierdzenie. Wybierz opcję Pokaż punkty przerwania (CTRL-SHIFT-F8) i dodaj moduł obsługi wyjątków przy podnoszeniu dla AssertionError. Zauważ, że może to spowolnić wykonywanie testów.
W przeciwnym razie, jeśli nie masz nic przeciwko wstrzymywaniu się na końcu każdego testu zakończonego niepowodzeniem (tuż przed błędem), a nie w momencie, gdy asercja nie powiedzie się, masz kilka opcji. Należy jednak pamiętać, że w tym momencie różne kody czyszczenia, takie jak zamykanie plików, które zostały otwarte w teście, mogły już zostać uruchomione. Możliwe opcje to:
Możesz powiedzieć pytestowi, aby wrzucił cię do debuggera w przypadku błędów, używając opcji --pdb .
Możesz zdefiniować następujący dekorator i ozdobić każdą odpowiednią funkcją testową. (Oprócz rejestrowania wiadomości możesz w tym momencie uruchomić pdb.post_mortem , a nawet interaktywny kod. Wchodzić w interakcję z lokalnymi ramkami, z których powstał wyjątek, jak opisano w tej odpowiedzi .)
źródło
pause_on_assert
odczytu z pliku, aby zdecydować, czy wstrzymać, czy nie.Jednym prostym rozwiązaniem, jeśli chcesz używać programu Visual Studio Code, może być użycie warunkowych punktów przerwania .
Umożliwiłoby to skonfigurowanie swoich twierdzeń, na przykład:
Następnie dodaj warunkowy punkt przerwania do linii potwierdzenia, który złamie się tylko wtedy, gdy asercja się nie powiedzie:
źródło