laranevans.com
Guides / Python and FastAPI for Spring Boot Engineers / 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.

Next: replace Spring Boot's conveniences.