Grunderna i Pytest
Nu ska vi börja testa med Pytest. Som vi nämnde i föregående lektion är Pytest mycket konfigurerbar och kan hantera komplexa testsviter. Men det krävs inte mycket för att börja skriva tester. Ju enklare ett ramverk är, desto bättre kan du skriva tester.
I slutet av det här avsnittet bör du ha allt du behöver för att börja skriva dina första tester och köra dem med Pytest.
Praxis
Innan vi går in på skrivtester måste vi ta upp några av de testkonventioner som Pytest förlitar sig på.
Det finns inga hårda regler för testfiler, testkataloger eller allmänna testlayouter i Python. Genom att känna till dessa regler kan du dra nytta av automatisk testidentifiering och körning utan att behöva någon extra konfiguration.
Testar katalog- och testfiler
Huvudkatalogen för tester är testkatalogen. Du kan placera den här katalogen på rotnivån i projektet, men det är inte heller ovanligt att se den tillsammans med kodmoduler.
Kommentar
I den här modulen använder vi som standard tester i roten av ett projekt.
Nu ska vi se hur roten i ett litet Python-projekt med namnet jformat
ser ut:
.
├── README.md
├── jformat
│ ├── __init__.py
│ └── main.py
├── setup.py
└── tests
└── test_main.py
Testkatalogen finns i roten i projektet med en enda testfil. I det här fallet anropas testfilen test_main.py. Det här exemplet visar två kritiska konventioner:
- Använd en testkatalog för att placera testfiler och kapslade testkataloger.
- Prefixtestfiler med test. Prefixet anger att filen innehåller testkod.
Varning
Undvik att använda test
(singularform) som katalognamn. Namnet test
är en Python-modul, så att skapa en katalog med namnet samma skulle åsidosätta den. Använd alltid plural tests
i stället.
Textfunktioner
Ett av de starka argumenten för att använda Pytest är att du kan skriva testfunktioner. På samma sätt som testfiler måste testfunktionerna vara prefix med test_
. Prefixet test_
säkerställer att Pytest samlar in testet och kör det.
Så här ser en enkel testfunktion ut:
def test_main():
assert "a string value" == "a string value"
Kommentar
Om du är bekant med unittest
kan det vara förvånande att se användningen av assert
i testfunktionen. Vi tar upp vanliga påståenden mer detaljerat senare, men med Pytest får du omfattande felrapportering med enkla påståenden.
Testklasser och testmetoder
På samma sätt som konventionerna för filer och funktioner använder testklasser och testmetoder följande konventioner:
- Testklasser är prefix med
Test
- Testmetoder är prefix med
test_
En viktig skillnad med Pythons unittest
bibliotek är att det inte finns något behov av arv.
I följande exempel används dessa prefix och andra Python-namngivningskonventioner för klasser och metoder. Den visar en liten testklass som kontrollerar användarnamn i ett program.
class TestUser:
def test_username(self):
assert default() == "default username"
Köra tester
Pytest är både ett testramverk och en testlöpare. Testkörare är en körbar fil på kommandoraden som på hög nivå kan:
- Utför samlingen med tester genom att hitta alla testfiler, testklasser och testfunktioner för en testkörning.
- Initiera en testkörning genom att köra alla tester.
- Håll reda på fel, fel och att skicka tester.
- Ge omfattande rapportering i slutet av en testkörning.
Kommentar
Eftersom Pytest är ett externt bibliotek måste det installeras för att kunna använda det.
Med tanke på innehållet i en test_main.py fil kan vi se hur Pytest beter sig när testerna körs:
# contents of test_main.py file
def test_main():
assert True
På kommandoraden i samma sökväg där den test_main.py filen finns kan vi köra den pytest
körbara filen:
$ 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 =============================
I bakgrunden samlar Pytest in exempeltestet i testfilen utan någon konfiguration som behövs.
Kraftfullt påstående
Hittills använder våra testexempel alla det vanliga assert
anropet. I Python används vanligtvis inte -instruktionen assert
för tester eftersom den saknar korrekt rapportering när försäkran misslyckas. Pytest har dock inte den här begränsningen. I bakgrunden gör Pytest det möjligt för instruktionen att utföra omfattande jämförelser utan att tvinga användaren att skriva mer kod eller konfigurera något.
Med den enkla assert
instruktionen kan du använda Pythons operatorer. Till exempel: >
, <
, !=
, >=
eller <=
. Alla Python-operatorer är giltiga. Den här funktionen kan vara den enskilt viktigaste funktionen i Pytest: du behöver inte lära dig ny syntax för att skriva intyg.
Nu ska vi se hur det översätts när du hanterar vanliga jämförelser med Python-objekt. I det här fallet går vi igenom felrapporten när du jämför långa strängar:
================================= 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 visar användbar kontext kring felet. Ett felaktigt hölje i början av strängen och ett extra tecken i ett ord. Men utöver strängar kan Pytest hjälpa till med andra objekt och datastrukturer. Så här fungerar det till exempel med listor:
________________________________ 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
Den här rapporten identifierar att index 1 (andra objektet i listan) skiljer sig. Det identifierar inte bara indexnumret, det ger också en representation av felet. Förutom objektjämförelser kan den även rapportera om objekt saknas och tillhandahålla information som kan berätta exakt vilket objekt som kan vara. I följande fall är "milk"
det :
________________________________ 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
Slutligen ska vi se hur det fungerar med ordlistor. Att jämföra två stora ordlistor kan vara överväldigande om det uppstår fel, men Pytest gör ett enastående jobb med att tillhandahålla kontext och hitta felet:
____________________________ 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 det här testet finns det två fel i ordlistan. Det ena är att värdet "street"
är annorlunda, och det andra är att det "number"
inte matchar.
Pytest identifierar dessa skillnader korrekt (även om det är ett fel i ett enda test). Eftersom ordlistorna innehåller många objekt utelämnar Pytest de identiska delarna och visar bara relevant innehåll. Nu ska vi se vad som händer om vi använder den föreslagna -vv
flaggan för att öka utförligheten i utdata:
____________________________ 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 }
Genom att köra pytest -vv
ökar rapporteringen mängden information och ger en detaljerad jämförelse. Rapporten identifierar och visar inte bara felet, utan gör att du snabbt kan göra ändringar för att åtgärda problemet.