Come progettare prompt dinamici con LLM per automatizzare la generazione di unit test in progetti Python di grandi dimensioni
Ti sei mai chiesto come far scrivere al tuo modello di linguaggio preferito test unitari completi per un codice Python complesso, senza dover scrivere manualmente ogni singolo caso di test?In questo articolo rispondiamo subito alla domanda, mostrando passo passo come progettareprompt dinamiciche sfruttano le capacità dei LLM (Large Language Model) per generare automaticamente unit test coerenti, mantenibili e pronti per l'integrazione in pipeline CI/CD.
Indice
- Analisi delle esigenze di test in ambienti Python complessi
- Tecniche per creare prompt contestuali
- Strategie few-shot e chain-of-thought prompting
- Integrazione pratica del prompt in CI/CD
- Conclusioni e takeaways
Analisi delle esigenze specifiche di test unitari in ambienti Python complessi
Prima di scrivere il primo prompt, è fondamentale comprendere il contesto in cui il codice vive. Nei progetti Python di grandi dimensioni, le principali sfide sono:
- ModularitÃ: migliaia di moduli, pacchetti e sottopacchetti.
- Dipendenze esterne: librerie di terze parti, API, database.
- Convenzioni di naming: pattern di naming per classi, funzioni e fixture di test.
- Copertura: obiettivi di coverage (es. > 90%) e policy di code review.
- Performance: test che non devono rallentare la pipeline CI.
Questi aspetti guidano la struttura del prompt: più informazioni contestuali fornisci, più il LLM potrà produrre test corretti al primo giro.
Raccolta delle informazioni chiave
- Path del moduloda testare (es.
src/services/user_service.py). - Docstringe commenti della funzione.
- Tipi di input/output(type hints).
- Dipendenze(es.
requests,sqlalchemy). - Convenzioni di namingdel progetto (es.
test_*.py,*_test.py).
Tecniche per creare prompt contestuali che includano codice esistente, dipendenze e convenzioni di naming
Un prompt dinamico è un testo che si costruisce programmaticamente al volo, inserendo le parti variabili del progetto. Ecco una struttura base:
/**
* Prompt per generare unit test Python con[LLM_NAME]* Modulo: {{module_path}}
* Dipendenze: {{dependencies}}
* Convenzioni: {{naming_convention}}
*/
Scrivi test unitari per la funzione {{function_name}} definita in {{module_path}}.
- Usapytesteunittest.mockper le dipendenze.
- Rispetta la convenzione di naming "{{naming_convention}}".
- Includi almeno 3 casi di test: caso normale, caso limite, caso di errore.
- Fornisci docstring per ogni test.
- Restituisci solo il codice del file di test, senza spiegazioni aggiuntive.
Codice della funzione:
```python
{{function_code}}
```Esempio pratico
Supponiamo di avere la funzionecreate_userinsrc/services/user_service.py:
def create_user(db: Session, username: str, email: str) -> User:
"""Crea un nuovo utente nel database.
Args:
db: Sessione SQLAlchemy attiva.
username: Nome utente unico.
email: Indirizzo email valido.
Returns:
L'oggetto User appena creato.
"""
if db.query(User).filter_by(username=username).first():
raise ValueError("Username already exists")
user = User(username=username, email=email)
db.add(user)
db.commit()
return userIl prompt dinamico generato in Python potrebbe essere:
import json, pathlib
module_path = "src/services/user_service.py"
function_name = "create_user"
function_code = pathlib.Path(module_path).read_text().split("def create_user")[1].split("\n\n")[0]
prompt = f"""
Scrivi test unitari per la funzione {function_name} definita in {module_path}.
- Usa pytest e unittest.mock.
- Rispetta la convenzione di naming "test_*.py".
- Includi casi: creazione valida, username duplicato, email non valido.
- Restituisci solo il codice del file di test.
Codice della funzione:
```python
{function_code}
```
"""
print(prompt)Il risultato è un prompt ricco di contesto che il LLM può trasformare direttamente in un filetest_user_service.pypronto per il commit.
Strategie per iterare e affinare i prompt usando few-shot e chain-of-thought prompting
Un singolo prompt può non bastare. Le tecniche più efficaci sono:
- Few-shot prompting: fornisci esempi di input-output (funzione + test) prima della richiesta corrente.
- Chain-of-thought (CoT): chiedi al modello di ragionare passo-passo prima di generare il codice.
Few-shot example
### Esempio 1
Funzione:
```python
def add(a: int, b: int) -> int:
return a + b
```
Test generato:
```python
import pytest
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
```
### Esempio 2
Funzione:
```python
def divide(a: float, b: float) -> float:
if b == 0:
raise ZeroDivisionError
return a / b
```
Test generato:
```python
import pytest
def test_divide():
assert divide(4, 2) == 2.0
with pytest.raises(ZeroDivisionError):
divide(1, 0)
```
### Nuova richiesta
Funzione:
```python
{{function_code}}
```
Genera test unitari rispettando lo stesso stile.Chain-of-thought prompting
Chiedi al modello di elencare i passaggi prima di scrivere il codice:
1. Identifica gli input della funzione e i loro tipi.
2. Individua le condizioni di errore.
3. Decidi i casi di test (normale, limite, errore).
4. Scrivi il codice pytest con fixture per le dipendenze.
5. Aggiungi docstring ai test.
Ora genera il test seguendo questi passaggi.Questa strategia riduce gli errori di omissione e migliora la coerenza con le politiche interne.
Integrazione pratica del prompt in CI/CD con GitHub Actions o Azure Pipelines
Una volta che il prompt è pronto, lo si può inserire in una pipeline automatica. Di seguito due esempi.
GitHub Actions
name: Generate Unit Tests
on:
push:
paths:
- 'src/**/*.py'
jobs:
generate-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install openai pytest
- name: Generate prompts
id: prompt
run: |
python scripts/generate_prompt.py > prompt.txt
- name: Call LLM
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
RESPONSE=$(curl -s -X POST https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "$(cat prompt.txt)"}]}' )
echo "$RESPONSE" | jq -r '.choices[0].message.content' > tests/generated_test.py
- name: Run generated tests
run: |
pytest tests/generated_test.pyAzure Pipelines
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.11'
- script: |
pip install openai pytest
displayName: 'Install dependencies'
- script: |
python scripts/generate_prompt.py > prompt.txt
displayName: 'Generate prompt'
- script: |
RESPONSE=$(curl -s -X POST https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $(OPENAI_API_KEY)" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "$(cat prompt.txt)"}]}' )
echo "$RESPONSE" | jq -r '.choices[0].message.content' > tests/generated_test.py
env:
OPENAI_API_KEY: $(openaiApiKey)
displayName: 'Call LLM'
- script: |
pytest tests/generated_test.py
displayName: 'Run generated tests'Con questi workflow, ogni volta che il codice sorgente cambia, la pipeline genera e verifica automaticamente nuovi unit test, garantendo che la coverage rimanga sempre alta.
Conclusioni e actionable takeaways
Progettare prompt dinamici per la generazione automatica di unit test in Python non è più un'idea futuristica: è una pratica realizzabile oggi con pochi passaggi chiave.
- Analizza il contesto: raccogli path, dipendenze, naming convention.
- Costruisci prompt contestualiusando template che includono il codice reale.
- Usa few-shot e CoTper guidare il modello verso risultati più affidabili.
- Integra il flusso in CI/CDcon GitHub Actions o Azure Pipelines per una verifica continua.
Seguendo questi consigli, potrai ridurre drasticamente il tempo speso a scrivere test manuali, aumentare la copertura e mantenere alta la qualità del codice nei progetti Python di grandi dimensioni.
Actionable checklist
- Definisci le convenzioni di naming dei test nel tuo repository.
- Crea uno script
generate_prompt.pyche estragga codice e dipendenze. - Aggiungi esempi few-shot al prompt per allineare lo stile.
- Configura una pipeline CI che invochi l'LLM e esegua
pytestsui file generati. - Monitora la coverage e aggiusta il prompt in caso di falsi negativi.
Con queste azioni, avrai una soluzione scalabile e pronta per il futuro, capace di far parlare i LLM al servizio della tua qualità del software.
Domande Frequenti
Qual è il vantaggio principale di usare i LLM per generare unit test?
I LLM riducono il tempo di scrittura dei test, aumentano la copertura e garantiscono coerenza con le convenzioni del progetto, soprattutto in codebase molto grandi.
Come posso garantire che i test generati siano affidabili?
Utilizza few-shot prompting con esempi di alta qualità , aggiungi chain-of-thought per far ragionare il modello e verifica sempre i test in una pipeline CI prima di accettarli.