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

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.641
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Unit test di metodi "privati"
« il: Luglio 07, 2017, 00:34 »
Ciao a tutti,

oggi a lavoro si discuteva di un argomento interessante. Il target era C#, ma credo che l'argomento sia cross-language.

Secondo voi, gli unit test devono testare anche i metodi privati delle classi, o avere come target solo la parte pubblica della classe?

Io sono del parere che i metodi privati sono dettagli implementativi che nemmeno i test dovrebbero conoscere, in quanto i test devono basarsi sulle specifiche e non sull'implementazione. Altri sostengono che invece si debba testare "tutto" il codice, per cui anche i metodi privati.

Voi cosa ne pensate?

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.854
  • Punti reputazione: 9
    • Mostra profilo
Re:Unit test di metodi "privati"
« Risposta #1 il: Luglio 07, 2017, 09:28 »
Mah. Anche se una funzione è "privata" in qualsiasi modo, avrà pure una signature, e sarà pure stata fatta con delle specifiche, magari non proprio delle specifiche scritte, ma insomma... si dovrà pur sapere in qualche modo quello che si suppone che faccia. E se si sa in qualche modo, allora si può testare. Si "deve" anche testare? Direi proprio di sì. I test dovrebbero stare più attaccati possibile al codice: saranno pure dettagli implementativi, ma appunto, se quando qualcosa cambia e si rompe tu puoi saperlo solo perché fallisce un test di livello molto più alto, poi può essere difficile trovare il baco. Certo, suppongo che ci siano vie di mezzo: se fai girare la suite di test come un maniaco dopo ogni singolo commit, allora probabilmente riesci a isolare i problemi anche con dei test messi più "a valle"... ma auguri con i tempi di consegna però.

Del resto, suppongo che se provi ad astrarre il tuo principio ancora un po', allora l'unico test che vale davvero la pena di effettuare è il test esistenzialista: controllare se l'utente è vivo o morto dopo un'ora di utilizzo del programma. Ma capisci che non sarebbe molto informativo.

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.104
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:Unit test di metodi "privati"
« Risposta #2 il: Luglio 07, 2017, 19:23 »
Se vuoi raggiungere un certo livello di coverage (branch coverage), come fai a escludere i metodi privati? Alla fine vengono tutti tirati in ballo (a meno che non hai classi tipo POJOs).

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.641
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #3 il: Luglio 07, 2017, 19:27 »
Però la coverage la devi calcolare a partire da utilizzi della classe fatti dall'esterno, mica devi chiamare a mano i metodi privati per coprire tutti i casi.

Se non riesci a coprire tutti i branch/le righe/ecc usando solo i metodi pubblici e tutti i casi, allora hai codice non raggiungibile ---> inutile.

Offline Markon

  • python sapiens sapiens
  • *
  • moderatore
  • Post: 4.104
  • Punti reputazione: 5
    • Mostra profilo
    • Neolithic
Re:Unit test di metodi "privati"
« Risposta #4 il: Luglio 08, 2017, 09:24 »
Appunto!

Se hai una cosa del tipo:

class Example(object):
  def __init__(self, foo):
    self.foo = foo
 
  def _get_something(self):
    return 'something'

  def _get_something_else(self):
    return 'something_else'

  def calculate_something(self):
    if self.foo == 'foo':
      return self._get_something()
    else:
      return self._get_something_else()

  def calculate(self):
    if self.foo == 'foo':
      return self._get_something()



Se hai un branch coverage del 100%, testi anche i metodi privati in questo caso. Se ci fossero metodi non "chiamati":

class Example(object):
  def __init__(self, foo):
    self.foo = foo
 
  def _get_something(self):
    return 'something'

  def _get_something_else(self):
    return 'something_else'

  def calculate_something(self):
    if self.foo == 'foo':
      return self._get_something()




allora come dici tu, sarebbe codice non raggiungibile, pertanto visibile con un coverage tool.

