Cvičení
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í
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í argumentucommand
a volitelně může seznam předponovatsudo
. Pokud je argument klíčovéhosudo
slova nastavený naFalse
, vrátí stejný příkaz zadaný jako vstup.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.
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
Znovu spusťte test pomocí Pytestu. Zkuste zvýšit úroveň podrobností výstupu pomocí příznaku
-v
s Pytestem:$ pytest -v test_exercise.py
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.
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 zpytest
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)
-
Aktualizujte soubor test_exercise.py tak, aby se naimportuje
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.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
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.args
můž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ávouadmin_command()
ve funkci. -
TestAdminCommand()
Testovací třída, která má pomocnou metoducommand()
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.