Test unitari per tubazioni di munging dei dati costituite da funzioni a linea singola


10

Leggendo l' Introduzione pratica alla programmazione funzionale di Mary Rose Cook , fornisce un esempio di anti-schema

def format_bands(bands):
    for band in bands:
        band['country'] = 'Canada'
        band['name'] = band['name'].replace('.', '')
        band['name'] = band['name'].title()

da

  • la funzione fa più di una cosa
  • il nome non è descrittivo
  • ha effetti collaterali

Come soluzione proposta, suggerisce di pipeline di funzioni anonime

pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                      call(lambda x: x.replace('.', ''), 'name'),
                      call(str.title, 'name')])

Tuttavia, questo mi sembra avere il rovescio della medaglia di essere ancora meno testabile; almeno i format_bands potrebbero avere un unit test per verificare se fa ciò che è destinato, ma come testare la pipeline? O l'idea che le funzioni anonime siano così esplicative da non dover essere testate?

La mia applicazione reale per questo è nel tentativo di rendere il mio pandascodice più funzionale. Avrò spesso una sorta di pipeline all'interno di una funzione "munging" "

def munge_data(df)
     df['name'] = df['name'].str.lower()
     df = df.drop_duplicates()
     return df

O riscrivere nello stile della pipeline:

def munge_data(df)
    munged = (df.assign(lambda x: x['name'].str.lower()
                .drop_duplicates())
    return munged

Qualche suggerimento per le migliori pratiche in questo tipo di situazione?


4
Quelle singole funzioni lambda sono troppo piccole per l'unità di test. Prova il risultato finale. Per dirla in altro modo, le funzioni anonime non sono testabili dall'unità, quindi non scrivere la funzione come funzione anonima se si prevede di testare l'unità singolarmente.
Robert Harvey,

Risposte:


1

Penso che ti sia sfuggita probabilmente la parte più importante dell'esempio corretto del libro. La modifica più fondamentale al codice è dal metodo che opera su tutti i valori in un elenco per operare su un elemento.

Esistono già funzioni come iter(in questo caso il nome pipeline_foreach) che eseguono una determinata operazione su tutti gli elementi in un elenco. Non è stato necessario duplicarlo con un forloop. Anche l'utilizzo di una nota operazione dell'elenco chiarisce il tuo intento. Con mapte stai trasformando i valori. Con iterte stai eseguendo un effetto collaterale con ogni elemento. Con forloop sei ... beh, non lo sai fino a quando non lo guardi.

Il codice corretto di esempio non è ancora molto funzionale, perché (per quanto posso dire) muta i valori nell'elenco senza restituirli, impedendo ulteriori tubazioni o composizione delle funzioni. Il metodo funzionalmente preferito mapcreerebbe un nuovo elenco di bande con il aggiornato countrye name. Quindi è possibile reindirizzare l'output alla funzione successiva o comporre mapcon un'altra funzione che ha preso un elenco di bande. Con iter, è come un vicolo cieco di pipeline.

Penso che il codice del risultato finale abbia piccole funzioni che sono troppo banali per disturbare i test qui. Dopotutto, non dovresti aver bisogno di scrivere unit test contro replaceo title. Ora forse vuoi comporli insieme nella tua funzione e unit test per ottenere la combinazione desiderata su un singolo oggetto. Io stesso, probabilmente avrei appena cambiato format_bandsin format_bandsingolare, lasciato cadere il for loop e chiamato pipeline_each(bands, format_band). Quindi potresti provare format_band per assicurarti di non aver dimenticato qualcosa.

Comunque, al tuo codice. Il tuo secondo esempio di codice sembra più pipeline-y. Ma questo da solo non offre i vantaggi della programmazione funzionale. In pratica, programmazione funzionale significa garantire la compatibilità delle funzioni con altre funzioni definendone la compatibilità solo in termini di input e output. Se ci sono effetti collaterali nascosti all'interno della funzione, quindi nonostante il suo ingresso / uscita si allinei con un'altra funzione, non puoi sapere se sono compatibili fino al runtime. Se tuttavia, due funzioni sono prive di effetti collaterali e corrispondono all'output-to-input, è possibile eseguire il pipeline o comporle senza preoccuparsi di risultati imprevisti.

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.