Structure and Test Your Code
Python organizes code into modules and packages with explicit imports and no one-class-per-file rule. Tests run through pytest, which replaces JUnit with plain functions and the assert statement.
Packages and imports
A Java package (com.example.users) maps onto a Python package: a folder with code. A Maven module maps onto a package too. One difference appears immediately: a Python module (a .py file) holds as many classes and functions as the unit of work needs. You do not split one class per file.
# src/my_service/users/service.py
from my_service.users.repository import UserRepository
from my_service.config import settings
| Java | Python |
|---|---|
package com.example.users |
package my_service/users/ |
import com.example.users.User |
from my_service.users import User |
public / package-private |
every top-level name is importable |
| Maven module | Python package |
static import |
from module import name |
Prefer absolute imports from the package root. Relative imports (from ..config import settings) read worse and break when you move a file.
Console entry points
Spring Boot's public static void main plus SpringApplication.run boots the app. For a command-line tool, declare an entry point in pyproject.toml and uv installs a runnable command:
[project.scripts]
my-service = "my_service.cli:main"
For the web service itself, the entry point is the ASGI server. Page 6 covers running FastAPI.
Tests with pytest
JUnit organizes tests into classes with annotated methods. pytest uses plain functions and the built-in assert. No test class, no assertEquals, no static imports of matchers.
# tests/test_service.py
from my_service.users.service import normalize_email
def test_normalize_lowercases_the_domain():
assert normalize_email("User@Example.COM") == "user@example.com"
uv run pytest
| JUnit 5 | pytest |
|---|---|
@Test |
a test_* function |
assertEquals(a, b) |
assert a == b |
assertThrows(E, ...) |
with pytest.raises(E): ... |
@BeforeEach |
a fixture |
@ParameterizedTest |
@pytest.mark.parametrize |
| Mockito mock | unittest.mock or a fixture |
Fixtures replace @BeforeEach
A fixture provides a value or resource to a test. Declare it once, then request it by adding its name as a parameter. Each test pulls in only the fixtures it names:
import pytest
@pytest.fixture
def repository():
return InMemoryUserRepository()
def test_saves_a_user(repository):
repository.save(User(id=1, email="a@example.com"))
assert repository.count() == 1
pytest builds the requested fixtures fresh for each test and tears them down afterward, the way @BeforeEach and @AfterEach bracket a JUnit test.