Cvičení

Dokončeno

V tomto cvičení použijete Pytest k otestování funkce. Pak zjistíte a opravíte některé potenciální problémy s funkcí, která způsobuje neúspěšné testy. Při identifikaci a opravě problematických testů nebo chyb v produkčním kódu je důležité zjistit a opravit selhání a používat bohaté hlášení chyb Pytestu.

V tomto cvičení používáme funkci, která přijímá admin_command() systémový příkaz jako vstup a volitelně ji sudo předponuje nástrojem. Funkce má chybu, kterou zjistíte psaním testů.

Krok 1 – přidání souboru s testy pro toto cvičení

  1. Pomocí konvencí názvu souboru Pythonu pro testovací soubory vytvořte nový testovací soubor. Pojmenujte testovací soubor test_exercise.py a přidejte následující kód:

    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
    

    Funkce admin_command() přebírá jako vstup seznam pomocí argumentu command a volitelně může seznam předponovat sudo. Pokud je argument klíčového sudo slova nastavený na False, vrátí stejný příkaz zadaný jako vstup.

  2. Ve stejném souboru připojte testy funkce admin_command() . Testy používají pomocnou metodu, která vrací ukázkový příkaz:

    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
    

Poznámka:

Není běžné mít testy ve stejném souboru jako skutečný kód. Pro zjednodušení budou příklady v tomto cvičení obsahovat skutečný kód ve stejném souboru. V reálných projektech Pythonu se testy obvykle oddělují soubory a adresáře od kódu, který testují.

Krok 2 : Spuštění testů a identifikace selhání

Teď, když má testovací soubor funkci k otestování a několik testů k ověření jejího chování, je čas spustit testy a pracovat se selháními.

  • Spusťte soubor v Pythonu:

    $ pytest test_exercise.py
    

    Spuštění by se mělo dokončit jedním testem a jedním selháním a výstupem selhání by měl být podobný následujícímu výstupu:

    =================================== 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 ==========================
    

    Výstup v test_sudo() testu selže. Pytest poskytuje podrobné informace o dvou seznamech, které se porovnávají. V tomto případě result proměnná nemá sudo v něm příkaz, což je to, co test očekává.

Krok 3 : Oprava chyby a úspěšné provedení testů

Před provedením jakýchkoli změn musíte pochopit, proč došlo k selhání na prvním místě. I když vidíte, že očekávání není splněno (sudo není ve výsledku), musíte zjistit, proč.

Při splnění podmínky se podívejte na následující řádky kódu funkce admin_command()sudo=True :

    if sudo:
        ["sudo"] + command

Operace seznamů se nepoužívá k vrácení hodnoty. Vzhledem k tomu, že se nevrací, funkce nakonec vrátí příkaz bez sudo nutnosti vždy.

  1. admin_command() Aktualizujte funkci tak, aby vrátila operaci seznamu tak, aby se při vyžádání sudo příkazu použil upravený výsledek. Aktualizovaná funkce by měla vypadat takto:

    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. Znovu spusťte test pomocí Pytestu. Zkuste zvýšit úroveň podrobností výstupu pomocí příznaku -v s Pytestem:

    $ pytest -v test_exercise.py
    
  3. Teď ověřte výstup. Teď by se měly zobrazit dva úspěšné testy:

    ============================= 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 ===============================
    

Poznámka:

Vzhledem k tomu, že funkce dokáže pracovat s více hodnotami různých velikostí a velikostí, je potřeba přidat více testů, aby se tyto varianty pokryly. Tím zabráníte budoucím změnám funkce, aby způsobovala jiné (neočekávané) chování.

Krok 4 : Přidání nového kódu s testy

Po přidání testů v předchozích krocích byste měli být spokojení s prováděním dalších změn funkce a jejich ověřením pomocí testů. I když se změny nezabývá existujícími testy, můžete si být jistí, že nenarušíte žádné předchozí předpoklady.

V tomto případě admin_command() funkce důvěřuje slepě, že command argument je vždy seznamem. Pojďme to vylepšit tím, že zajistíme vyvolání výjimky s užitečnou chybovou zprávou.

  1. Nejprve vytvořte test, který zachycuje chování. I když se funkce ještě neaktualizuje, vyzkoušejte přístup typu test-first (označovaný také jako vývoj řízený testy nebo TDD).

    • Aktualizujte soubor test_exercise.py tak, aby se naimportuje pytest v horní části. Tento test používá interní pomocníka z pytest architektury:
    import pytest
    
    • Teď do třídy přidejte nový test, který zkontroluje výjimku. Tento test by měl od funkce očekávat TypeError , když do ní předaná hodnota není seznam:
        def test_non_list_commands(self):
            with pytest.raises(TypeError):
                admin_command("some command", sudo=True)
    
  2. Znovu spusťte testy pomocí Pytestu, měly by všechny projít:

    ============================= 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 je dostatečně dobrý, aby zkontroloval TypeError , ale bylo by dobré přidat kód s užitečnou chybovou zprávou.

  3. Aktualizujte funkci tak, aby explicitně vyvolala TypeError užitečnou chybovou zprávu:

    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. Nakonec aktualizujte metodu test_non_list_commands() a zkontrolujte chybovou zprávu:

    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'>"
    

    Aktualizovaný test se používá error jako proměnná, která obsahuje všechny informace o výjimce. Pomocí , error.value.argsmůžete se podívat na argumenty výjimky. V tomto případě má první argument řetězec chyby, který test může zkontrolovat.

Kontrola práce

V tuto chvíli byste měli mít testovací soubor Pythonu s názvem test_exercise.py , který zahrnuje:

  • Funkce admin_command() , která přijímá argument, a argument klíčového slova.
  • Výjimka TypeError s užitečnou chybovou zprávou admin_command() ve funkci.
  • TestAdminCommand() Testovací třída, která má pomocnou metodu command() a tři testovací metody, které funkci kontrolujíadmin_command().

Všechny testy by se měly předávat bez chyb, když je spustíte v terminálu.