Test unitari a Django


12

Faccio davvero fatica a scrivere test unitari efficaci per un grande progetto Django. Ho una copertura dei test ragionevolmente buona, ma mi sono reso conto che i test che ho scritto sono sicuramente test di integrazione / accettazione, non test unitari e ho parti critiche della mia applicazione che non sono state testate in modo efficace. Voglio riparare questo al più presto.

Ecco il mio problema Il mio schema è profondamente relazionale e fortemente orientato al tempo, offrendo al mio oggetto modello un elevato accoppiamento interno e molto stato. Molti dei miei metodi di modello eseguono query in base a intervalli di tempo e ho molto da auto_now_addfare nei campi con data e ora. Quindi prendi un metodo simile a questo per esempio:

def summary(self, startTime=None, endTime=None):
    # ... logic to assign a proper start and end time 
    # if none was provided, probably using datetime.now()

    objects = self.related_model_set.manager_method.filter(...)

    return sum(object.key_method(startTime, endTime) for object in objects)

Come si può provare qualcosa del genere?

Ecco dove sono finora. Mi viene in mente che l'obiettivo del test unitario dovrebbe avere un comportamento beffardo by key_methodsui suoi argomenti, sta summaryfiltrando / aggregando correttamente per produrre un risultato corretto?

Deridere datetime.now () è abbastanza semplice, ma come posso prendere in giro il resto del comportamento?

  • Potrei usare gli infissi, ma ho sentito i pro e i contro dell'usare gli infissi per costruire i miei dati (la scarsa manutenibilità è una truffa che mi colpisce).
  • Potrei anche impostare i miei dati tramite ORM, ma ciò può essere limitante, perché quindi devo creare anche oggetti correlati. E l'ORM non ti consente di pasticciare auto_now_addmanualmente con i campi.
  • Deridere l'ORM è un'altra opzione, ma non solo è difficile deridere i metodi ORM profondamente annidati, ma la logica nel codice ORM viene derisa dal test e il derisione sembra rendere il test realmente dipendente dagli interni e dalle dipendenze del funzione sotto test.

I dadi più difficili da decifrare sembrano essere le funzioni come questa, che si trovano su alcuni strati di modelli e funzioni di livello inferiore e sono molto dipendenti dal tempo, anche se queste funzioni potrebbero non essere super complicate. Il mio problema generale è che, indipendentemente da come sembri tagliarlo, i miei test sembrano molto più complessi delle funzioni che stanno testando.


Dovresti scrivere i test unitari prima d'ora in poi, questo ti aiuta a individuare i problemi di testabilità nel tuo progetto prima che venga scritto il codice di produzione effettivo.
Chedy2149,

2
È utile, ma in realtà non affronta il problema di come testare al meglio le applicazioni intrinsecamente pesanti, con ORM pesanti.
Acjay,

Devi sottrarre lo strato di persistenza
Chedy2149,

1
Sembra grandioso ipoteticamente, ma quando si tratta di manutenzione del progetto, penso che ci sia un costo non banale per l'inserimento di uno strato di persistenza su misura tra la logica aziendale e l'ORM Django estremamente ben documentato. Improvvisamente, le classi si riempiono di un mucchio di minuscoli metodi intermedi che devono essere rifattorizzati nel tempo. Ma forse questo è giustificato nei luoghi in cui la testabilità è fondamentale.
acjay

Risposte:


6

Ho intenzione di andare avanti e registrare una risposta per quello che ho inventato finora.

La mia ipotesi è che per una funzione con accoppiamento e stato profondi, la realtà è che ci vorranno semplicemente molte linee per controllare il suo contesto esterno.

Ecco come appare approssimativamente il mio caso di test, basandomi sulla libreria Mock standard:

  1. Uso l'ORM standard per impostare la sequenza di eventi
  2. Creo il mio inizio datetimee sovvertisco i auto_now_addtempi per adattarli a una sequenza temporale fissa del mio design. Pensavo che l'ORM non lo consentisse, ma funziona benissimo.
  3. Mi assicuro che la funzione sotto test utilizzi in from datetime import datetimemodo da poter applicare datetime.now()solo quella funzione (se derido l'intera datetimeclasse, l'ORM si adatta perfettamente).
  4. Creo il mio sostituto per object.key_method(), con funzionalità semplice ma ben definita che dipende dagli argomenti. Voglio che dipenda dagli argomenti, perché altrimenti non potrei sapere se la logica della funzione sotto test funziona. Nel mio caso, restituisce semplicemente il numero di secondi tra startTimee endTime. Lo rattoppo avvolgendolo in un lambda e rattoppando direttamente object.key_method()usando il new_callablekwarg di patch.
  5. Infine, eseguo una serie di asserzioni su vari inviti summarycon diversi argomenti per verificare l'uguaglianza con i risultati calcolati a mano attesi, tenendo conto del comportamento dato della simulazionekey_method

Inutile dire che questo è significativamente più lungo e più complicato della funzione stessa. Dipende dal DB e non sembra proprio un test unitario. Ma è anche abbastanza disaccoppiato dagli interni della funzione - solo la sua firma e le sue dipendenze. Quindi penso che in realtà potrebbe essere un test unitario.

Nella mia app, la funzione è piuttosto fondamentale e soggetta a refactoring per ottimizzarne le prestazioni. Quindi penso che ne valga la pena, nonostante la complessità. Ma sono ancora aperto a idee migliori su come affrontare questo. Tutto parte del mio lungo viaggio verso uno stile di sviluppo più guidato dai test ...

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.