Grunnleggende om Pytest
La oss komme i gang med testing med Pytest. Som vi nevnte i forrige enhet, er Pytest svært konfigurerbar og kan håndtere komplekse testpakker, men det krever ikke mye for å begynne å skrive tester. Jo enklere et rammeverk er, jo enklere kan du skrive tester, jo bedre.
På slutten av denne delen bør du ha alt du trenger for å begynne å skrive dine første tester og kjøre dem med Pytest.
Konvensjoner
Før vi dykker ned i skrivetester, må vi dekke noen av testkonvensjonene som Pytest er avhengig av.
Det er ikke mange harde regler om testfiler, testkataloger eller generelle testoppsett i Python. Ved å kjenne disse reglene kan du dra nytte av automatisk testoppdagelse og kjøring uten behov for ekstra konfigurasjon.
Tester katalog- og testfiler
Hovedkatalogen for tester er tester katalog. Du kan plassere denne katalogen på rotnivået i prosjektet, men det er heller ikke uvanlig å se den sammen med kodemoduler.
Notat
I denne modulen bruker vi som standard tester ved roten av et prosjekt.
La oss se hvordan roten til et lite Python-prosjekt med navnet jformat ser ut:
.
├── README.md
├── jformat
│ ├── __init__.py
│ └── main.py
├── setup.py
└── tests
└── test_main.py
Den tester katalogen er roten til prosjektet med én enkelt testfil. I dette tilfellet kalles testfilen test_main.py. Dette eksemplet viser to kritiske konvensjoner:
- Bruk en tester katalog for å plassere testfiler og nestede testmapper.
- Prefikstestfiler med test. Prefikset angir at filen inneholder testkode.
Forsiktighet
Unngå å bruke test (entallsform) som katalognavn. Navnet på test er en Python-modul, så oppretting av en katalog som heter det samme, overstyrer den. Bruk alltid flertalls tests i stedet.
Testfunksjoner
Et sterkt argument for å bruke Pytest er at det lar deg skrive testfunksjoner. På samme måte som testfiler, må testfunksjoner prefikset med test_. Prefikset test_ sikrer at Pytest samler inn testen og utfører den.
Slik ser en enkel testfunksjon ut:
def test_main():
assert "a string value" == "a string value"
Notat
Hvis du er kjent med unittest, kan det være overraskende å se bruken av assert i testfunksjonen. Vi dekker tydelige deklarasjoner mer detaljert senere, men med Pytest får du omfattende feilrapportering med enkle påskrifter.
Testklasser og testmetoder
I likhet med konvensjonene for filer og funksjoner bruker testklasser og testmetoder følgende konvensjoner:
- Testklasser er prefiks med
Test - Testmetoder er prefiks med
test_
En kjerneforskjell med Pythons unittest bibliotek er at det ikke er behov for arv.
Følgende eksempel bruker disse prefiksene og andre Python-navnekonvensjoner for klasser og metoder. Den demonstrerer en liten testklasse som kontrollerer brukernavn i et program.
class TestUser:
def test_username(self):
assert default() == "default username"
Kjør tester
Pytest er både et testrammeverk og en testløper. Testløperen er en kjørbar fil på kommandolinjen som (på et høyt nivå) kan:
- Utfør samlingen av tester ved å finne alle testfiler, testklasser og testfunksjoner for en testkjøring.
- Start en testkjøring ved å utføre alle testene.
- Hold oversikt over feil, feil og bestått tester.
- Gi omfattende rapportering på slutten av en testkjøring.
Notat
Fordi Pytest er et eksternt bibliotek, må det installeres for å kunne bruke det.
Gitt disse innholdet i en test_main.py fil, kan vi se hvordan Pytest oppfører seg som kjører testene:
# contents of test_main.py file
def test_main():
assert True
I kommandolinjen, i samme bane som test_main.py filen finnes, kan vi kjøre pytest kjørbart:
$ pytest
=========================== test session starts ============================
platform -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private/tmp/project
collected 1 item
test_main.py . [100%]
============================ 1 passed in 0.00s =============================
Bak kulissene samler Pytest inn eksempeltesten i testfilen uten nødvendig konfigurasjon.
Den kraftige deklarasjonen
Så langt bruker testeksempler alle vanlig assert samtale. I Python brukes vanligvis ikke setningen til tester, fordi den assert mangler riktig rapportering når deklarasjonen mislykkes. Pytest har imidlertid ikke denne begrensningen. Bak kulissene gjør Pytest det mulig for setningen å utføre rike sammenligninger uten å tvinge brukeren til å skrive mer kode eller konfigurere noe.
Ved å bruke den enkle assert setningen kan du benytte deg av Pythons operatorer, for eksempel , >, <, !=eller >=<=. Alle Python-operatorer er gyldige. Denne funksjonen kan være den viktigste enkeltfunksjonen i Pytest: Du trenger ikke å lære ny syntaks for å skrive deklarasjoner.
La oss se hvordan dette oversettes når du arbeider med vanlige sammenligninger med Python-objekter. I dette tilfellet går vi gjennom feilrapporten når vi sammenligner lange strenger:
================================= FAILURES =================================
____________________________ test_long_strings _____________________________
def test_long_strings():
left = "this is a very long strings to be compared with another long string"
right = "This is a very long string to be compared with another long string"
> assert left == right
E AssertionError: assert 'this is a ve...r long string' == 'This is a ve...r long string'
E - This is a very long string to be compared with another long string
E ? ^
E + this is a very long strings to be compared with another long string
E ? ^ +
test_main.py:4: AssertionError
Pytest viser nyttig kontekst rundt feilen: feil foringsrør i begynnelsen av strengen og et ekstra tegn i et ord. Men utover strenger kan Pytest hjelpe med andre objekter og datastrukturer. Slik fungerer den for eksempel med lister:
________________________________ test_lists ________________________________
def test_lists():
left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
right = ["sugar", "coffee", "wheat", "salt", "water", "milk"]
> assert left == right
E AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'co...ater', 'milk']
E At index 1 diff: 'wheat' != 'coffee'
E Full diff:
E - ['sugar', 'coffee', 'wheat', 'salt', 'water', 'milk']
E ? ---------
E + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E ? +++++++++
test_main.py:9: AssertionError
Denne rapporten identifiserer at indeks 1 (andre element i listen) er forskjellig. Ikke bare identifiserer den indeksnummeret, det gir også en representasjon av feilen. Bortsett fra elementsammenligninger, kan den også rapportere om elementer mangler, og gi informasjon som kan fortelle deg nøyaktig hvilket element som kan være. I følgende tilfelle er det "milk":
________________________________ test_lists ________________________________
def test_lists():
left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
right = ["sugar", "wheat", "salt", "water", "milk"]
> assert left == right
E AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'wh...ater', 'milk']
E At index 2 diff: 'coffee' != 'salt'
E Left contains one more item: 'milk'
E Full diff:
E - ['sugar', 'wheat', 'salt', 'water', 'milk']
E + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E ? ++++++++++
test_main.py:9: AssertionError
Til slutt, la oss se hvordan det fungerer med ordlister. Sammenligning av to store ordlister kan være overveldende hvis det er feil, men Pytest gjør en fremragende jobb med å gi kontekst og finne feilen:
____________________________ test_dictionaries _____________________________
def test_dictionaries():
left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
> assert left == right
E AssertionError: assert {'county': 'F...rry Ln.', ...} == {'county': 'F...ry Lane', ...}
E Omitting 3 identical items, use -vv to show
E Differing items:
E {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E {'number': 39} != {'number': 38}
E Full diff:
E {
E 'county': 'Frett',...
E
E ...Full output truncated (12 lines hidden), use '-vv' to show
I denne testen er det to feil i ordlisten. Den ene er at den "street" verdien er forskjellig, og den andre er at "number" ikke samsvarer.
Pytest oppdager nøyaktig disse forskjellene (selv om det er én feil i én enkelt test). Fordi ordlistene inneholder mange elementer, utelater Pytest de identiske delene og viser bare relevant innhold. La oss se hva som skjer hvis vi bruker det foreslåtte -vv flagget for å øke detaljnivået i utdataene:
____________________________ test_dictionaries _____________________________
def test_dictionaries():
left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
> assert left == right
E AssertionError: assert {'county': 'Frett',\n 'number': 39,\n 'state': 'Nevada',\n 'street': 'Ferry Ln.',\n 'zipcode': 30877} == {'county': 'Frett',\n 'number': 38,\n 'state': 'Nevada',\n 'street': 'Ferry Lane',\n 'zipcode': 30877}
E Common items:
E {'county': 'Frett', 'state': 'Nevada', 'zipcode': 30877}
E Differing items:
E {'number': 39} != {'number': 38}
E {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E Full diff:
E {
E 'county': 'Frett',
E - 'number': 38,
E ? ^
E + 'number': 39,
E ? ^
E 'state': 'Nevada',
E - 'street': 'Ferry Lane',
E ? - ^
E + 'street': 'Ferry Ln.',
E ? ^
E 'zipcode': 30877,
E }
Ved å kjøre pytest -vvøker rapporteringen detaljmengden og gir en detaljert sammenligning. Ikke bare oppdager og viser denne rapporten feilen, men den lar deg raskt gjøre endringer for å løse problemet.