Guidelines for Writing Tests¶
File Structure¶
Each test file tests all classes and functions in its corresponding source file. The structure mirrors src/rendercv/:
src/rendercv/renderer/templater/date.py
→ tests/renderer/templater/test_date.py
(tests all functions and classes in date.py)
src/rendercv/schema/models/cv/section.py
→ tests/schema/models/cv/test_section.py
(tests all functions and classes in section.py)
Naming Conventions¶
Test names must include the name of the function or class being tested.
When you need only one test, use test_ + the name:
- Testing
clean_url()→test_clean_url - Testing
Cv→test_cv
When you need multiple tests, wrap them in a class using Test + PascalCase name:
- Testing
clean_url()→TestCleanUrl - Testing
Cv→TestCv
Example with one test:
@pytest.mark.parametrize(
("url", "expected_clean_url"),
[
("https://example.com", "example.com"),
("https://example.com/", "example.com"),
("https://example.com/test", "example.com/test"),
],
)
def test_clean_url(url, expected_clean_url):
assert clean_url(url) == expected_clean_url
Example with multiple tests:
class TestComputeDateString:
@pytest.mark.parametrize(...)
def test_date_parameter_takes_precedence(self, ...):
...
@pytest.mark.parametrize(...)
def test_date_ranges(self, ...):
...
@pytest.mark.parametrize(...)
def test_returns_none_for_incomplete_data(self, ...):
...
Use Parametrize for Variations¶
Instead of writing multiple similar tests, use @pytest.mark.parametrize:
@pytest.mark.parametrize(
("input_a", "input_b", "expected"),
[
("2020-01-01", "2021-01-01", "Jan 2020 – Jan 2021"),
("2020-01", "2021-02-01", "Jan 2020 – Feb 2021"),
(2020, 2021, "2020 – 2021"),
],
)
def test_date_ranges(self, input_a, input_b, expected):
result = compute_date_string(None, input_a, input_b, EnglishLocale())
assert result == expected
Shared Fixtures with conftest.py¶
Place shared fixtures in conftest.py. Use the closest one possible:
- Fixtures for one folder → that folder's
conftest.py - Fixtures for multiple folders → their closest common parent's
conftest.py
tests/
├── conftest.py # Used across all tests
├── schema/
│ ├── conftest.py # Used by schema tests only
│ └── models/
│ └── cv/
│ ├── conftest.py # Used by CV model tests only
│ ├── test_section.py
│ └── test_cv.py
└── renderer/
└── ...
Testing Principles¶
Keep tests focused. Test functions in isolation: input → output.
Don't create unnecessary fixtures. If setup is one clear line, inline it:
# Don't:
@pytest.fixture
def locale(self):
return EnglishLocale()
def test_something(self, locale):
result = format_date(Date(2020, 1, 1), locale)
# Do:
def test_something(self):
result = format_date(Date(2020, 1, 1), EnglishLocale())
Prefer real behavior over mocking. Only mock when there's no practical alternative (external APIs, file system, etc.).
Name tests by expected behavior, not by input:
- Good:
test_returns_none_for_incomplete_data- describes what should happen - Bad:
test_function_with_none_input- describes input but not behavior
Keep tests simple:
def test_something(self, input, expected):
result = function_under_test(input)
assert result == expected
What to test:
- Input → expected output
- Input → expected error
What to avoid:
- Testing implementation details instead of behavior
- Complex test setup when simple values work