« Ultima modifica: Luglio 08, 2017, 09:27 da Markon »

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.641
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #5 il: Luglio 08, 2017, 10:25 »
Devo averti frainteso io allora.

Non voglio evitare che vengano coperti i metodi privati, semplicemente sono convinto che non abbia senso scrivere dei test appositi per i metodi privati ma che vadano testati attraverso l'interfaccia pubblica che la classe fornisce, che è il flusso di esecuzione corretto.

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.854
  • Punti reputazione: 9
    • Mostra profilo
Re:Unit test di metodi "privati"
« Risposta #6 il: Luglio 08, 2017, 11:51 »
Sì ma non c'è dubbio che se testi, per dire, solo l'entry-point della tua applicazione, prima o poi raggiungi anche tutto il resto e in certo senso hai "coperto" tutto. Ma allora davvero, tanto vale testare se l'utente è vivo o morto dopo un'ora di utilizzo.

Ora, non è i test siano una religione con dei dogmi. Dopo un po' uno fa come funziona per lui, magari bilanciando con altri strumenti. Però è vero che se certi pattern sono diffusi e (gioco di parole) testati, un motivo ci sarà.

Immagina di avere una funzione A che, attraverso diversi gradi di chiamate, ne raggiunge altre venti, B, C, etc. Immagina di testare solo A. Per un certo significato della parola "coprire", puoi dire di aver coperto anche B, C, etc. Ma a parte il vanto di aver coperto tutto, a lato pratico che cosa ti serve? Immagina di fare venti modifiche puntuali, una per ciascuna delle venti funzion B, C... Poi fai girare di nuovo la suite di test, e scopri che fallisce. Diciamo senza eccezioni, senza niente di clamoroso. Soltanto, con certi input prima A ti restituiva "42", adesso ti restituisce "24". Mi spieghi come fai a risalire a quale delle venti modifiche che hai fatto causa il problema? Va bene, puoi cavartela con altri strumenti. Se le venti modifiche corrispondono a venti commit, puoi armarti di pazienza e git bisect, e prima o poi ce la fai. Ma allora è "git driven development", non "test driven development", non so se mi spiego.

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.641
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #7 il: Luglio 08, 2017, 12:11 »
Sì ma non estremizziamo i concetti.

Io non parlo di testare solo l'entry point, io parlo di unit-test classe per classe, che devono garantire che le pre e post condizioni di ogni metodo pubblico siano rispettate.

E attenzione, lo scopo dei test non è debuggare i problemi. E' di rilevarli. Come capire cosa si è rotto non è compito dei test.

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 2.854
  • Punti reputazione: 9
    • Mostra profilo
Re:Unit test di metodi "privati"
« Risposta #8 il: Luglio 08, 2017, 12:45 »
> E attenzione, lo scopo dei test non è debuggare i problemi. E' di rilevarli. Come capire cosa si è rotto non è compito dei test.

"Quasi" corretto. Se lo scopo fosse solo rilevare il problema, allora (again) anche l'utente infuriato che ti picchia con un randello perché ha perso tutti i soldi gestiti dal tuo programma è un rilevatore affidabile del problema. Affidabile, ma poco utile. Più sposti a valle il rilevatore, meno utile diventa. Rileva lo stesso, eh?

Direi piuttosto che compito dei test (uno dei) è rilevare il problema *il più vicino possibile* a dove si è verificato.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #9 il: Luglio 08, 2017, 14:25 »
Ma ovviamente testo anche i metodi privati, scherziamo? Quando il mio software va giu', i page arrivano mica al professorone che ha deciso che le cose private non si testano.

Sembra tutta una discussione molto teorica e molto pratica.

Personalmente cerco di muovermi a coverage totale. Se devo testare roba privata, amen. Quello che *non* si dovrebbe fare (a prescindere) e' scrivere test che dipendono in modo troppo stretto da dettagli implementativi. E qui voi starete pensando "ti abbiamo acchiappato che finalmente ti sei bevuto il cervello"... ma il fatto e' che non e' che non bisogna dipendere dai dettagli implementativi perche' se no gesu' bambino piange. Il motivo per cui non lo si deve fare e' che poi diventa piu' difficile modificare il codice (perche' ogni volta che qualcuno lo tocca deve anche toccare i test). Perfetto. Quindi non lo si fa per evitarsi del dolore. Non c'e' la XUnit Schutzstaffel.

