Atklājiet pilnu Pytest potenciālu ar paplašinātām fiksatoru metodēm. Apgūstiet parametrizēto testēšanu un viltotu objektu integrāciju robustai Python testēšanai.
Pytest paplašināto fiksatoru apgūšana: parametrizētā testēšana un viltotu objektu (mock) integrācija
Pytest ir jaudīgs un elastīgs testēšanas ietvars Python valodai. Tā vienkāršība un paplašināmība padara to par iecienītu izvēli izstrādātāju vidū visā pasaulē. Viena no Pytest pārliecinošākajām funkcijām ir tā fiksatoru sistēma, kas nodrošina elegantu un atkārtoti lietojamu testu iestatīšanu. Šis emuāra ieraksts iedziļinās paplašinātās fiksatoru metodēs, īpaši koncentrējoties uz parametrizēto testēšanu un viltotu objektu (mock) integrāciju. Mēs izpētīsim, kā šīs metodes var ievērojami uzlabot jūsu testēšanas darbplūsmu, nodrošinot robustāku un uzturamāku kodu.
Izpratne par Pytest fiksatoriem
Pirms iedziļināmies sarežģītākās tēmās, īsumā atkārtosim Pytest fiksatoru pamatus. Fiksators ir funkcija, kas tiek izpildīta pirms katras testa funkcijas, kurai tā ir piesaistīta. To izmanto, lai nodrošinātu fiksētu sākuma stāvokli testiem, nodrošinot konsekvenci un samazinot atkārtota koda rakstīšanu. Fiksatori var veikt tādus uzdevumus kā:
- Datu bāzes savienojuma izveide
- Pagaidu failu vai direktoriju izveide
- Objektu inicializēšana ar specifiskām konfigurācijām
- Autentifikācija ar API
Fiksatori veicina koda atkārtotu izmantošanu un padara jūsu testus lasāmākus un uzturamākus. Tos var definēt dažādos tvērumos (funkcija, modulis, sesija), lai kontrolētu to dzīves ciklu un resursu patēriņu.
Pamata fiksatora piemērs
Šeit ir vienkāršs Pytest fiksatora piemērs, kas izveido pagaidu direktoriju:
import pytest
import tempfile
import os
@pytest.fixture
def temp_dir():
with tempfile.TemporaryDirectory() as tmpdir:
yield tmpdir
Lai izmantotu šo fiksatoru testā, vienkārši iekļaujiet to kā argumentu savai testa funkcijai:
def test_create_file(temp_dir):
filepath = os.path.join(temp_dir, "test_file.txt")
with open(filepath, "w") as f:
f.write("Hello, world!")
assert os.path.exists(filepath)
Parametrizētā testēšana ar Pytest
Parametrizētā testēšana ļauj palaist vienu un to pašu testa funkciju vairākas reizes ar dažādiem ievades datu komplektiem. Tas ir īpaši noderīgi, lai testētu funkcijas ar mainīgām ievadēm un gaidāmajiem rezultātiem. Pytest nodrošina @pytest.mark.parametrize dekoratoru parametrizētu testu ieviešanai.
Parametrizētās testēšanas priekšrocības
- Samazina koda dublēšanos: Izvairieties no vairāku gandrīz identisku testa funkciju rakstīšanas.
- Uzlabo testa pārklājumu: Viegli testējiet plašāku ievades vērtību diapazonu.
- Uzlabo testa lasāmību: Skaidri definējiet ievades vērtības un gaidāmos rezultātus katram testa gadījumam.
Pamata parametrizācijas piemērs
Pieņemsim, ka jums ir funkcija, kas saskaita divus skaitļus:
def add(x, y):
return x + y
Jūs varat izmantot parametrizēto testēšanu, lai testētu šo funkciju ar dažādām ievades vērtībām:
import pytest
@pytest.mark.parametrize("x, y, expected", [
(1, 2, 3),
(5, 5, 10),
(-1, 1, 0),
(0, 0, 0),
])
def test_add(x, y, expected):
assert add(x, y) == expected
Šajā piemērā dekorators @pytest.mark.parametrize definē četrus testa gadījumus, katru ar dažādām vērtībām x, y un gaidāmajam rezultātam. Pytest palaidīs test_add funkciju četras reizes, vienu reizi katram parametru komplektam.
Paplašinātas parametrizācijas metodes
Pytest piedāvā vairākas paplašinātas parametrizācijas metodes, tostarp:
- Fiksatoru izmantošana ar parametrizāciju: Apvienojiet fiksatorus ar parametrizāciju, lai nodrošinātu dažādas iestatīšanas katram testa gadījumam.
- ID testa gadījumiem: Piešķiriet pielāgotus ID testa gadījumiem, lai uzlabotu ziņošanu un atkļūdošanu.
- Netiešā parametrizācija: Parametrizējiet fiksatoriem nodotos argumentus, ļaujot dinamiski izveidot fiksatorus.
Fiksatoru izmantošana ar parametrizāciju
Tas ļauj dinamiski konfigurēt fiksatorus, pamatojoties uz testam nodotajiem parametriem. Iedomājieties, ka jūs testējat funkciju, kas mijiedarbojas ar datu bāzi. Jūs varētu vēlēties izmantot dažādas datu bāzes konfigurācijas (piemēram, dažādas savienojuma virknes) dažādiem testa gadījumiem.
import pytest
@pytest.fixture
def db_config(request):
if request.param == "prod":
return {"host": "prod.example.com", "port": 5432}
elif request.param == "test":
return {"host": "test.example.com", "port": 5433}
else:
raise ValueError("Invalid database environment")
@pytest.fixture
def db_connection(db_config):
# Simulate establishing a database connection
print(f"Connecting to database at {db_config['host']}:{db_config['port']}")
return f"Connection to {db_config['host']}"
@pytest.mark.parametrize("db_config", ["prod", "test"], indirect=True)
def test_database_interaction(db_connection):
# Your test logic here, using the db_connection fixture
print(f"Using connection: {db_connection}")
assert "Connection" in db_connection
Šajā piemērā db_config fiksators ir parametrizēts. Arguments indirect=True norāda Pytest nodot parametrus ("prod" un "test") db_config fiksatora funkcijai. Pēc tam db_config fiksators atgriež dažādas datu bāzes konfigurācijas, pamatojoties uz parametra vērtību. db_connection fiksators izmanto db_config fiksatoru, lai izveidotu datu bāzes savienojumu. Visbeidzot, test_database_interaction funkcija izmanto db_connection fiksatoru, lai mijiedarbotos ar datu bāzi.
ID testa gadījumiem
Pielāgoti ID nodrošina aprakstošākus nosaukumus jūsu testa gadījumiem testa pārskatā, padarot vieglāku kļūdu identificēšanu un atkļūdošanu.
import pytest
@pytest.mark.parametrize(
"input_string, expected_output",
[
("hello", "HELLO"),
("world", "WORLD"),
("", ""),
],
ids=["lowercase_hello", "lowercase_world", "empty_string"],
)
def test_uppercase(input_string, expected_output):
assert input_string.upper() == expected_output
Bez ID Pytest ģenerētu vispārīgus nosaukumus, piemēram, test_uppercase[0], test_uppercase[1] utt. Ar ID testa pārskatā tiks parādīti jēgpilnāki nosaukumi, piemēram, test_uppercase[lowercase_hello].
Netiešā parametrizācija
Netiešā parametrizācija ļauj parametrizēt ievadi fiksatoram, nevis tieši testa funkcijai. Tas ir noderīgi, ja vēlaties izveidot dažādas fiksatora instances, pamatojoties uz parametra vērtību.
import pytest
@pytest.fixture
def input_data(request):
if request.param == "valid":
return {"name": "John Doe", "email": "john.doe@example.com"}
elif request.param == "invalid":
return {"name": "", "email": "invalid-email"}
else:
raise ValueError("Invalid input data type")
def validate_data(data):
if not data["name"]:
return False, "Name cannot be empty"
if "@" not in data["email"]:
return False, "Invalid email address"
return True, "Valid data"
@pytest.mark.parametrize("input_data", ["valid", "invalid"], indirect=True)
def test_validate_data(input_data):
is_valid, message = validate_data(input_data)
if input_data == {"name": "John Doe", "email": "john.doe@example.com"}:
assert is_valid is True
assert message == "Valid data"
else:
assert is_valid is False
assert message in ["Name cannot be empty", "Invalid email address"]
Šajā piemērā input_data fiksators ir parametrizēts ar vērtībām "valid" un "invalid". Arguments indirect=True norāda Pytest nodot šīs vērtības input_data fiksatora funkcijai. Pēc tam input_data fiksators atgriež dažādas datu vārdnīcas, pamatojoties uz parametra vērtību. Pēc tam test_validate_data funkcija izmanto input_data fiksatoru, lai testētu validate_data funkciju ar dažādiem ievades datiem.
Viltotu objektu (mocking) izmantošana ar Pytest
Viltotu objektu izmantošana (mocking) ir tehnika, ko izmanto, lai testēšanas laikā aizstātu reālas atkarības ar kontrolētiem aizstājējiem (mocks). Tas ļauj izolēt testējamo kodu un izvairīties no paļaušanās uz ārējām sistēmām, piemēram, datu bāzēm, API vai failu sistēmām.
Viltotu objektu izmantošanas priekšrocības
- Koda izolēšana: Testējiet kodu izolēti, nepaļaujoties uz ārējām atkarībām.
- Uzvedības kontrolēšana: Definējiet atkarību uzvedību, piemēram, atgrieztās vērtības un izņēmumus.
- Testu paātrināšana: Izvairieties no lēnām vai neuzticamām ārējām sistēmām.
- Robežgadījumu testēšana: Simulējiet kļūdu situācijas un robežgadījumus, kurus ir grūti reproducēt reālā vidē.
unittest.mock bibliotēkas izmantošana
Python nodrošina unittest.mock bibliotēku viltotu objektu izveidei. Pytest nevainojami integrējas ar unittest.mock, padarot atkarību aizstāšanu testos vienkāršu.
Pamata viltotu objektu piemērs
Pieņemsim, ka jums ir funkcija, kas iegūst datus no ārēja API:
import requests
def get_data_from_api(url):
response = requests.get(url)
response.raise_for_status() # Raise an exception for bad status codes
return response.json()
Lai testētu šo funkciju, faktiski neveicot pieprasījumu API, jūs varat aizstāt requests.get funkciju ar viltotu objektu:
import pytest
import requests
from unittest.mock import patch
@patch("requests.get")
def test_get_data_from_api(mock_get):
# Configure the mock to return a specific response
mock_get.return_value.json.return_value = {"data": "test data"}
mock_get.return_value.status_code = 200
# Call the function being tested
data = get_data_from_api("https://example.com/api")
# Assert that the mock was called with the correct URL
mock_get.assert_called_once_with("https://example.com/api")
# Assert that the function returned the expected data
assert data == {"data": "test data"}
Šajā piemērā dekorators @patch("requests.get") aizstāj requests.get funkciju ar viltotu objektu. Arguments mock_get ir viltotais objekts. Mēs varam konfigurēt viltoto objektu, lai tas atgrieztu konkrētu atbildi, un pārbaudīt, vai tas tika izsaukts ar pareizo URL.
Viltotu objektu izmantošana ar fiksatoriem
Jūs varat arī izmantot fiksatorus, lai izveidotu un pārvaldītu viltotus objektus. Tas var būt noderīgi, lai koplietotu viltotus objektus starp vairākiem testiem vai lai izveidotu sarežģītākas viltotu objektu iestatīšanas.
import pytest
import requests
from unittest.mock import Mock
@pytest.fixture
def mock_api_get():
mock = Mock()
mock.return_value.json.return_value = {"data": "test data"}
mock.return_value.status_code = 200
return mock
@pytest.fixture
def patched_get(mock_api_get, monkeypatch):
monkeypatch.setattr(requests, "get", mock_api_get)
return mock_api_get
def test_get_data_from_api(patched_get):
# Call the function being tested
data = get_data_from_api("https://example.com/api")
# Assert that the mock was called with the correct URL
patched_get.assert_called_once_with("https://example.com/api")
# Assert that the function returned the expected data
assert data == {"data": "test data"}
Šeit mock_api_get izveido viltotu objektu un atgriež to. Pēc tam patched_get izmanto monkeypatch, pytest fiksatoru, lai aizstātu reālo `requests.get` ar viltoto objektu. Tas ļauj citiem testiem izmantot to pašu viltoto API galapunktu.
Paplašinātas viltotu objektu metodes
Pytest un unittest.mock piedāvā vairākas paplašinātas viltotu objektu metodes, tostarp:
- Blakusefekti: Definējiet pielāgotu uzvedību viltotiem objektiem, pamatojoties uz ievades argumentiem.
- Īpašību aizstāšana: Aizstājiet objektu īpašības.
- Konteksta pārvaldnieki: Izmantojiet viltotus objektus konteksta pārvaldniekos pagaidu aizstāšanai.
Blakusefekti
Blakusefekti ļauj definēt pielāgotu uzvedību jūsu viltotajiem objektiem, pamatojoties uz saņemtajiem ievades argumentiem. Tas ir noderīgi, lai simulētu dažādus scenārijus vai kļūdu situācijas.
import pytest
from unittest.mock import Mock
def test_side_effect():
mock = Mock()
mock.side_effect = [1, 2, 3]
assert mock() == 1
assert mock() == 2
assert mock() == 3
with pytest.raises(StopIteration):
mock()
Šis viltotais objekts atgriež 1, 2 un 3 secīgos izsaukumos, pēc tam izraisa StopIteration izņēmumu, kad saraksts ir izsmelts.
Īpašību aizstāšana ar viltotiem objektiem
Īpašību aizstāšana ar viltotiem objektiem ļauj simulēt objektu īpašību uzvedību. Tas ir noderīgi, testējot kodu, kas paļaujas uz objektu īpašībām, nevis metodēm.
import pytest
from unittest.mock import patch
class MyClass:
@property
def my_property(self):
return "original value"
def test_property_mocking():
obj = MyClass()
with patch.object(obj, "my_property", new_callable=pytest.PropertyMock) as mock_property:
mock_property.return_value = "mocked value"
assert obj.my_property == "mocked value"
Šis piemērs aizstāj my_property īpašību MyClass objektam, ļaujot jums kontrolēt tās atgriezto vērtību testa laikā.
Konteksta pārvaldnieki
Viltotu objektu izmantošana konteksta pārvaldniekos ļauj īslaicīgi aizstāt atkarības konkrētam koda blokam. Tas ir noderīgi, testējot kodu, kas mijiedarbojas ar ārējām sistēmām vai resursiem, kurus vajadzētu aizstāt tikai uz ierobežotu laiku.
import pytest
from unittest.mock import patch
def test_context_manager_mocking():
with patch("os.path.exists") as mock_exists:
mock_exists.return_value = True
assert os.path.exists("dummy_path") is True
# The mock is automatically reverted after the 'with' block
# Ensure the original function is restored, although we can't really assert
# the real `os.path.exists` function's behavior without a real path.
# The important thing is that the patch is gone after the context.
print("Mock has been removed")
Parametrizācijas un viltotu objektu apvienošana
Šīs divas jaudīgās metodes var apvienot, lai izveidotu vēl sarežģītākus un efektīvākus testus. Jūs varat izmantot parametrizāciju, lai testētu dažādus scenārijus ar dažādām viltotu objektu konfigurācijām.
import pytest
import requests
from unittest.mock import patch
def get_user_data(user_id):
url = f"https://api.example.com/users/{user_id}"
response = requests.get(url)
response.raise_for_status()
return response.json()
@pytest.mark.parametrize(
"user_id, expected_data",
[
(1, {"id": 1, "name": "John Doe"}),
(2, {"id": 2, "name": "Jane Smith"}),
],
)
@patch("requests.get")
def test_get_user_data(mock_get, user_id, expected_data):
mock_get.return_value.json.return_value = expected_data
mock_get.return_value.status_code = 200
data = get_user_data(user_id)
assert data == expected_data
mock_get.assert_called_once_with(f"https://api.example.com/users/{user_id}")
Šajā piemērā funkcija test_get_user_data ir parametrizēta ar dažādām user_id un expected_data vērtībām. Dekorators @patch aizstāj requests.get funkciju. Pytest palaidīs testa funkciju divas reizes, vienu reizi katram parametru komplektam, ar viltotu objektu, kas konfigurēts, lai atgrieztu atbilstošos expected_data.
Labākā prakse paplašināto fiksatoru izmantošanai
- Saglabājiet fiksatorus mērķtiecīgus: Katram fiksatoram jābūt skaidram un konkrētam mērķim.
- Izmantojiet atbilstošus tvērumus: Izvēlieties atbilstošu fiksatora tvērumu (funkcija, modulis, sesija), lai optimizētu resursu izmantošanu.
- Dokumentējiet fiksatorus: Skaidri dokumentējiet katra fiksatora mērķi un lietojumu.
- Izvairieties no pārmērīgas viltotu objektu izmantošanas: Aizstājiet tikai tās atkarības, kas nepieciešamas testējamā koda izolēšanai.
- Rakstiet skaidrus apgalvojumus (assertions): Pārliecinieties, ka jūsu apgalvojumi ir skaidri un konkrēti, pārbaudot gaidīto testējamā koda uzvedību.
- Apsveriet uz testiem balstītu izstrādi (TDD): Rakstiet testus pirms koda rakstīšanas, izmantojot fiksatorus un viltotus objektus, lai vadītu izstrādes procesu.
Noslēgums
Pytest paplašinātās fiksatoru metodes, ieskaitot parametrizēto testēšanu un viltotu objektu integrāciju, nodrošina jaudīgus rīkus robustu, efektīvu un uzturamu testu rakstīšanai. Apgūstot šīs metodes, jūs varat ievērojami uzlabot sava Python koda kvalitāti un racionalizēt testēšanas darbplūsmu. Atcerieties koncentrēties uz skaidru, mērķtiecīgu fiksatoru izveidi, izmantojot atbilstošus tvērumus un rakstot visaptverošus apgalvojumus. Ar praksi jūs spēsiet pilnībā izmantot Pytest fiksatoru sistēmas potenciālu, lai izveidotu visaptverošu un efektīvu testēšanas stratēģiju.