Fixture di Pytest

Completato

Le fixture sono funzioni helper pytest usate per creare test modulari, scalabili e gestibili. È possibile usare le fixture per configurare le precondizioni per i test, ad esempio le connessioni di database, la creazione di dati di test o la configurazione di uno stato del sistema necessario prima che un test possa essere eseguito. Possono anche essere usati per la pulizia dopo l'esecuzione dei test.

Le caratteristiche principali delle fixture pytest includono:

  • Controllo ambito: le fixture possono essere configurate per avere ambiti diversi usando il scope parametro , ad esempio function, classmodule, o session, che determina la frequenza con cui viene chiamata la fixture.
  • Gestione della configurazione e dell'disinstallazione: Pytest gestisce il ciclo di vita delle fixture, configurando e rimuovendo automaticamente in base alle esigenze.
  • Inserimento delle dipendenze: le fixture vengono inserite nelle funzioni di test come argomenti, rendendo chiaro quali test dipendono da quali fixture.
  • Riutilizzabilità e modularità: le fixture possono essere definite in un'unica posizione e usate in più funzioni di test, moduli o persino progetti.

Creazione di una fixture di file temporanea

Quando si scrivono test che interagiscono con i file, è normale che siano necessari file temporanei che non comprofondono il file system dopo il test. Con pytest è possibile creare una fixture che configura un file temporaneo. La fixture usa il modulo di tempfile Python per generare file temporanei in modo sicuro, assicurandosi che possano essere usati ed eliminati senza influire sull'ambiente locale. (In questa versione iniziale della nostra fixture, il file non verrà eliminato automaticamente a causa del flag delete=False. Affronteremo l'eliminazione dei file in seguito.)

Ecco l'aspetto del dispositivo:

import pytest
import tempfile

@pytest.fixture
def tmp_file():
    def create():
        # Create a named temporary file that persists beyond the function scope
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

In questa configurazione, tmp_file() funge da fixture. Il nome della fixture è il modo in cui i test vi fanno riferimento. All'interno della fixture, la funzione create() nidificata crea il file solo quando viene chiamato, anziché durante la configurazione della fixture. In questo modo è possibile controllare con precisione quando viene creato il file temporaneo, utile nei test in cui gli intervalli e lo stato del file sono critici.

Nella funzione annidata create() viene creato un file temporaneo, quindi ne viene restituito il percorso assoluto. Ecco un esempio di come un test potrebbe usare la fixture scritta:

import os

def test_file(tmp_file):
    path = tmp_file()
    assert os.path.exists(path)

Un test usa una fixture specificando il nome della fixture come argomento. Il nostro semplice caso d'uso può essere facilmente espanso scrivendo nel file o apportando modifiche come la modifica delle autorizzazioni o la proprietà.

Gestione dell'ambito

In pytest la gestione del ciclo di vita delle risorse di test tramite routine di installazione e di disinstallazione è fondamentale per mantenere ambienti di test puliti ed efficienti. Si vuole anche proteggere l'integrità dei test assicurandosi che ogni test inizi con uno stato noto e coerente. Per impostazione predefinita, le fixture pytest operano con ambito di function, il che influisce sul comportamento in due modi:

  • Ciclo di vita per test: il valore restituito della fixture viene ricalcolato per ogni funzione di test che lo usa, a garanzia che ogni test funzioni con uno stato aggiornato.
  • Pulizia dopo ogni utilizzo: tutte le operazioni di pulizia necessarie vengono eseguite dopo ogni test che utilizza la fixture.

Pytest consente anche di definire l'ambito delle fixture in modo più ampio per ottimizzare le prestazioni e l'utilizzo delle risorse. La definizione dell'ambito è particolarmente utile in situazioni come la gestione dello stato del database o quando sono presenti configurazioni di stato complesse che richiedono molto tempo per stabilire. I quattro ambiti disponibili sono:

  • function: ambito predefinito, la fixture viene eseguita una volta per ogni test
  • class: la fixture viene eseguita una volta per ogni classe di test.
  • module: Viene eseguito una volta per modulo.
  • session: viene eseguito una sola volta per sessione di test. Questo ambito è utile per operazioni costose che devono essere mantenute nell'intera sessione di test, ad esempio l'inizializzazione di un servizio o l'avvio di un server di database.

In questo caso, l'esecuzione una sola volta indica che il valore restituito viene memorizzato nella cache. Una fixture con ambito "module" può quindi essere chiamata più volte in un modulo di test, ma il valore restituito è quello del primo test che lo ha chiamato.

Ecco l'aspetto della fixture tmp_file() con un ambito di modulo:

import pytest
import tempfile

@pytest.fixture(scope="module")
def tmp_file():
    def create():
        temp = tempfile.NamedTemporaryFile(delete=False)
        return temp.name
    return create

Gestione della pulizia

