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 napsání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 commandFunkce
admin_command()přebírá jako vstup seznam pomocí argumentucommanda volitelně může seznam předponovatsudo. Pokud je argument klíčovéhosudoslova 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í mají příklady v tomto cvičení 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.pySpuš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ěresultproměnná nemásudov 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ísudopří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 commandZnovu spusťte test pomocí Pytestu. Zkuste zvýšit úroveň podrobností výstupu pomocí příznaku
-vs Pytestem:$ pytest -v test_exercise.pyTeď 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í písmen, je potřeba přidat více testů, aby tyto varianty byly pokryty. 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
pytestv horní části. Tento test používá interní pomocníka zpytestarchitektury:
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. Všechny by měly 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
TypeErroruž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 commandNakonec 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á
errorjako 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
TypeErrors 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 měly při spuštění v terminálu proběhnout bez chyb.