Topic: Unit test di metodi "privati"  (Letto 1166 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.642
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #15 il: Luglio 19, 2017, 14:11 »
Non mi sono dimenticato di rispondere eh ;)

  • Il concetto di "funzione privata" chiaramente dipende dal linguaggio di riferimento, quello che intendevo io è che non esistono funzioni private a livello globale, devono per forza essere locali a qualcosa. Tutto qui.
  • Per il discorso del codice già esistente, so benissimo che dai 3 punti che riporti non si scappa :)
  • Diciamo allora che *io* farei _parse_line/LineParser pubblica (o package local, o assembly local, o qualsiasi cosa sia nel linguaggio di riferimento la minima visiblità possibile che garantisce che un test possa invocarla senza schifezze di reflection) per poterla testare. A prescindere che sia una funzione o una classe.

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.104
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:Unit test di metodi "privati"
« Risposta #16 il: Luglio 27, 2017, 11:25 »
Non ho letto con attenzione tutti i precedenti post, pero' mi pare che nessuno abbia menzionato l'uso di mocks per verificare il comportamento - che potrebbe nel caso di python essere tradotto come "testare i metodi privati".

Nel caso specifico:

class ...
    def parse_logfile(self, fh):
        return [self._parse_line(line) for line in fh]

    def _parse_line(self, line):
        if self._is_type1_logline(line):
             return self._parse_type1(line)
        elif ....


self._is_type_1_logline(line) -> questo dovra' pur avere una chiamata sull'oggetto giusto?

assert(iscalled, line.is_type_1)
assert return_value == expected_result


Voglio dire, alla fine si tratta di definire le "expectations"  e verificare che il software testato le soddisfi. In Python potrai anche testare i metodi privati, ma francamente preferisco usare mocks/spy, cosi' da non focalizzarmi sul nome del metodo, su come il metodo e' spezzettato, etc., ma sull'algoritmo che potrebbe si' cambiare, ma il che e' meno probabile rispetto a un normale refactoring, etc.

In ogni caso, verificare che lo stato soddisfi le expectations non e' necessariamente una cosa sbagliata - dipende anche dal contesto.

