Mosjon
I denne øvelsen bruker du Pytest til å teste en funksjon. Deretter finner og løser du noen potensielle problemer med funksjonen som forårsaker mislykkede tester. Det er viktig å se på feil og bruke Pytests omfattende feilrapportering for å identifisere og løse problematiske tester eller feil i produksjonskoden.
For denne øvelsen bruker vi en funksjon kalt admin_command() som godtar en systemkommando som inndata, og eventuelt prefikser den med sudo verktøyet. Funksjonen har en feil som du oppdager ved å skrive tester.
Trinn 1 – Legg til en fil med tester for denne øvelsen
Opprett en ny testfil ved hjelp av Pythons filnavnkonvensjoner for testfiler. Gi navn til testfilen test_exercise.py, og legg til følgende kode:
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 commandFunksjonen
admin_command()tar en liste som inndata ved hjelp av argumentetcommand, og kan eventuelt prefikse listen medsudo. Hvissudonøkkelordargument er satt tilFalse, returneres den samme kommandoen som er angitt som inndata.I samme fil tilføyer du testene for
admin_command()-funksjonen. Testene bruker en hjelpemetode som returnerer en eksempelkommando: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
Notat
Det er ikke vanlig å ha tester i samme fil som faktisk kode. Eksemplene i denne øvelsen har faktisk kode i samme fil for enkelhet. I virkelige Python-prosjekter skilles tester vanligvis av filer og kataloger fra koden de tester.
Trinn 2 – Kjør testene og identifiser feilen
Nå som testfilen har en funksjon å teste og et par tester for å bekrefte virkemåten, er det på tide å kjøre testene og arbeide med feil.
Utfør filen med Python:
$ pytest test_exercise.pyKjøringen skal fullføres med én testpassering og én feil, og feilutdataene bør være lik følgende utdata:
=================================== 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 ==========================Utdataene mislykkes på
test_sudo()test. Pytest gir detaljer om de to listene som sammenlignes. I dette tilfellet har ikkeresultvariabelensudo-kommandoen i seg, og det er det testen forventer.
Trinn 3 – Løs feilen og få testene til å bestå
Før du gjør endringer, må du forstå hvorfor det er en feil i utgangspunktet. Selv om du kan se at forventningen ikke blir oppfylt (sudo ikke er i resultatet), må du finne ut hvorfor.
Se på følgende kodelinjer fra admin_command()-funksjonen når sudo=True betingelsen er oppfylt:
if sudo:
["sudo"] + command
Operasjonen av listene brukes ikke til å returnere verdien. Siden den ikke returneres, ender funksjonen opp med å returnere kommandoen uten sudo alltid.
Oppdater
admin_command()-funksjonen for å returnere listeoperasjonen slik at det endrede resultatet brukes når du ber om ensudo-kommando. Den oppdaterte funksjonen skal se slik ut: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 commandKjør testen på nytt med Pytest. Prøv å øke detaljnivået til utdataene ved hjelp av
-vflagg med Pytest:$ pytest -v test_exercise.pyBekreft nå utdataene. Det skal vise to bestått tester nå:
============================= 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 ===============================
Notat
Fordi funksjonen er i stand til å arbeide med flere verdier av forskjellige foringsrør, bør flere tester legges til for å dekke disse variasjonene. Dette vil hindre fremtidige endringer i funksjonen fra å forårsake en annen (uventet) virkemåte.
Trinn 4 – Legg til ny kode med tester
Når du har lagt til tester i de forrige trinnene, bør du føle deg komfortabel med å gjøre flere endringer i funksjonen og bekrefte dem med tester. Selv om endringene ikke dekkes av eksisterende tester, kan du føle deg trygg på at du ikke bryter noen tidligere antagelser.
I dette tilfellet klarerer admin_command()-funksjonen blindt at argumentet command alltid er en liste. La oss forbedre dette ved å sikre at et unntak med en nyttig feilmelding blir hevet.
Først oppretter du en test som registrerer virkemåten. Selv om funksjonen ikke er oppdatert ennå, kan du prøve en test-første tilnærming (også kjent som Test Driven Development eller TDD).
- Oppdater test_exercise.py filen slik at den importerer
pytestøverst. Denne testen bruker en intern hjelper fra rammeverketpytest:
import pytest- Nå føyer du til en ny test i klassen for å kontrollere unntaket. Denne testen bør forvente en
TypeErrorfra funksjonen når verdien som sendes til den, ikke er en liste:
def test_non_list_commands(self): with pytest.raises(TypeError): admin_command("some command", sudo=True)- Oppdater test_exercise.py filen slik at den importerer
Kjør testene på nytt med Pytest. De bør alle passere:
============================= 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 ===============================Testen er god nok til å se etter
TypeErrormen det ville være bra å legge til koden med en nyttig feilmelding.Oppdater funksjonen for å heve en
TypeErroreksplisitt med en nyttig feilmelding: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 commandTil slutt oppdaterer du
test_non_list_commands()metoden for å se etter feilmeldingen: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'>"Den oppdaterte testen bruker
errorsom en variabel som inneholder all unntaksinformasjon. Ved hjelp averror.value.argskan du se på argumentene for unntaket. I dette tilfellet har det første argumentet feilstrengen som testen kan kontrollere.
Kontroller arbeidet ditt
På dette tidspunktet bør du ha en Python-testfil med navnet test_exercise.py som inkluderer:
- En
admin_command()funksjon som godtar et argument, og et nøkkelordargument. - Et
TypeErrorunntak med en nyttig feilmelding iadmin_command()-funksjonen. - En
TestAdminCommand()testklasse som har encommand()hjelpemetode og tre testmetoder som kontrollereradmin_command()-funksjonen.
Alle tester bør bestå uten feil når du kjører dem i terminalen.