Come progettare prompt dinamici con LLM per generare unit test Python in grandi progetti?

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 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

  1. Path del moduloda testare (es.src/services/user_service.py).
  2. Docstringe commenti della funzione.
  3. Tipi di input/output(type hints).
  4. Dipendenze(es.requests,sqlalchemy).
  5. 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 user

Il 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.py

Azure 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

  1. Definisci le convenzioni di naming dei test nel tuo repository.
  2. Crea uno scriptgenerate_prompt.pyche estragga codice e dipendenze.
  3. Aggiungi esempi few-shot al prompt per allineare lo stile.
  4. Configura una pipeline CI che invochi l'LLM e eseguapytestsui file generati.
  5. 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.

💼 Vuoi ottimizzare i tuoi processi con l'AI?

Scopri come possiamo aiutarti a creare prompt personalizzati e strategie AI su misura per il tuo business.

Richiedi Consulenza Gratuita