(spoiler: per me non ha senso avere 100% coverage -> sempre interessante http://rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf )

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.642
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #17 il: Luglio 27, 2017, 11:49 »
Voglio dire, alla fine si tratta di definire le "expectations"  e verificare che il software testato le soddisfi. In Python potrai anche testare i metodi privati, ma francamente preferisco usare mocks/spy, cosi' da non focalizzarmi sul nome del metodo, su come il metodo e' spezzettato, etc., ma sull'algoritmo che potrebbe si' cambiare, ma il che e' meno probabile rispetto a un normale refactoring, etc.

You got exactly my point. E' esattamente quello che intendo io con "i metodi privati sono dettagli implementativi".

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #18 il: Agosto 02, 2017, 10:17 »
  • Il concetto di "funzione privata" chiaramente dipende dal linguaggio di riferimento, quello che intendevo io è che non esistono funzioni private a livello globale, devono per forza essere locali a qualcosa. Tutto qui.

Se esiste il concetto di metodo non pubblico, quello che puoi applicare come "si deve non si deve" fare e' di fatto estendibile a funzioni non pubbliche.


Citazione
  • Diciamo allora che *io* farei _parse_line/LineParser pubblica (o package local, o assembly local, o qualsiasi cosa sia nel linguaggio di riferimento la minima visiblità possibile che garantisce che un test possa invocarla senza schifezze di reflection) per poterla testare. A prescindere che sia una funzione o una classe.

Si, ok. Escludiamo le schifezze, sono d'accordo. Sono solo dettagli implementativi. Assumiamo che anche le cose non pubbliche siano in qualche modo testabili meccanicamente e parliamo solo sull'opportunita' di farlo.

Torniamo al punto: io ti sto dicendo che molto probabilmente LineParser sarebbe (dal mio punto di vista) un dettaglio implementativo e quindi non vorrei metterla pubblica. Anche perche', tanto per dire, con determinati design potresti avere LineParser come interfaccia (o analogo) e poi implementazioni non pubbliche che in qualche modo carichi nel tuo affare che parsa il file (se vuoi, e' potenzialmente uno strategy pattern. Non sto dicendo che *deve* essere. Ma nel caso in cui hai righe di log con differenze di formato sostanziali potrebbe essere un discorso sensato.

E sarebbe altrettanto sensato testarle individualmente *e* testare la logica che carica quella giusta. Questo stile di testing potenzialmente ti porta ad avere una coverage effettiva (non parlo di branch o line coverage, piu' in senso lato) piu' alta.

In generale, non e' infrequente avere codice dove hai il metodo principale che prende come input on oggetto complicato da costruire. Complicato puo' essere per tanti motivi. Ma insomma, immaginati qualcosa che aggrega insieme vari elementi che ti servono per fare i tuoi conti. Nota... non farlo non e' una buona idea: puoi facilmente garantire come invariante che una volta che quell'oggetto complicato e' costruito tutti i suoi componenti sono in uno stato consistente fra loro (usare il type-system per fare enforcing di vincoli).

Questo metodo principale al suo interno chiama dei metodi i quali tipicamente prendono come parametri un subset dei parametri dell'oggetto iniziale. Questi metodi sono ovviamente piu' semplici da testare individualmente per alcuni motivi:
- prendono oggetti "semplici", quindi diventa piu' semplice scrivere i test. eventualmente test parametrici
- a questo punto potresti anche volere testare aree di codice che di per se non sono raggiungibili dal metodo principale [so che pensi che sia una cattiva idea, ma ora ti mostro le alternative]
- siccome il metodo fa molto meno, e' piu' facile testare tutte le post-condizioni

Quindi a noi... perche' ti dico che preferisco testare anche le aree di codice?

Ora, supponiamo che il nostro oggetto principale abbia due attributi booleani. La nostra logica di costruzione dell'oggetto ci dice che solo uno dei due deve essere vero (ovvero potremmo  avere assert a xor b come precondizione del costruttore).

E boh... immaginiamo che sto sotto metodo prenda sti due attributi.

[codice]
def _f(a, b):
  if a and not b:
      do_something()
  elif b and not a:
      do_something_else()
[/codice]

Ora io *so* che siccome questo e' privato e me lo chiama solo il metodo di fuori questo metodo e' "safe". Copre tutti i casi. Pero'... se per qualche motivo ci fosse un baco nella costruzione dell'oggetto principale per cui mi trovassi a == b, quello che succede e' che _f non farebbe *niente*, che potrebbe voler dire che silenziosamente passiamo sopra una condizione di errore.

Tipicamente voglio che *se* succede qualcosa del genere mi trovo con un messaggio di log (o un'eccezione o qualcunuqe cosa). Ma *voglio* sapere che qualcuno ha chiamato f_ in produzione con dei parametri sbagliati. Cosa che non sarebbe possibile: ma che se succede indica che ho un baco (in una parte completamente scollegata del codice) e lo voglio sapere.

E tu dirai... ma se lo controllo gia' in costruzione? Ridondanza. Voglio *sapere* se qualcosa non sta funzionando. E *sicuramente* non voglio corrompere dati.

Ci sono casi in cui _f non sta meglio scritta come ho fatto la sopra? Certo che si. Ma questo non accade sempre, e quando accade il contrario (i.e., la computazione successiva si aspetta che do_something or do_something_else siano state chiamate) voglio *davvero* che l'errore di stato non sia ignorato. E quindi "devo" fare sta roba.

Certo, potrei anche (in determinati casi e linguaggi) fare trick per passare l'oggetto principale costruito anche con gli attributi "impossibili". Che so... un mock. Bene... scriptiamo *tutto* il mock per avere un oggetto che si comporta esattamente come il nostro oggetto complicato salvo il fatto che ha a e b uguali? Questo e' *costoso*. E
 un oggetto non banale, abbiamo detto. E scrivere "tanto" mock... si fanno errori, ed e' fragile. Scriptiamo il mock solo con quello che ci serva? Ecco che abbiamo introdotto implicitamente fragilita' nel test: vuole dire che essenzialmente il test per funzionare ha bisogno che _f venga chiamata abbastanza presto da fallire prima che cominciamo ad accedere alla parte non scriptata del mock.

I mock a volte sono proprio comodi, ma siate critici ogni volta che ne vedete uno. C'era proprio bisogno?

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #19 il: Agosto 02, 2017, 10:24 »
Voglio dire, alla fine si tratta di definire le "expectations"  e verificare che il software testato le soddisfi. In Python potrai anche testare i metodi privati, ma francamente preferisco usare mocks/spy, cosi' da non focalizzarmi sul nome del metodo, su come il metodo e' spezzettato, etc., ma sull'algoritmo che potrebbe si' cambiare, ma il che e' meno probabile rispetto a un normale refactoring, etc.

No, mi dispiace. I mock sono notori proprio perche' rispetto ad altri tipi di test double sono estremamente fragili e in generale portano naturalmente a testare le cose sbagliate per inesperienza *e* ti spingono decisamente verso whitebox testing spintissimo. Il codice in cui lo stile predominante di test usa i mock prende un mucchio di tempo in piu' per essere manipolato.

Tra l'altro, un abuso di mock tipicamente succede o quando si ha una popolazione di sviluppatori pigri che hanno imparato un test double e vogliono farci tutto per la pigrizia di non imparare quando non e' il caso, oppure, peggio ancora, quando si hanno forti problemi sul design del progetto e solo i mock riescono davvero a consentire di essere testato in modo facile.

Nota, uso regolarmente mocks. Non ne abuso.
In ogni caso, verificare che lo stato soddisfi le expectations non e' necessariamente una cosa sbagliata - dipende anche dal contesto.

> (spoiler: per me non ha senso avere 100% coverage -> sempre interessante http://rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf )

Troppo lungo e troppo verboso.  Bravo scrittore eh... pero' purtroppo non posso fare forward dei page che prendo io. Fino a quel momento, credo che ignorero' bellamente le sue opinioni e continuero' a fare andare avanti la mia roba con i requisiti di availability che ho. ;)

E no, non e' che 100% coverage sia un valore in assoluto. Semplicemente piu' ti ci avvicini (a patto di non farlo in modo stupido) e meno incognite hai nelle emergenze. Soprattutto, sai *dove* non cercare il problema. E hai un po' piu' di tranquillita' quando fai review dei cambiamenti.
« Ultima modifica: Agosto 02, 2017, 10:29 da riko »

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.104
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:Unit test di metodi "privati"
« Risposta #20 il: Agosto 07, 2017, 14:16 »
> No, mi dispiace. I mock sono notori proprio perche' rispetto ad altri tipi di test double sono estremamente fragili e in generale portano naturalmente a testare le cose sbagliate per inesperienza *e* ti spingono decisamente verso whitebox testing spintissimo. Il codice in cui lo stile predominante di test usa i mock prende un mucchio di tempo in piu' per essere manipolato.

Purtroppo e' vero ... Pero' qui stiamo dando per scontato che siamo tutti adulti e sappiamo cosa stiamo facendo .  :P

Sono d'accordo comunque che a volte l'utilizzo smisurato di mocks etc. e' un possibile segnale di bisogno di refactoring (design), pero' e' anche vero che in certi casi non vedo tu come possa farne a meno. Ok, fake objects, dummies, etc. tramite l'utilizzo giusto di interfacce, ecc., insomma sono d'accordo che a volte si esagera... Ci sono decine di articoli su "how not to use mocks" (esempio banale in Scala, per non citare il "solito" Java: http://engineering.monsanto.com/2015/07/28/avoiding-mocks/ ) - ma e' banale, alla fine basta ristrutturare il codice.

A volte pero' la scelta non e' cosi' banale, soprattutto quando hai un test piu' "interessante" di un altro dove vuoi verificare il comportamento (piu' white che black).

Lascio con un interessante articolo che avrai senza dubbio letto: https://martinfowler.com/articles/mocksArentStubs.html 
> Of these kinds of doubles, only mocks insist upon behavior verification.

il che a mio parere e' grosso modo simile a "bisogna testare i metodi privati?". In Python tutto cio' e' banale, ma alla fine testare i metodi privati e' davvero al limite con i mocks - perche' stai verificando il comportamento, non piu' lo stato.

Il fatto e' anche che tools in Java come mockito hanno in realta' rovinato la terminologia e confuso ancor di piu' gli sviluppatori. "Uso un mock!" -> in realta' si sta usando uno stub, ma mockito te lo vende come mock. Insomma, il numero di volte che capita di *dover* verificare il comportamento di un certo metodo rispetto al numero di volte che non te ne frega ha spesso un rapporto di 1:3, o forse inferiore.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #21 il: Agosto 09, 2017, 14:49 »
Sono d'accordo comunque che a volte l'utilizzo smisurato di mocks etc. e' un possibile segnale di bisogno di refactoring (design), pero' e' anche vero che in certi casi non vedo tu come possa farne a meno. Ok, fake objects, dummies, etc. tramite l'utilizzo giusto di interfacce, ecc., insomma sono d'accordo che a volte si esagera... Ci sono decine di articoli su "how not to use mocks" (esempio banale in Scala, per non citare il "solito" Java: http://engineering.monsanto.com/2015/07/28/avoiding-mocks/ ) - ma e' banale, alla fine basta ristrutturare il codice.

1. io non ho detto che "non bisogna usarli". quello che io ho detto e':
    a. rispetto ad altri tipi di test double sono estremamente fragili (vero)
    b. in mano a gente inesperta spostano il focus sulle cose sbagliate (e' molto semplice andare a "testare la specifica implementazione")
    c. Il codice in cui lo stile predominante di test usa i mock prende un mucchio di tempo in piu' per essere manipolato. (pure vero, principalmente per via di (a), ma anche il semplice fatto che i mock li devi scriptare ogni volta "nel test/nel setup", mentre il comportamento di altri tipi di stub e' built-in nella definizione dello stup).
2. Ora, farne a meno e' in generale banale (specie in Python). La questione e' se sia una buona idea. Io direi di no. Non ho religione, tantomeno per i test double. Uso qualunque cosa ritenga la piu' efficace per il problema che devo risolvere. Specie per testare determinati edge case i mock sono molto comodi, e quindi li uso. Ogni volta che sono la scelta giusta li uso. Confesso... qualche volta lo faccio anche quando non credo che lo siano per semplice consistenza con la code base esistente. Ma vabbe'.

> A volte pero' la scelta non e' cosi' banale, soprattutto quando hai un test piu' "interessante" di un altro dove vuoi verificare il comportamento (piu' white che black).

E qui la questione e' filosofica. Cosa e' il "comportamento"? La cosa che i mock fanno e che gli altri test double in generale non fanno e' di testare *esattamente* cosa viene chiamato e in che ordine. Questa e' una possibile definizione di "comportamento", ma e' anche quella che ti porta nella direzione dei test fragili. Il comportamento che tipicamente interessa non e' cosi' specificato. Certo, se testi "lo script", allora testi per piu' forte ragione anche il comportamento (forse... to a point, in effetti, facciamo finta di si, anche se non e' cosi'). Ma in effetti mi basta qualcosa di piu' debole. Il comportamento che mi interessa e' essenzialmente: "se a questo oggetto sono successe queste cose in passato, allora chiamando questa cosa con questi parametri ci sara' questo side-effect e valore di ritorno, e da ora in poi il mio oggetto rispondera' alle cose in questa maniera". Se poi questo avviene perche' un certo metodo viene chiamato o meno e', se vuoi, meno interessante. Certo: se posso dimostrare che l'unico modo che una certa cosa accada sia chiamando un certo metodo (sembra complicato, ma in tanti casi e' semplice), allora basta testare che quel metodo e' stato chiamato. Eppero' questa cosa e' rischiosa: versioni successive potrebbero introdurre un secondo modo di fare quella cosa. Nota che anche il tipo di cose che non si vedono *assolutamente* in una code review, a meno che i reviewer non abbiano la cosa in mente. Ma vabbe'.

> il che a mio parere e' grosso modo simile a "bisogna testare i metodi privati?". In Python tutto cio' e' banale, ma alla fine testare i metodi privati e' davvero al limite con i mocks - perche' stai verificando il comportamento, non piu' lo stato.

Capisco la similitudine. Ma il punto e' che non "bisogna" fare nulla. Il problema e' semplice: testare i metodi non pubblici mi serve? Se no, niente. Quando mi serve, risolte circostanze sulla vita a medio termine sul progetto, boh, lo faccio. E il Professorone che mi dice di farlo o non farlo puo' tranquillamente venire qua a prendere il mio pager. Posso anche lanciarlo, ma ho notato che i pager lanciati si trasformano automaticamente in uccelli paduli.

Citazione
Il fatto e' anche che tools in Java come mockito hanno in realta' rovinato la terminologia e confuso ancor di piu' gli sviluppatori. "Uso un mock!" -> in realta' si sta usando uno stub, ma mockito te lo vende come mock.

Ora... mockito fa mock + qualche forma di injection/patching/spying. Ma dal punto di vista della classificazione quelli sono proprio dei mock (e in addizione ti consente anche di fare spy). Prova a rileggere la differenza fra mock e stub nell'articolo che hai linkato. Quello che ti da mockito sono proprio dei mock. Tipicamente per uno stub non usi nessuna libreria particolare in effetti (se non poi quando e se devi fare patching/inspecting).

>  Insomma, il numero di volte che capita di *dover* verificare il comportamento di un certo metodo rispetto al numero di volte che non te ne frega ha spesso un rapporto di 1:3, o forse inferiore.

Questa non la capisco molto...

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.104
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:Unit test di metodi "privati"
« Risposta #22 il: Agosto 10, 2017, 11:39 »
> Ora... mockito fa mock + qualche forma di injection/patching/spying. Ma dal punto di vista della classificazione quelli sono proprio dei mock (e in addizione ti consente anche di fare spy). Prova a rileggere la differenza fra mock e stub nell'articolo che hai linkato. Quello che ti da mockito sono proprio dei mock. Tipicamente per uno stub non usi nessuna libreria particolare in effetti (se non poi quando e se devi fare patching/inspecting).

Hai ragione! Mi riferivo comunque alle spies. (Credo che online si trovino parecchi esempi che usano mockito e menzionano "stub"... bah!)

> Questa non la capisco molto...

Nel senso che spesso i mock vengono abusati, quando in realta' si potrebbe tranquillamente usare uno stub (o qualcos'altro), ma vedo spesso che si usa mockito per il semplice fatto che e' semplice da usare...
« Ultima modifica: Agosto 10, 2017, 13:56 da Markon »

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #23 il: Agosto 14, 2017, 15:16 »
> Nel senso che spesso i mock vengono abusati, quando in realta' si potrebbe tranquillamente usare uno stub (o qualcos'altro), ma vedo spesso che si usa mockito per il semplice fatto che e' semplice da usare...

Ah, certo. Si. Questo e' vero. E ho visto fare la stessa cosa con mock (la libreria di foord) in Python.
E non sono manco tutti Javisti.