Ćwiczenie
W tym ćwiczeniu użyjesz narzędzia Pytest do przetestowania funkcji. Następnie znajdziesz i rozwiążesz pewne potencjalne problemy z funkcją, która powoduje niepowodzenie testów. Patrząc na błędy i korzystając z zaawansowanego raportowania błędów Pytest, niezbędne jest zidentyfikowanie i naprawienie problematycznych testów lub usterek w kodzie produkcyjnym.
W tym ćwiczeniu użyjemy funkcji o nazwie admin_command()
, która akceptuje polecenie systemowe jako dane wejściowe i opcjonalnie prefiksuje je za pomocą sudo
narzędzia. Funkcja zawiera usterkę, którą można wykryć, pisząc testy.
Krok 1. Dodawanie pliku z testami na potrzeby tego ćwiczenia
Używając konwencji nazw plików języka Python dla plików testowych, utwórz nowy plik testowy. Nadaj plikowi testowe nazwę test_exercise.py i dodaj następujący kod:
def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: ["sudo"] + command return command
Funkcja
admin_command()
przyjmuje listę jako dane wejściowe przy użyciu argumentucommand
i opcjonalnie może prefiksować listę za pomocą poleceniasudo
. Jeśli argument słowa kluczowegosudo
jest ustawiony naFalse
wartość , zwraca to samo polecenie podane jako dane wejściowe.W tym samym pliku dołącz testy dla
admin_command()
funkcji. Testy używają metody pomocniczej, która zwraca przykładowe polecenie:class TestAdminCommand: def command(self): return ["ps", "aux"] def test_no_sudo(self): result = admin_command(self.command(), sudo=False) assert result == self.command() def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() assert result == expected
Uwaga
Nie ma często testów w tym samym pliku co rzeczywisty kod. Dla uproszczenia przykłady w tym ćwiczeniu będą miały rzeczywisty kod w tym samym pliku. W rzeczywistych projektach języka Python testy są zwykle oddzielone plikami i katalogami od kodu, który testuje.
Krok 2. Uruchamianie testów i identyfikowanie błędu
Teraz, gdy plik testowy ma funkcję do testowania i kilka testów w celu zweryfikowania jego zachowania, nadszedł czas, aby uruchomić testy i pracować z błędami.
Wykonaj plik przy użyciu języka Python:
$ pytest test_exercise.py
Przebieg powinien zostać ukończony z jednym przebiegiem testu i jednym niepowodzeniem, a dane wyjściowe błędu powinny być podobne do następujących danych wyjściowych:
=================================== FAILURES =================================== __________________________ TestAdminCommand.test_sudo __________________________ self = <test_exercise.TestAdminCommand object at 0x10634c2e0> def test_sudo(self): result = admin_command(self.command(), sudo=True) expected = ["sudo"] + self.command() > assert result == expected E AssertionError: assert ['ps', 'aux'] == ['sudo', 'ps', 'aux'] E At index 0 diff: 'ps' != 'sudo' E Right contains one more item: 'aux' E Use -v to get the full diff test_exercise.py:24: AssertionError =========================== short test summary info ============================ FAILED test_exercise.py::TestAdminCommand::test_sudo - AssertionError: assert... ========================= 1 failed, 1 passed in 0.04s ==========================
Dane wyjściowe kończą się niepowodzeniem w
test_sudo()
teście. Narzędzie Pytest zawiera szczegółowe informacje o dwóch porównywanych listach. W takim przypadku zmiennaresult
nie masudo
w nim polecenia , co jest oczekiwane przez test.
Krok 3. Naprawienie usterki i wykonanie testów
Przed wprowadzeniem jakichkolwiek zmian musisz zrozumieć, dlaczego w pierwszej kolejności występuje błąd. Chociaż widać, że oczekiwanie nie jest spełnione (sudo
nie jest wynikiem), musisz dowiedzieć się, dlaczego.
Po spełnieniu warunku zapoznaj się z następującymi wierszami kodu z admin_command()
funkcji sudo=True
:
if sudo:
["sudo"] + command
Operacja list nie jest używana do zwracania wartości. Ponieważ nie jest zwracany, funkcja kończy się zwracaniem polecenia bez sudo
konieczności.
Zaktualizuj funkcję ,
admin_command()
aby zwrócić operację listy, tak aby zmodyfikowany wynik był używany podczas żądaniasudo
polecenia. Zaktualizowana funkcja powinna wyglądać następująco:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if sudo: return ["sudo"] + command return command
Uruchom ponownie test za pomocą narzędzia Pytest. Spróbuj zwiększyć szczegółowość danych wyjściowych przy użyciu
-v
flagi z narzędziem Pytest:$ pytest -v test_exercise.py
Teraz sprawdź dane wyjściowe. Powinien teraz wyświetlić dwa testy z przekazywaniem:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 cachedir: .pytest_cache rootdir: /private collected 2 items test_exercise.py::TestAdminCommand::test_no_sudo PASSED [ 50%] test_exercise.py::TestAdminCommand::test_sudo PASSED [100%] ============================== 2 passed in 0.00s ===============================
Uwaga
Ponieważ funkcja może pracować z większą częścią różnych wielkości liter, należy dodać więcej testów, aby uwzględnić te odmiany. Zapobiegłoby to przyszłym zmianom funkcji, co spowodowałoby inne (nieoczekiwane) zachowanie.
Krok 4. Dodawanie nowego kodu z testami
Po dodaniu testów w poprzednich krokach należy czuć się komfortowo, wprowadzając więcej zmian w funkcji i weryfikując je przy użyciu testów. Nawet jeśli zmiany nie są objęte istniejącymi testami, możesz mieć pewność, że nie łamie żadnych poprzednich założeń.
W takim przypadku funkcja ufa ślepo, admin_command()
że command
argument jest zawsze listą. Ulepszmy to, upewniając się, że zostanie zgłoszony wyjątek z przydatnym komunikatem o błędzie.
Najpierw utwórz test, który przechwytuje zachowanie. Mimo że funkcja nie została jeszcze zaktualizowana, spróbuj użyć podejścia testowego (nazywanego również programowaniem opartym na testach lub TDD).
-
Zaktualizuj plik test_exercise.py, aby import był importowy
pytest
u góry. Ten test używa wewnętrznego pomocnika z platformypytest
:
import pytest
- Teraz dołącz nowy test do klasy, aby sprawdzić wyjątek. Ten test powinien oczekiwać
TypeError
elementu z funkcji, gdy wartość przekazana do niej nie jest listą:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)
-
Zaktualizuj plik test_exercise.py, aby import był importowy
Uruchom ponownie testy za pomocą narzędzia Pytest. Wszystkie powinny przejść:
============================= test session starts ============================== Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: /private/ collected 3 items test_exercise.py ... [100%] ============================== 3 passed in 0.00s ===============================
Test jest wystarczająco dobry, aby sprawdzić
TypeError
, ale warto dodać kod z przydatnym komunikatem o błędzie.Zaktualizuj funkcję, aby jawnie zgłosić
TypeError
komunikat o błędzie:def admin_command(command, sudo=True): """ Prefix a command with `sudo` unless it is explicitly not needed. Expects `command` to be a list. """ if not isinstance(command, list): raise TypeError(f"was expecting command to be a list, but got a {type(command)}") if sudo: return ["sudo"] + command return command
Na koniec zaktualizuj metodę
test_non_list_commands()
, aby sprawdzić komunikat o błędzie:def test_non_list_commands(self): with pytest.raises(TypeError) as error: admin_command("some command", sudo=True) assert error.value.args[0] == "was expecting command to be a list, but got a <class 'str'>"
Zaktualizowany test używa
error
jako zmiennej zawierającej wszystkie informacje o wyjątku. Za pomocą metodyerror.value.args
możesz przyjrzeć się argumentom wyjątku. W takim przypadku pierwszy argument zawiera ciąg błędu, który może sprawdzić test.
Sprawdź swoją pracę
W tym momencie powinien istnieć plik testowy języka Python o nazwie test_exercise.py zawierający następujące elementy:
-
admin_command()
Funkcja, która akceptuje argument i argument słowa kluczowego. - Wyjątek
TypeError
z pomocnym komunikatem o błędzie wadmin_command()
funkcji. - Klasa testowa
TestAdminCommand()
, która ma metodęcommand()
pomocnika i trzy metody testowe sprawdzająceadmin_command()
funkcję.
Wszystkie testy powinny przechodzić bez błędów po uruchomieniu ich w terminalu.