Ćwiczenie

Ukończone

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

  1. 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 argumentu command i opcjonalnie może prefiksować listę za pomocą polecenia sudo. Jeśli argument słowa kluczowego sudo jest ustawiony na Falsewartość , zwraca to samo polecenie podane jako dane wejściowe.

  2. 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 zmienna result nie ma sudo 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.

  1. Zaktualizuj funkcję , admin_command() aby zwrócić operację listy, tak aby zmodyfikowany wynik był używany podczas żądania sudo 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
    
  2. 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
    
  3. 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.

  1. 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 platformy pytest :
    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)
    
  2. 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.

  3. 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
    
  4. 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ą metody error.value.argsmoż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 w admin_command() funkcji.
  • Klasa testowa TestAdminCommand() , która ma metodę command() pomocnika i trzy metody testowe sprawdzające admin_command() funkcję.

Wszystkie testy powinny przechodzić bez błędów po uruchomieniu ich w terminalu.