Testing con pytest
Proteggi il modulo fatturazione dello studio legale con test automatici: ogni bug intercettato è un errore in fattura evitato.
Testing con pytest
Il modulo di fatturazione dello studio legale dell'Avv. Marco Rossi gestisce soldi veri. Un bug nel calcolo dell'IVA, uno sconto applicato due volte, un arrotondamento sbagliato: ogni errore finisce direttamente in fattura e compromette la credibilità dello studio. I test automatici sono la rete di sicurezza che intercetta questi problemi prima che arrivino al cliente.
Il primo test
Partiamo dal cuore del sistema: la funzione che calcola il totale di una fattura con IVA e sconto.
# fatturazione.py
class FatturaInvalida(Exception):
"""Eccezione per fatture con dati non validi."""
pass
def calcola_totale_fattura(importo, aliquota_iva=22, sconto=0):
"""Calcola il totale fattura con IVA e sconto opzionale."""
if importo < 0:
raise FatturaInvalida("L'importo non può essere negativo")
if aliquota_iva < 0:
raise FatturaInvalida("L'aliquota IVA non può essere negativa")
if sconto < 0 or sconto > 100:
raise FatturaInvalida("Lo sconto deve essere tra 0 e 100")
importo_scontato = importo * (1 - sconto / 100)
totale = importo_scontato * (1 + aliquota_iva / 100)
return round(totale, 2)# test_fatturazione.py
from fatturazione import calcola_totale_fattura, FatturaInvalida
def test_totale_base_con_iva_standard():
"""1000 EUR + 22% IVA = 1220 EUR."""
assert calcola_totale_fattura(1000) == 1220.00
def test_totale_con_sconto():
"""1000 EUR - 10% sconto + 22% IVA = 1098 EUR."""
assert calcola_totale_fattura(1000, sconto=10) == 1098.00
def test_totale_senza_iva():
"""Regime forfettario: IVA a 0%."""
assert calcola_totale_fattura(1500, aliquota_iva=0) == 1500.00Esegui i test con pytest -v dalla riga di comando. Pytest trova automaticamente tutti i file e le funzioni che iniziano con test_.
Testare casi limite
I bug si nascondono ai confini: importo zero, sconto al massimo, valori negativi. Sono i casi che nessuno pensa di verificare manualmente.
def test_importo_zero():
"""Una fattura a importo zero deve restituire zero."""
assert calcola_totale_fattura(0) == 0.00
def test_sconto_totale():
"""Sconto del 100% azzera il totale."""
assert calcola_totale_fattura(5000, sconto=100) == 0.00
def test_importo_centesimi():
"""Verifica arrotondamento corretto su importi con decimali."""
risultato = calcola_totale_fattura(99.99, aliquota_iva=22)
assert risultato == 121.99 # 99.99 * 1.22 = 121.9878, arrotondatoTestare eccezioni
Le funzioni che gestiscono dati finanziari devono rifiutare input non validi in modo esplicito. Con pytest.raises verifichiamo che l'eccezione scatti quando deve.
import pytest
def test_importo_negativo_solleva_eccezione():
"""Un importo negativo deve generare FatturaInvalida."""
with pytest.raises(FatturaInvalida):
calcola_totale_fattura(-500)
def test_iva_negativa_solleva_eccezione():
"""Un'aliquota IVA negativa non ha senso fiscale."""
with pytest.raises(FatturaInvalida):
calcola_totale_fattura(1000, aliquota_iva=-5)
def test_sconto_oltre_cento_solleva_eccezione():
"""Sconto superiore al 100% è un errore di input."""
with pytest.raises(FatturaInvalida):
calcola_totale_fattura(1000, sconto=150)
def test_messaggio_eccezione():
"""Verifica il messaggio di errore specifico."""
with pytest.raises(FatturaInvalida, match="L'importo non può essere negativo"):
calcola_totale_fattura(-100)Fixture condivise
Le fixture sono dati di test riutilizzabili. Invece di ripetere la creazione di clienti e fatture in ogni test, li definiamo una volta sola.
@pytest.fixture
def cliente_esempio():
"""Cliente di test riutilizzabile."""
return {
"nome": "Avv. Marco Rossi",
"codice_fiscale": "RSSMRC80A01H501Z",
"partita_iva": "12345678901",
}
@pytest.fixture
def fattura_esempio(cliente_esempio):
"""Fattura di test con dati realistici."""
return {
"numero": "2024-001",
"cliente": cliente_esempio,
"voci": [
{"descrizione": "Consulenza legale", "importo": 500, "ore": 2},
{"descrizione": "Redazione contratto", "importo": 800, "ore": 4},
],
"aliquota_iva": 22,
}
def test_fattura_ha_cliente_valido(fattura_esempio):
"""La fattura deve avere un cliente con codice fiscale."""
assert "codice_fiscale" in fattura_esempio["cliente"]
assert len(fattura_esempio["cliente"]["codice_fiscale"]) == 16
def test_totale_voci_fattura(fattura_esempio):
"""Il totale delle voci deve corrispondere alla somma degli importi."""
totale_voci = sum(v["importo"] for v in fattura_esempio["voci"])
assert totale_voci == 1300Le fixture si passano come parametri ai test. Pytest le inietta automaticamente in base al nome.
Parametrize per varianti
Lo studio Rossi lavora con diverse aliquote IVA: 22% ordinaria, 10% ridotta, 4% minima, 0% per il regime forfettario. Testare ogni combinazione a mano è noioso. @pytest.mark.parametrize genera un test per ogni set di parametri.
@pytest.mark.parametrize("importo, aliquota, atteso", [
(1000, 22, 1220.00), # IVA ordinaria
(1000, 10, 1100.00), # IVA ridotta
(1000, 4, 1040.00), # IVA minima
(1000, 0, 1000.00), # Regime forfettario
(500, 22, 610.00), # Importo diverso
])
def test_calcolo_con_diverse_aliquote(importo, aliquota, atteso):
"""Verifica il calcolo con tutte le aliquote IVA italiane."""
assert calcola_totale_fattura(importo, aliquota_iva=aliquota) == attesoCon 5 righe di parametri, pytest esegue 5 test indipendenti. Se uno fallisce, sai esattamente quale aliquota causa il problema.
Organizzazione progetto test
Man mano che il progetto cresce, serve una struttura chiara per i file di test.
# Struttura consigliata per lo studio legale
#
# studio_legale/
# fatturazione.py
# clienti.py
# pratiche.py
# tests/
# __init__.py
# conftest.py # fixture condivise tra tutti i test
# test_fatturazione.py
# test_clienti.py
# test_pratiche.py
# conftest.py — fixture disponibili per TUTTI i test
import pytest
@pytest.fixture
def tariffa_oraria():
"""Tariffa oraria standard dello studio."""
return 150.00
@pytest.fixture
def lista_clienti():
"""Lista clienti di test."""
return [
{"nome": "Mario Bianchi", "codice_fiscale": "BNCMRA75D10F205X"},
{"nome": "Elena Verdi", "codice_fiscale": "VRDLNE82H41H501K"},
]Il file conftest.py è speciale: pytest lo carica automaticamente e rende le fixture disponibili a tutti i test nella stessa cartella e sottocartelle, senza bisogno di import.
Da ricordare
- ●Nomina i test in modo descrittivo:
test_cosa_fa_quando_condizione— il nome è la documentazione - ●Testa i casi limite (zero, massimo, negativo) perché i bug si nascondono lì
- ●Usa
pytest.raisesper verificare che le eccezioni scattino quando devono - ●Le fixture eliminano la duplicazione dei dati di test e si iniettano automaticamente
- ●
@pytest.mark.parametrizegenera test multipli da una tabella di valori — perfetto per varianti IVA - ●Metti le fixture condivise in
conftest.pyper renderle disponibili a tutto il progetto