Il codice precedente che specifica la tmp_file fixture crea un file temporaneo, ma non gestisce automaticamente la pulizia dopo il completamento dei test. Per assicurarsi che i file temporanei non siano rimasti indietro, è possibile usare la fixture di request pytest per registrare una funzione di pulizia.

Ecco come modificare la fixture tmp_file per includere la pulizia automatica:

import pytest
import tempfile
import os

@pytest.fixture(scope="module")
def tmp_file(request):
    # Create a temporary file that persists beyond the function scope
    temp = tempfile.NamedTemporaryFile(delete=False)

    def create():
        # Returns the path of the temporary file
        return temp.name

    def cleanup():
        # Remove the file after the tests are done
        os.remove(temp.name)

    # Register the cleanup function to be called after the last test in the module
    request.addfinalizer(cleanup)

    return create

Se si usa request.addfinalizer() e si passa alla funzione annidata cleanup(), la pulizia viene chiamata in base all'ambito. In questo caso, l'ambito è module e di conseguenza, dopo tutti i test in un modulo, Pytest chiama la funzione di pulizia.

Uso di conftest.py

Invece di includere le fixture nei file di test, è possibile salvarle in un file di conftest.py . Tutte le fixture in conftest.py sono automaticamente disponibili per i test nella stessa directory senza doverli importare in modo esplicito.

Esplorazione delle fixture predefinite

Pytest include molte fixture predefinite progettate per semplificare i test. Queste fixture possono gestire automaticamente la configurazione e la pulizia, consentendo di concentrarsi sulla scrittura dei test case anziché sulla gestione dei test.

Gli elementi principali integrati includono:

  • cache: usato per creare e gestire la cache a livello di test, utile per l'archiviazione dei dati tra sessioni di test.
  • capsys: acquisisce e consente l'ispezione di stderr e stdout, semplificando l'ispezione e l'esecuzione di test degli output della console.
  • tmpdir: fornisce una directory temporanea per i file che devono essere creati e usati durante i test.
  • monkeypatch: consente di modificare in modo sicuro il comportamento e i valori di oggetti, funzioni e l'ambiente del sistema operativo.

Il ruolo di monkeypatching nei test

Il test del codice strettamente integrato con risorse esterne, ad esempio database o API esterne, può risultare complesso a causa delle dipendenze coinvolte. Una tecnica chiamata monkey patching comporta la temporanea modifica del sistema durante le esecuzioni di test, per ottenere l'indipendenza dai sistemi esterni e per alterare in sicurezza lo stato e il comportamento dell'ambiente del sistema operativo durante i test.

Ecco un esempio di come sovrascrivere la funzione os.path.exists() usando il fixture monkeypatch:

import os

def test_os(monkeypatch):
    # Override os.path.exists to always return False
    monkeypatch.setattr('os.path.exists', lambda x: False)
    assert not os.path.exists('/')

In alternativa, è possibile usare il setattr() metodo con riferimento diretto all'oggetto e all'attributo:

def test_os(monkeypatch):
    # Specify the object and attribute to override
    monkeypatch.setattr(os.path, 'exists', lambda x: False)
    assert not os.path.exists('/')

Oltre a impostare attributi e metodi di override, la monkeypatch fixture può impostare ed eliminare variabili di ambiente, modificare i valori del dizionario e modificare i percorsi di sistema. La monkeypatch fixture ripristina automaticamente le modifiche dopo ogni test, ma è comunque necessario prestare attenzione quando si usa la monkeypatch fixture. Ecco alcuni motivi per prestare attenzione quando viene usato:

-Chiarezza e manutenzione del codice: l'utilizzo monkeypatch eccessivo o l'uso in modi complessi può rendere il test più difficile da comprendere e gestire. Quando si leggono i risultati del test, potrebbe non essere immediatamente chiaro come si comportano normalmente i componenti rispetto a come vengono modificati per i test.-Validità dei test: Il monkeypatching può talvolta portare a test che passano in condizioni artificiali molto diverse dall'ambiente di produzione. Ciò può creare un falso senso di sicurezza, poiché i test potrebbero essere superati a causa di una modifica troppo drastica del comportamento del sistema.-Dipendenza eccessiva dai dettagli di implementazione: i test che si basano sul monkeypatching potrebbero essere strettamente associati a dettagli di implementazione specifici del codice che vengono testati. Ciò può rendere i test fragili e soggetti a interruzioni con modifiche anche minime alla codebase sottostante.-Complessità del debug: il debug di test che usano monkeypatch può essere più complesso, soprattutto se la patch modifica aspetti fondamentali del comportamento delle applicazioni. Comprendere il motivo per cui un test non riesce potrebbe richiedere un approfondimento sul modo in cui i componenti vengono modificati durante il test.

Sebbene monkeypatch sia uno strumento potente per la creazione di ambienti di test isolati e controllati, deve essere usato in modo appropriato e con una chiara comprensione del modo in cui influisce sul gruppo di test e sul comportamento dell'applicazione.