Øvelse
I denne øvelse vil du bruge pytest med @pytest.mark.parametrize til at teste en funktion, inklusive tilfælde, hvor der forventes en undtagelse. Derefter refaktorerer du en klassebaseret test fra xUnit-stil setup_method() og teardown_method() tilgang til en fastgørelse. Ved at bruge parametrisering og fixturer kan du skrive tests, der dækker flere tilfælde med mindre gentagelse og en klarere opsætning.
For hjælp til opsætning, se Python-dokumentationen for virtuelle miljøer, pip-dokumentationen for pip install og pytest-dokumentationen for installation af pytest. For flere detaljer om pytest-funktionerne i denne øvelse, se pytest-dokumentationen for parametrisering af testfunktioner, assertions om forventede undtagelser, fixtures, xUnit-lignende opsætning samt midlertidige mapper og filer.
Før du starter
Brug Python 3.10 eller nyere, som beskrevet i modulforudsætningerne. Hvis du allerede har et virtuelt miljø med pytest installeret og aktiveret, så fortsæt til trin 1. Ellers skal du oprette og aktivere et virtuelt miljø i den mappe, hvor du vil oprette test_advanced.py.
På Windows PowerShell:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest --version
På macOS, Linux eller Windows-undersystem til Linux:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest --version
Versionskommandoen skal printe en pytest-version, lignende denne output:
pytest x.y.z
Installationskommandoerne fastgør ikke pytest til en bestemt version. Pip bruger pakkemetadata til at vælge en stabil pytest-udgivelse, der er kompatibel med Python-versionen i det virtuelle miljø.
Brug python -m pytest den gennem hele denne øvelse, så pytest kører fra det aktive miljø. Hvis Windows PowerShell blokerer Activate.ps1, se dokumentationen Python venv for PowerShell execution policy-muligheden, eller kør det virtuelle miljøs Python direkte for pip- og pytest-kommandoerne. Eksempel:
.\.venv\Scripts\python -m pip install --upgrade pip
.\.venv\Scripts\python -m pip install pytest
.\.venv\Scripts\python -m pytest -v test_advanced.py
Trin 1 – Tilføj en fil med test til denne øvelse
I den samme mappe, hvor du kører pytest, opretter du en ny testfil kaldet test_advanced.py. Læg ikke filen i .venv-mappen . Tilføj følgende kode:
import pytest def str_to_bool(string): normalized = string.lower() if normalized in ["yes", "y", "1"]: return True if normalized in ["no", "n", "0"]: return False raise ValueError(f"Cannot convert {string!r} to a boolean")Funktionen
str_to_bool()accepterer en streng som input. Den returnererTruefor anerkendte sande værdier, returnererFalsefor anerkendte falske værdier og hæver aValueErrorfor enhver anden værdi.Tilføj testene for funktionen
str_to_bool()i den samme fil. Brug@pytest.mark.parametrizeden til at teste de sande værdier først:@pytest.mark.parametrize("string", ["Y", "y", "1", "YES"]) def test_str_to_bool_true(string): assert str_to_bool(string) is TrueDerefter skal du tilføje en anden test med de falske værdier:
@pytest.mark.parametrize("string", ["N", "n", "0", "NO"]) def test_str_to_bool_false(string): assert str_to_bool(string) is FalseDer findes nu to testfunktioner, der dækker repræsentative input for både og
TrueFalsereturværdierne.Tilføj endelig en parametriseret test for værdier, som funktionen ikke bør acceptere. Brug
pytest.raises()som kontekstmanager, så testen kun består, nårstr_to_bool()den forventede undtagelse aktiveres:@pytest.mark.parametrize("string", ["maybe", "2"]) def test_str_to_bool_invalid(string): with pytest.raises(ValueError, match="Cannot convert"): str_to_bool(string)Argumentet
matchtjekker undtagelsesbeskeden med et regulært udtryk. Her tjekker testen kun den stabile del af beskeden, så den ikke afhænger af den præcise ugyldige værdi.
Seddel
For enkelhedens skyld holder denne øvelse koden under test og testene i samme fil. I virkelige Python-projekter er applikationskode og tests normalt opdelt i forskellige filer og mapper, såsom en src/-pakke og en tests/-mappe.
Trin 2 – Kør testene, og udforsk rapporten
Når du har tilføjet testene, kør pytest og inspicer outputtet. Brug flaget for øget ordstil (-v) så du kan se hver inputværdi behandles som en separat test.
På Windows PowerShell:
python -m pytest -v test_advanced.py
På macOS, Linux eller Windows-undersystem til Linux:
python -m pytest -v test_advanced.py
Outputtet bør være lignende følgende rapport:
============================= test session starts ==============================
platform ... -- Python 3.x.y, pytest-x.y.z, pluggy-x.y.z -- ...
rootdir: ...
...
collected 10 items
test_advanced.py::test_str_to_bool_true[Y] PASSED [ 10%]
test_advanced.py::test_str_to_bool_true[y] PASSED [ 20%]
test_advanced.py::test_str_to_bool_true[1] PASSED [ 30%]
test_advanced.py::test_str_to_bool_true[YES] PASSED [ 40%]
test_advanced.py::test_str_to_bool_false[N] PASSED [ 50%]
test_advanced.py::test_str_to_bool_false[n] PASSED [ 60%]
test_advanced.py::test_str_to_bool_false[0] PASSED [ 70%]
test_advanced.py::test_str_to_bool_false[NO] PASSED [ 80%]
test_advanced.py::test_str_to_bool_invalid[maybe] PASSED [ 90%]
test_advanced.py::test_str_to_bool_invalid[2] PASSED [100%]
============================== 10 passed in 0.01s ==============================
Dit operativsystem, Python-version, pytest-version, eksekverbar sti, rodmappe, plugin- og cachelinjer, samlings-progress-formatering, linjewrapping og runtime kan variere. Selvom du kun skrev tre testfunktioner, samlede pytest 10 tests, fordi hver parametriseret inputværdi kører som sin egen testcase.
Trin 3 - Refaktorerer opsætning og demontering til en lampe
Tilføj en klassebaseret test til test_advanced.py filen. Denne test bruger xUnit-stil
setup_method()ogteardown_method()metoder til at oprette og fjerne en fil omkring hver testmetode:from pathlib import Path class TestFile: def setup_method(self): self.path = Path("done") self.path.write_text("1", encoding="utf-8") def teardown_method(self): self.path.unlink(missing_ok=True) def test_done_file(self): contents = self.path.read_text(encoding="utf-8") assert contents == "1"Denne test kan bestå sig, men den skriver et fast filnavn i den aktuelle arbejdsmappe og kræver separat oprydningskode. Hvis en testkørsel afbrydes eller tests kører samtidig, såsom med
pytest-xdisteller overlappende lokale kørsler, kan denne tilgang efterlade forældede filer, overskrive en eksisterende fil med navnet færdig eller få tests til at påvirke hinanden.Tilføj en modulniveau-fixture, der bruger den indbyggede
tmp_pathfixture til at oprette en midlertidig fil. I næste trin opdaterer du klassen til at bruge denne fixture i stedet for opsætnings- og nedbrydningsmetoderne. For læsbarhed placeres denne modulniveau-fixture efter de parametriseredestr_to_booltests og før klassen.TestFilePytest opdager modulniveau-armaturer uanset hvor de optræder i modulet, så positionen er for menneskelige læsere, ikke pytest:@pytest.fixture def tmp_file(tmp_path): def write(): path = tmp_path / "done" path.write_text("1", encoding="utf-8") return path return writeFixturen
tmp_file()bruger pyteststmp_pathfixture, som giver en unikpathlib.Pathmidlertidig mappe for hver testfunktionskald, inklusive hver parametriseret case. Pytest administrerer den midlertidige mappe, så testen ikke afhænger af en hardkodet filsti.Slet den tidligere
TestFileklasse, inklusive denssetup_method()ogteardown_method()metoder, og erstat den med denne endelige version, der bruger fixturen i stedet for opsætnings- og nedtrækningsmetoderne:class TestFile: def test_done_file(self, tmp_file): path = tmp_file() contents = path.read_text(encoding="utf-8") assert contents == "1"Du kan også fjerne importen
from pathlib import Path, du tilføjede i det forrige trin, fordi testen ikke længere opretter etPathobjekt direkte.
Kontrollér dit arbejde
Nu burde du have en Python-fil kaldet test_advanced.py som indeholder:
- En
str_to_bool()funktion, der accepterer en streng og returnerer en boolesk værdi for genkendte sande og falske værdier. - Tre parametriserede tests for
str_to_bool()funktionen: en der testerTrueværdier, en der testerFalseværdier, og en der tjekker ugyldige værdier hæverValueError. - En speciallavet pytest-fixture, der bruger den indbyggede
tmp_pathfixture til at oprette en midlertidig færdig fil med noget indhold. - En enkelt
TestFileklasse med én metode, der bruger den brugerdefineredetmp_filefixture til at oprette og læse filen. - Ingen
from pathlib import Pathimport og ingensetup_method()/teardown_method()metoder i den endelige version.
Den endelige fil bør ligne følgende:
import pytest
def str_to_bool(string):
normalized = string.lower()
if normalized in ["yes", "y", "1"]:
return True
if normalized in ["no", "n", "0"]:
return False
raise ValueError(f"Cannot convert {string!r} to a boolean")
@pytest.mark.parametrize("string", ["Y", "y", "1", "YES"])
def test_str_to_bool_true(string):
assert str_to_bool(string) is True
@pytest.mark.parametrize("string", ["N", "n", "0", "NO"])
def test_str_to_bool_false(string):
assert str_to_bool(string) is False
@pytest.mark.parametrize("string", ["maybe", "2"])
def test_str_to_bool_invalid(string):
with pytest.raises(ValueError, match="Cannot convert"):
str_to_bool(string)
@pytest.fixture
def tmp_file(tmp_path):
def write():
path = tmp_path / "done"
path.write_text("1", encoding="utf-8")
return path
return write
class TestFile:
def test_done_file(self, tmp_file):
path = tmp_file()
contents = path.read_text(encoding="utf-8")
assert contents == "1"
Kør testfilen igen.
På Windows PowerShell:
python -m pytest -v test_advanced.py
På macOS, Linux eller Windows-undersystem til Linux:
python -m pytest -v test_advanced.py
Den endelige rapport skal vise 11 indsamlede tests: 10 tilfælde fra de parametriserede tests og én klassebaseret test.
============================= test session starts ==============================
platform ... -- Python 3.x.y, pytest-x.y.z, pluggy-x.y.z -- ...
rootdir: ...
...
collected 11 items
test_advanced.py::test_str_to_bool_true[Y] PASSED [ 9%]
test_advanced.py::test_str_to_bool_true[y] PASSED [ 18%]
test_advanced.py::test_str_to_bool_true[1] PASSED [ 27%]
test_advanced.py::test_str_to_bool_true[YES] PASSED [ 36%]
test_advanced.py::test_str_to_bool_false[N] PASSED [ 45%]
test_advanced.py::test_str_to_bool_false[n] PASSED [ 54%]
test_advanced.py::test_str_to_bool_false[0] PASSED [ 63%]
test_advanced.py::test_str_to_bool_false[NO] PASSED [ 72%]
test_advanced.py::test_str_to_bool_invalid[maybe] PASSED [ 81%]
test_advanced.py::test_str_to_bool_invalid[2] PASSED [ 90%]
test_advanced.py::TestFile::test_done_file PASSED [100%]
============================== 11 passed in 0.01s ==============================
Alle prøver burde bestå uden fejl.