Skip to content

Commit 9923e91

Browse files
committed
Merge branch '2025-testtips'
2 parents 58b4e55 + ec0fa27 commit 9923e91

9 files changed

Lines changed: 546 additions & 0 deletions

File tree

2025/testtips/pyproject.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[project]
2+
name = "testtips"
3+
version = "0.0.1"
4+
authors = [{name = "ArjanCodes"}]
5+
license = {text = "MIT"}
6+
requires-python = ">=3.12"
7+
dependencies = [
8+
"httpx>=0.28.1",
9+
"hypothesis>=6.135.26",
10+
"pytest>=8.4.1",
11+
"python-dotenv>=1.1.1",
12+
]
13+
14+
[tool.pytest.ini_options]
15+
pythonpath = "."
16+

2025/testtips/tests/test_tips.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# tests/test_weather.py
2+
3+
import sys
4+
5+
import pytest
6+
from weather import WeatherService
7+
8+
# from hypothesis import given
9+
# from hypothesis.strategies import floats
10+
11+
12+
# ✅ Parametrization
13+
@pytest.mark.parametrize(
14+
"city,expected_temp", [("London", 15), ("Berlin", 20), ("Paris", 17)]
15+
)
16+
def test_get_temperature_multiple_cities(monkeypatch, city, expected_temp):
17+
def fake_get(url, params):
18+
class FakeResponse:
19+
def raise_for_status(self):
20+
pass
21+
22+
def json(self):
23+
return {"current": {"temp_c": expected_temp}}
24+
25+
return FakeResponse()
26+
27+
monkeypatch.setattr("weather.httpx.get", fake_get)
28+
29+
service = WeatherService(api_key="fake-key")
30+
temp = service.get_temperature(city)
31+
32+
assert temp == expected_temp
33+
34+
35+
# ✅ pytest.raises
36+
def test_get_temperature_raises_http_error(monkeypatch):
37+
def fake_get(url, params):
38+
class FakeResponse:
39+
def raise_for_status(self):
40+
raise Exception("API error")
41+
42+
return FakeResponse()
43+
44+
monkeypatch.setattr("weather.httpx.get", fake_get)
45+
46+
service = WeatherService(api_key="fake-key")
47+
48+
with pytest.raises(Exception):
49+
service.get_temperature("Tokyo")
50+
51+
52+
# ✅ pytest.mark.skip
53+
@pytest.mark.skip(reason="Skipping this test for demonstration purposes.")
54+
def test_skipped_example():
55+
assert False
56+
57+
58+
# ✅ pytest.mark.skipif
59+
@pytest.mark.skipif(sys.platform == "win32", reason="Does not run on Windows")
60+
def test_only_runs_on_non_windows():
61+
assert True
62+
63+
64+
# ✅ pytest.mark.xfail
65+
@pytest.mark.xfail(reason="Known bug: API sometimes returns wrong temperature")
66+
def test_expected_failure_example():
67+
assert 2 + 2 == 5
68+
69+
70+
# ✅ Hypothesis property-based test
71+
# @given(floats(min_value=-50, max_value=50))
72+
# def test_temperature_range_property(temp):
73+
# assert -50 <= temp <= 50
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import httpx
2+
import pytest
3+
from weather import WeatherService
4+
5+
6+
# Tip 1: Single assert per test
7+
def test_get_temperature_returns_expected_value(monkeypatch):
8+
def fake_get(url, params):
9+
class FakeResponse:
10+
def raise_for_status(self):
11+
pass
12+
13+
def json(self):
14+
return {"current": {"temp_c": 22}}
15+
16+
return FakeResponse()
17+
18+
monkeypatch.setattr("weather.httpx.get", fake_get)
19+
20+
service = WeatherService(api_key="fake-key")
21+
temp = service.get_temperature("Amsterdam")
22+
23+
assert temp == 22
24+
25+
26+
# Tip 2: Clear and descriptive names
27+
def test_get_temperature_for_different_city(monkeypatch):
28+
def fake_get(url, params):
29+
class FakeResponse:
30+
def raise_for_status(self):
31+
pass
32+
33+
def json(self):
34+
return {"current": {"temp_c": 18}}
35+
36+
return FakeResponse()
37+
38+
monkeypatch.setattr("weather.httpx.get", fake_get)
39+
40+
service = WeatherService(api_key="fake-key")
41+
temp = service.get_temperature("Berlin")
42+
43+
assert temp == 18
44+
45+
46+
def test_get_temperature_handles_api_error(monkeypatch):
47+
def fake_get(url, params):
48+
class FakeResponse:
49+
def raise_for_status(self):
50+
raise httpx.HTTPError("API error")
51+
52+
return FakeResponse()
53+
54+
monkeypatch.setattr("weather.httpx.get", fake_get)
55+
56+
service = WeatherService(api_key="fake-key")
57+
58+
with pytest.raises(httpx.HTTPError):
59+
service.get_temperature("Paris")
60+
61+
62+
def test_get_temperature_returns_float(monkeypatch):
63+
def fake_get(url, params):
64+
class FakeResponse:
65+
def raise_for_status(self):
66+
pass
67+
68+
def json(self):
69+
return {"current": {"temp_c": 19.5}}
70+
71+
return FakeResponse()
72+
73+
monkeypatch.setattr("weather.httpx.get", fake_get)
74+
75+
service = WeatherService(api_key="fake-key")
76+
temp = service.get_temperature("Rome")
77+
78+
assert isinstance(temp, float)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import pytest
2+
from weather import WeatherService
3+
4+
5+
# This fixture creates a WeatherService instance you can reuse
6+
@pytest.fixture
7+
def weather_service():
8+
return WeatherService(api_key="fake-key")
9+
10+
11+
def test_get_temperature_returns_expected_value(weather_service, monkeypatch):
12+
"""
13+
Test that get_temperature returns the correct temperature.
14+
"""
15+
16+
def fake_get(url, params):
17+
class FakeResponse:
18+
def raise_for_status(self):
19+
pass
20+
21+
def json(self):
22+
return {"current": {"temp_c": 20}}
23+
24+
return FakeResponse()
25+
26+
monkeypatch.setattr("weather.httpx.get", fake_get)
27+
28+
temp = weather_service.get_temperature("Amsterdam")
29+
assert temp == 20
30+
31+
32+
def test_get_temperature_returns_float(weather_service, monkeypatch):
33+
"""
34+
Test that get_temperature returns a float value.
35+
"""
36+
37+
def fake_get(url, params):
38+
class FakeResponse:
39+
def raise_for_status(self):
40+
pass
41+
42+
def json(self):
43+
return {"current": {"temp_c": 18.5}}
44+
45+
return FakeResponse()
46+
47+
monkeypatch.setattr("weather.httpx.get", fake_get)
48+
49+
temp = weather_service.get_temperature("Berlin")
50+
assert isinstance(temp, float)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from weather import WeatherService
2+
from unittest.mock import patch, MagicMock
3+
4+
# Section 4: Mocking with patch + MagicMock
5+
def test_get_temperature_with_mocking():
6+
"""
7+
Example of mocking httpx.get with a MagicMock.
8+
"""
9+
# Create a MagicMock response object
10+
mock_response = MagicMock()
11+
mock_response.raise_for_status.return_value = None
12+
mock_response.json.return_value = {"current": {"temp_c": 25}}
13+
14+
# Patch httpx.get so it returns our mock_response
15+
with patch("weather.httpx.get", return_value=mock_response) as mock_get:
16+
service = WeatherService(api_key="fake-key")
17+
temp = service.get_temperature("Paris")
18+
19+
assert temp == 25
20+
mock_get.assert_called_once()
21+
22+
23+
# Section 5: Monkey patching with monkeypatch fixture
24+
def test_get_temperature_with_monkeypatch(monkeypatch):
25+
"""
26+
Example of monkeypatching httpx.get with a manual stub.
27+
"""
28+
29+
def fake_get(url, params):
30+
class FakeResponse:
31+
def raise_for_status(self): pass
32+
def json(self): return {"current": {"temp_c": 19}}
33+
return FakeResponse()
34+
35+
# Monkeypatch httpx.get to use fake_get
36+
monkeypatch.setattr("weather.httpx.get", fake_get)
37+
38+
service = WeatherService(api_key="fake-key")
39+
temp = service.get_temperature("Berlin")
40+
41+
assert temp == 19
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from weather_refactor import WeatherService
2+
3+
def test_get_temperature_with_stub_client():
4+
class StubClient:
5+
def get(self, url, params):
6+
class Response:
7+
def raise_for_status(self): pass
8+
def json(self): return {"current": {"temp_c": 18}}
9+
return Response()
10+
11+
service = WeatherService(client=StubClient(), api_key="fake_key")
12+
assert service.get_temperature("Oslo") == 18

0 commit comments

Comments
 (0)