Ora se una persona e' ragionevole, riesce a scrivere test di metodi privati/protetti senza dipendere in modo eccessivo dal codice. Cioe'... se poi cambio il nome del metodo privato o la signature, il refactoring mi operera' anche sul test e amen.

Perche' pensare di potere testare solo la roba pubblica e' relativamente ingenua. Ci sono oggetti la cui effettiva interfaccia pubblica ha *un* metodo. Che magari prende o oggetti abbastanza nestati (tipo immaginati un oggetto che essenzialmente e' una configurazione), oppure ha un po' di parametri (che so... addirittura 3, o peggio 4). Ora testare un metodo con 3-4 argomenti e' parecchio complicato. Essenzialmente il numero di test "medio" che devi fare per coprire e' in qualche modo correlato con il prodotto cartesiano degli "stati" diversi che ciascun argomento puo' avere. Stati qui e' vago... per intenderci... a seconda del metodo potrebbe essere lista vuota/lista con un solo elemento/lista con un numero pari di elementi/etc. A seconda di quello che fa cambiare il comportamento del test. Questa e' una semplificazione necessaria perche' non possiamo davvero testare per tutti i valori (a meno che non sia tipo una enum o qualcosa del genere).

Bene... spesso invece si possono testare i metodi privati, che magari sono piu' semplici, e avere il 100% della coverage sul loro comportamento. A questo punto per il test del metodo pubblico non e' piu' necessario ragionare interamente in termini della casistica dei parametri di input, ed essenzialmente i test che vanno scritto dipendono dai parametri di output dei metodi privati. Quando ci si trova in questa situazione, testare i metodi privati permette di ammazzare la complessita' dei test. Sotto determinate ipotesi, si passa da testare X*Y*Z casi a doverne testare X+Y+Z.

GlennHK> Però la coverage la devi calcolare a partire da utilizzi della classe fatti dall'esterno, mica devi chiamare a mano i metodi privati per coprire tutti i casi.

No, devo scrivere tutti i test che sono necessari per raggiungere una buona coverage.

Scusa, pensiamo a qualcosa di questo tipo:


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


Ora una roba del genere la possiamo scrivere in tanti modi, non necessariamente come l'ho scritta io. Ma mi pare codice abbastanza ragionevole. E' relativamente semplice che si capisce cosa fa, e' tutto sommato relativamente flessibile da potere crescere un pochetto, cerca di non creare un numero delirante di sotto-oggetti e tipicamente i log non sono complicati abbastanza da scriversi un "vero" parser LLLR per dire.

Puoi testare tutto a partire dal metodo pubblico? Certo. Ma quanto e' piu' comodo testare individualmente ciascuno dei metodi privati per vedere che fa la cosa giusta e poi avere un test piu' semplice per il metodo pubblico? Parecchio. Se non lo si vede in questo esempio, immaginiamo una roba in cui ci sono piu' di un parametro (per cui si comincia a vedere l'esplosione cartesiana).


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


GlennHK> Se non riesci a coprire tutti i branch/le righe/ecc usando solo i metodi pubblici e tutti i casi, allora hai codice non raggiungibile ---> inutile.

A volte non e' questione di "non riuscire". A volte e' questione di praticita'. E anche di leggibilita' del test.

Ric> Direi piuttosto che compito dei test (uno dei) è rilevare il problema *il più vicino possibile* a dove si è verificato.

Yep.

Glenn> Io non parlo di testare solo l'entry point, io parlo di unit-test classe per classe, che devono garantire che le pre e post condizioni di ogni metodo pubblico siano rispettate.

Guarda che non e' balzano il contro-esempio. Cioe'... abbiamo anche classi private/protette/package local. Se decidiamo che i test dei metodi protetti *non* devono essere fatti cascasse il mondo, sembrerebbe arbitrario dire che invece le classi protette devono esserle. Se credessimo in questa cosa ci troveremmo con persone che scrivono classi invece di metodi perche' cosi' e' piu' semplice testarle. E sarebbe un bel costo. Quindi direi che classi e metodi privati devono seguire lo stesso destino. O consentiamo di testare entrambi, oppure li proibiamo entrambi.

Ora, se decidiamo di proibirli entrambi *e* applichiamo un buon principio ingegneristico (quello di restringere al minimo l'accesso) ci troviamo che in pratica possiamo solo scrivere test a livello di componente (tipo il macro-componente che carichiamo con guice o con spring o quello che hai in C#). E scusa, ma a questo punto c'e' rischio che quasi non sono unit-test.


No, secondo me hai letto male il fatto che non si testano i metodi privati. Quello che trovi sul Meszaros e' che *quando* si riesce a coprire tutto con black box e interfaccia pubblica e' ovviamente preferibile. Ma non e' che non ammette la possibilita (possibilita' "normale" che capita normalmente nel lifecycle del software)

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.641
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #10 il: Luglio 08, 2017, 17:45 »
Non sono per niente d'accordo con l'esempio.

Io l'esempio che hai fatto tu lo testerei così:

def test_1():
    parser = Parser()
    parser.parse_logfile([line_of_type1])
    # assert

def test_2():
    parser = Parser()
    parser.parse_logfile([line_of_type2])
    # assert


Passo sempre dall'interfaccia pubblica, pur sapendo che quello si traduce in una chiamata al metodo privato. Non sto dicendo che i test glass_box mi facciano schifo. Sto solo dicendo che trovo, imho, assurdo dover scrivere test appositi per i metodi privati.
Chi mi garantisce che posso chiamare il metodo privato della classe senza aver eseguito delle inizializzazioni che invece i metodi pubblici fanno?
Il programmatore scrive il software affinché venga usato nel modo che indica lui, non deve pensare che ogni metodo privato deve trovare uno stato coerente con quello che garantiscono i metodi pubblici.
Chiaro che se chiamo i metodi privati in modo arbitrario potenzialmente posso arrivare a stato incoerente.

Esempio:

class ...:
    def process():
        if condizione1: self.error_code = 1
        if condizione2: self.error_code = 2
        if condizione3: self.error_code = 3
        # do other stuff
        self._check_error()

    def process2():
        if condizione1: self.error_code = 4
        if condizione2: self.error_code = 5
        if condizione3: self.error_code = 6
        # do other stuff
        self._check_error()

    def _check_error():
        if self.error_code:
            raise ValueError(f"{self.error_code} error code")


Come testi _check_error? Setti l'error_code a mano? Io non lo farei.

Se la logica di parse di ogni linea è molto complessa, fai una classe LineParser e testi quella. Semplice, pulito, testabile.

Tra l'altro i test sono assert. Le assert sono booleane. Le assert non ti dicono quale riga di codice ha rotto cosa. Le assert dicono: "ehi, lo stato a cui sei arrivato non è corretto".

Oltretutto vorrei aprire una parentesi che non ha a che fare con Python.
Come fai in un linguaggio come C#/Java a chiamare un metodo privato da un test?

Offline Tungsteno

  • python erectus
  • ***
  • Post: 181
  • Punti reputazione: 0
    • Mostra profilo
    • Sviluppo Videogiochi
Re:Unit test di metodi "privati"
« Risposta #11 il: Luglio 09, 2017, 18:20 »
Scusate la mia intromissione,  ma da ignorante in materia, non dipenderebbe tutto da come uno implementa le proprie classi?
« Ultima modifica: Luglio 09, 2017, 18:36 da Tungsteno »

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #12 il: Luglio 10, 2017, 10:23 »
Non sono per niente d'accordo con l'esempio.

Lo immaginavo. Il problema principale e' trovare un esempio che sia allo tempo relativamente naturale, sufficientemente semplice da non avere bisogno di pagine di spiegazioni e  mostri in modo inequivocabile la situazione. Ora, piu' parametri aggiungo, piu' diventa semplice fare vedere il problema. Ma gli esempi tendono ad essere o artificiali o ad avere bisogno di un bel po' di dominio.

Posso "descriverne" uno... avevo un coso che veniva chiamato quando un pacchetto udp di risposta veniva mandato. Ovviamente il pacchetto conteneva la roba che mi serviva binaria. Perche' l'interfaccia era cosi'? Eh, per come funzionavano le librerie al contorno. Posso entrare in dettagli, ma penso che sia abbastanza evidente che sia piu' semplice scrivere la maggior parte dei test per gli internals che prendono roba decodificata. I test sono piu' espliciti, piu' leggibili, piu' intuitivi....

Citazione
Glenn> Io l'esempio che hai fatto tu lo testerei così:

Ovviamente.

def test_1():
    parser = Parser()
    parser.parse_logfile([line_of_type1])
    # assert

def test_2():
    parser = Parser()
    parser.parse_logfile([line_of_type2])
    # assert


Si capisco. E in Python viene anche bellino perche' una lista e un file iterato linea per linea si comportano proprio allo stesso modo. E ovviamente una lista di un singolo elemento (o di pochi elementi) non e' particolarmente complicato da scrivere.
Ci hai ragione: esempio sbagliatino (per lo meno in Python).

Sinceramente mi sarei aspettato un'altra obiezione: non ha nessun senso che quell'affare sia una classe. Scritto come lo ho scritto, sarebbe stata la mia prima obiezione. Ovviamente poco sarebbe cambiato: avremmo potuto parlare di funzione pubblica e funzione privata senza spostare il discorso di una virgola. E infatti nota che io non avevo dato "parser" come nome alla classe in modo intenzionale. Discorso lungo e un po' tangenziale.

Altra cosa, l'interfaccia pubblica potrebbe astrarre dettagli rispetto a quello che succede internamente e non mi sembra affatto male avere test piu' granulari. Perche' alla fine credo che i test abbiano un ruolo molto piu' ampio di quello che suggerisci. Meszaros dice:

Self-Testing Code helps us:
– Produce better quality software
– Produce the right software
– Work faster
– Respond to change (agility)
• It does this by:
– Providing focus
– Providing rapid feedback
– Reducing stress levels (anxiety)


Io mi appello a "work faster" qui. Se i test piu' granulari mi aiutano a fixare un change piu' rapidamente, ben venga. Onestamente navigare a botte di debugger fintanto che si isola il problema non e' proprio divertente. E tutte le volte che mi imbatto in quaslcosa di strano, lo documento con un test. Giusto per sicurezza.

> Passo sempre dall'interfaccia pubblica, pur sapendo che quello si traduce in una chiamata al metodo privato. Non sto dicendo che i test glass_box mi facciano schifo.

Come ti pare: ma sappi che non c'e' nessuna ragione per cui non devi fare whitebox testing negli unit-test, fintanto che questo non crea altri problemi (test fragili etc).

Toh, per questi qua il whitebox testing e' proprio tipico degli unit test. Io non sono 100% in accordo, ma tanto per dire.

http://softwaretestingfundamentals.com/unit-testing/


Glen> Sto solo dicendo che trovo, imho, assurdo dover scrivere test appositi per i metodi privati.

Vedrai che quando i problemi si complicano, ti sembrera' meno assurdo.

> Chi mi garantisce che posso chiamare il metodo privato della classe senza aver eseguito delle inizializzazioni che invece i metodi pubblici fanno?

Semplicemente perche' se il codice sotto test si basa in modo eccessivo su stato mutabile (altrimenti il problema non sussiste, no?) non passera' la code review per quel motivo.
Io non voglio in produzione codice che si basa eccessivamente sullo stato.

La maggior parte dei miei oggetti hanno *uno* stato per tutta la loro esistenza. Il rimanente ha un numero limitato e finito di stati, possibilmente con un diagramma di transizione di stato molto semplice. Se non e' semplice, passo ad altre tecniche.
Quindi capisci che non ho in generale quel tipo di problema per scelte fatte a monte, scelte che difendo molto intensamente. Ancora una volta, tutte le volte che hai un baco in produzione, se il tuo oggetto ha *uno* stato, devi solo capire come mai e' stato creato in modo che il metodo ha fallito. Se invece l'oggetto cambia stato, devi controllarlo per tutto il suo lifecycle. E quando hai, appunto, un'applicazione complessa questa cosa puo' diventare sgradevole (e soprattutto diventa piu' complicata con il crescere del lifespan dell'oggetto). I miei oggetti immutabili invece sono O(1) da validare.

In termini di Java, quasi tutto e' final. La roba mutabile cerco di confinarla a variabili (non campi) e possibilmente *non* passarla ad altri metodi -- a meno che non sia indispensabile --. Se devo ritornarla, non e' infrequente che la impacchetti in modo immutabile oppure, posso valutare di ritornarla cosi' solo se nessun altro ha ownership di quel dato (manco per errore) in modo da non avere mai e poi mai azione a distanza. Se stai mutando un oggetto, voglio che tu sia l'unico ad averci sopra le mani.

Ci sono eccezioni? Certo. Ma finche' sono eccezioni so dove guardare quando c'e' un problema.

Citazione
Il programmatore scrive il software affinché venga usato nel modo che indica lui, non deve pensare che ogni metodo privato deve trovare uno stato coerente con quello che garantiscono i metodi pubblici.
Chiaro che se chiamo i metodi privati in modo arbitrario potenzialmente posso arrivare a stato incoerente.

Se abusi dello stato mutabile, certo. Pero' se abusi dello stato mutabile il problema non sono i test ai metodi privati. Il problema e' il codice stesso.
Cioe' capisco la tua posizione: hai quello che per me e' un pesantissimo code smell e questo ti costringe ad una disciplina molto rigorosa sui test (quindi passare sempre e solo dall'interfaccia pubblica).
E a questo punto ti dico... testare per ogni metodo e ogni input che vuoi testare (tutti pubblici, ben inteso) non basta: devi avere un test per ogni stato possibile del tuo oggetto. Vedi che avere un solo stato possibile salva tempo?

Citazione
Esempio:

class ...:
    def process():
        if condizione1: self.error_code = 1
        if condizione2: self.error_code = 2
        if condizione3: self.error_code = 3
        # do other stuff
        self._check_error()

    def process2():
        if condizione1: self.error_code = 4
        if condizione2: self.error_code = 5
        if condizione3: self.error_code = 6
        # do other stuff
        self._check_error()

    def _check_error():
        if self.error_code:
            raise ValueError(f"{self.error_code} error code")


Glen> Come testi _check_error? Setti l'error_code a mano? Io non lo farei.

Non mi sono mai posto il problema. Di solito quando vedo cose del genere sto pettinando qualcuno con un rastrello agricolo e la faccenda si risolve scrivendo codice testabile e ben fatto. Riflettiamo, vuoi?

Allora, qualunque cosa succede, se c'e' un  qualunque errore, quell'affare tira un eccezione. Il che vuole dire che, molto probabilmente, l'oggetto sara' distrutto come parte dello stack unwinding. Ritengo improbabile che sia importante salvare lo stato di errore in un campo. Anche perche' non e' mai una buona idea fidarsi dello stato che e' stato settato da un metodo che ha terminato in errore. E dopo tutto non serve: hai gia' l'eccezione. Ergo non serve scrivere il codice in quel modo. error_code puo' essere una variabile e check_error dovrebbe prenderla come argomento. E la posso testare una volta per tutte. Viceversa, devo duplicare tutti i casi di errore quando scrivo test per process1() e per process2(). Se invece io so che check_error(error) si comporta bene, allora basta che testi che in caso di errore viene chiamata e separatamente che funziona. Ecco che ho M+N test invece che MxN.

Detto questo, quello stile e' cosi' imperativo che non lo farei passare volentieri.

> Se la logica di parse di ogni linea è molto complessa, fai una classe LineParser e testi quella. Semplice, pulito, testabile.

Certo. Pero' scusa, quella classe non sarebbe certo pubblica (non ha ragione di esserlo). Quindi cosa fai, finche' hai un metodo non lo testi, ma improvvisamente quando diventa una classe, ugualmente privata/protetta, si? Perche'? Non e' comunque parte dell'interfaccia pubblica. E specie in Python non vedi assurdo nel dire che l'oggetto foo._parse_line non debba essere testato, mentre _LineParser() si? A me sembrano uguali...

Poi sempre fra di noi... finche' hai codice che lavora su quantita' piccole di dati, puoi permetterti tutto quello che vuoi. Pero' poi cosa succede... Siamo sicuri che questa line parser sia immutabile? Se creo *un* oggetto per parsare tutte linee del file (o, in questo caso, 2-3 oggetti per parsarle tutte) ok. Che pero' richiede che gli oggetti siano scritti in modo stateless. E quindi non capisco l'enorme beneficio di avere un oggetto invece che un metodo, se non per motivi puramente "religiosi". Sono la stessa roba e si comportano allo stesso modo (posso trasformare l'uno nell'altro in modo banale)

Se gli oggetti non sono stateless -- e quindi il cambiamento di stato rende molto piu' pulito avere una classe separata --, bene, buona fortuna. Quel codice a me non serve. Provassi a farlo girare sui miei log, schianterei qualunque cosa.

> Tra l'altro i test sono assert. Le assert sono booleane. Le assert non ti dicono quale riga di codice ha rotto cosa. Le assert dicono: "ehi, lo stato a cui sei arrivato non è corretto".

Non sono solo assert. Anche le condizioni di errore lanciate dal codice fanno parte del test. Pero' vedi... se testo roba piccola, non e' particolarmente difficile ricondurre l'errore alla linea giusta. Secondo me invece che fare la guerra ai test dei metodi privati, dovresti fare la guerra allo stile iper-imperativo di codice che sembra evidenziare il problema. Fra i due, quello e' il male. Se ve ne libererete ,avrete codice molto piu' robusto e facile da testare.

Citazione
Oltretutto vorrei aprire una parentesi che non ha a che fare con Python.
Come fai in un linguaggio come C#/Java a chiamare un metodo privato da un test?

Storia lunga. Diciamo che in generale non lo fai. Ci sono un paio di tricks, ma normalmente quello che fai e' marcarli protected e annotare che lo sono solo per testing. Il tooling fallira' la build se infrangi sta cosa. Oppure non li testi e testi solo la roba protected. Diciamo cosi'... ci sono trick. E ci sono librerie che ti aiutano a farlo. Ma direi che e' un po' beside the point.

E nota che io non ti sto dicendo che bisogna fare tutto whitebox. Figuriamoci. Quando sei fortunato e black box ti basta, meglio. Tipicamente meno test e in generale meno rischio che siano brittle. Ma semplicemente sono pragmatico. Quando mi rendo conto che la strada piu' efficace passa per white box, ci vado  senza pensarci due volte. In generale ho uno stile misto, in cui tutto quello che si puo' fare con bb finisce con bb e copro il resto con wb (se necesario)

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.641
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Unit test di metodi "privati"
« Risposta #13 il: Luglio 10, 2017, 23:12 »
Premessa: ragiono a classi solo perché è il contesto in cui tipicamente si vedono metodi privati. Non hanno senso le "funzioni" private, a meno di roba interna ad un modulo, e in quel caso ha senso che dei test abbiano come target il modulo stesso (visto che dovrebbero essere trasparenti per specifica, a differenza dei metodi privati).

Sono d'accordo con te per una cosa però: metodi che dipendono pesantemente dallo stato sono code smell. Ma se ti ritrovi a dover testare cose già scritte? (che è tipo il 90% del lavoro che faccio io, maintenance di codice già esistente)

Il discorso degli oggetti immutabili mi incuriosisce, mi piacerebbe approfondirlo in un'altra sede magari.

L'unica cosa che non mi ha convinto del tuo discorso è il dover fare LineParser privata. Ma varrebbe lo stesso se avessi fatto una funzione parse_line_1 pubblica, perché mi aspetto che la funzione sappia come parsare una linea a partire da un contesto ben definito e dalla linea, cose che può ricevere in input. Per cui mi aspetterei una classe o una funzione pubblica riutilizzabile, non una classe privata. Sono consapevole che sono varie strade che portano alla stessa soluzione.

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re:Unit test di metodi "privati"
« Risposta #14 il: Luglio 11, 2017, 09:44 »
Premessa: ragiono a classi solo perché è il contesto in cui tipicamente si vedono metodi privati. Non hanno senso le "funzioni" private, a meno di roba interna ad un modulo, e in quel caso ha senso che dei test abbiano come target il modulo stesso (visto che dovrebbero essere trasparenti per specifica, a differenza dei metodi privati).

Non sono d'accordo sul fatto che "non hanno senso". La visibilita' e' un concetto se vuoi un po' tangenziale a "cosa sono io". Ci sono specifiche sottigliezze, ma alla fine se hai il concetto di "metodo privato" e di conseguenza anche di "metodo statico privato", cosa c'e' di strano in una funzione privata? Il motivo per cui non ci pensi e' che Java, per dire, non ha quasi il concetto di "funzione". In go, per esempio, hai funzioni private. Sono comode.

Dopo di che non mi e' chiaro perche' sostieni che per le funzioni private (e devo assumere, i metodi statici privati) le regole sono diverse che per i metodi non statici privati.

Citazione
Sono d'accordo con te per una cosa però: metodi che dipendono pesantemente dallo stato sono code smell. Ma se ti ritrovi a dover testare cose già scritte? (che è tipo il 90% del lavoro che faccio io, maintenance di codice già esistente)

una fra:
1. suck it up
2. cerca un posto dove la gente lavora meglio
3. insegna loro a scrivere codice migliore (non che sia banale)

Temo non ci sia soluzione. L'unica e' morbidamente edurli.

> Il discorso degli oggetti immutabili mi incuriosisce, mi piacerebbe approfondirlo in un'altra sede magari.

Quando vuoi.

Citazione
L'unica cosa che non mi ha convinto del tuo discorso è il dover fare LineParser privata. Ma varrebbe lo stesso se avessi fatto una funzione parse_line_1 pubblica, perché mi aspetto che la funzione sappia come parsare una linea a partire da un contesto ben definito e dalla linea, cose che può ricevere in input. Per cui mi aspetterei una classe o una funzione pubblica riutilizzabile, non una classe privata. Sono consapevole che sono varie strade che portano alla stessa soluzione.

Si, fermati un secondo. E' piu' semplice di cosi'. Quelli cui piacciono i linguaggi tipo Java insistono molto che ogni ente debba avere la visibilita' minima possibile. Siccome un LineParser per uno specifico formato di log e' molto probabilmente usata solo dalla classe che parsa il file, finirebbe per stare bene come classe statica interna privata.

E quello che sto cercando di dire e' che in qualche modo hai importo "restrizioni" su cosa comporta essere privati per i metodi (non si testano), ma non le applichi per classi e apparentemente funzioni. Quello che ti sto dicendo e' che sono tutti la stessa cosa. Se l'idea e' che la roba privata non si testa perche' e' un dettaglio implementativo, lo stesso concetto deve valere come best practice per funzioni e classi. Altrimenti si arriva ad assurdi logici. Se invece si riconosce che le restrizioni non sono in se e per e se valide, ... beh, ancora una volta funzioni metodi e classi si possono trattare allo stesso modo: nessuna ha vincoli specifici contro essere testata se privato.