I test unitari non dovrebbero usare i miei metodi?


83

Oggi stavo guardando un video di "Nozioni di base su JUnit " e l'autore ha affermato che durante il test di un determinato metodo nel programma non è necessario utilizzare altri metodi propri nel processo.

Per essere più precisi, stava parlando di testare alcuni metodi di creazione dei record che prendevano un nome e un cognome per gli argomenti e li utilizzava per creare record in una determinata tabella. Ma ha affermato che nel processo di test di questo metodo, non dovrebbe usare gli altri suoi metodi DAO per interrogare il database per verificare il risultato finale (per verificare che il record sia stato effettivamente creato con i dati giusti). Sosteneva che, per questo, avrebbe dovuto scrivere un codice JDBC aggiuntivo per interrogare il database e controllare il risultato.

Penso di capire lo spirito della sua affermazione: non vuoi che il caso di test di un metodo dipenda dalla correttezza degli altri metodi (in questo caso, il metodo DAO), e questo si ottiene scrivendo (di nuovo) la tua convalida / codice di supporto (che dovrebbe essere più specifico e focalizzato, quindi, codice più semplice).

Tuttavia, voci nella mia testa hanno iniziato a protestare con argomenti come la duplicazione del codice, sforzi aggiuntivi inutili, ecc. Voglio dire, se eseguiamo l'intera batteria di test e testiamo accuratamente tutti i nostri metodi pubblici (incluso il metodo DAO in questo caso), non dovremmo va bene usare solo alcuni di questi metodi durante il test di altri metodi? Se uno di loro non sta facendo quello che dovrebbe, allora il suo caso di test fallirà e possiamo ripararlo ed eseguire nuovamente la batteria di prova. Non è necessario duplicare il codice (anche se il codice duplicato è in qualche modo più semplice) o sprecare sforzi.

Ho un forte feeling al riguardo a causa delle recenti applicazioni Excel - VBA che ho scritto (correttamente testate dall'unità grazie a Rubberduck per VBA ), in cui l'applicazione di questa raccomandazione significherebbe molto lavoro extra aggiuntivo, senza alcun vantaggio percepito.

Puoi per favore condividere le tue opinioni su questo?


79
È un po 'strano vedere un test unitario che coinvolge affatto il database
Richard Tingle il

4
IMO va bene chiamare IFF altre classi sono abbastanza veloci. Derida tutto ciò che deve andare su disco o su una rete. Non ha senso deridere un semplice IMO di vecchia classe.
RubberDuck,

2
Hai un link a questo video?
candied_orange,

17
" adeguatamente testato dall'unità grazie a Rubberduck per VBA " - signore, avete appena fatto la mia giornata. Riparerei l'errore di battitura e lo modificavo da "RubberDuck" a "Rubberduck", ma mi sentirei come uno spammer che lo fa e aggiungo un link a rubberduckvba.com ( Possiedo il nome di dominio e co-proprietario del progetto con @RubberDuck) - quindi commenterò qui. Ad ogni modo è fantastico vedere le persone che usano effettivamente lo strumento che è stato responsabile della maggior parte delle mie notti insonni per la parte migliore degli ultimi due anni! =)
Mathieu Guindon,

4
@ Mat'sMug e RubberDuck Adoro quello che stai facendo con Rubberduck. Per favore continua così. Certamente la mia vita è più facile per questo (mi capita di fare molti piccoli programmi e prototipi in Excel VBA). A proposito, la menzione di Rubberduck nel mio post era solo che cercavo di essere gentile con RubberDuck stesso, che a sua volta è stato carino con me qui in PE. :)
carlossierra,

Risposte:


186

Lo spirito della sua affermazione è davvero corretto. Il punto di unit test è isolare il codice, testarlo senza dipendenze, in modo che qualsiasi comportamento errato possa essere rapidamente riconosciuto dove sta accadendo.

Detto questo, il test unitario è uno strumento ed è destinato a servire i tuoi scopi, non è un altare da pregare. A volte ciò significa lasciare le dipendenze perché funzionano in modo sufficientemente affidabile e non si vuole preoccuparsi di deriderle, a volte ciò significa che alcuni dei test unitari sono in realtà abbastanza vicini se non effettivamente test di integrazione.

Alla fine non ti stai valutando, l'importante è il prodotto finale del software in fase di test, ma devi solo essere consapevole di quando pieghi le regole e decidi quando valgono i compromessi.


117
Evviva il pragmatismo.
Robert Harvey,

6
Vorrei aggiungere che non è tanto l'affidabilità quanto la velocità che è il problema che porta a eliminare le dipendenze. Il test nel mantra dell'isolamento è così avvincente che spesso questo argomento è mancato.
Michael Durrant,

4
"... il test unitario è uno strumento ed è destinato a servire i tuoi scopi, non è un altare da pregare." - Questo!
Wayne Conrad,

15
"non un altare da pregare" - in arrivo gli allenatori TDD arrabbiati!
Den

5
@ jpmc26: peggio ancora, non vuoi che tutti i tuoi test unitari vengano superati perché hanno gestito correttamente il servizio Web, il che è un comportamento valido e corretto, ma non esercita mai effettivamente il codice per quando è attivo! Una volta che lo stub puoi scegliere se è "su" o "giù" e testare entrambi.
Steve Jessop,

36

Penso che questo dipenda dalla terminologia. Per molti, un "unit test" è una cosa molto specifica, e per definizione non può avere una condizione pass / fail che dipende da qualsiasi codice esterno all'unità (metodo, funzione, ecc.) Da testare. Ciò include l'interazione con un database.

Per altri, il termine "unit test" è molto più flessibile e comprende qualsiasi tipo di test automatizzato, incluso il codice di test che testa parti integrate dell'applicazione.

Un purista di test (se posso usare quel termine) potrebbe chiamarlo test di integrazione , distinto da un test unitario in base al fatto che il test dipende da più di una pura unità di codice.

Ho il sospetto che tu stia utilizzando la versione più libera del termine "unit test" e in realtà ti riferisci a un "test di integrazione".

Usando queste definizioni, non dovresti dipendere da un codice esterno all'unità testata in un "test unitario". In un "test di integrazione", tuttavia, è perfettamente ragionevole scrivere un test che eserciti un determinato pezzo di codice e quindi controlli il database per i criteri di passaggio.

Se dovresti dipendere da test di integrazione o unit test o entrambi è un argomento di discussione molto più ampio.


Hai ragione sui tuoi sospetti. Sto abusando dei termini e ora vedo come ogni tipo di test può essere gestito in modo più appropriato. Grazie!
carlossierra,

11
"Per altri, il termine" unit test "è molto più flessibile" - a parte qualsiasi altra cosa, i cosiddetti "framework di unit test" sono un modo davvero conveniente per organizzare ed eseguire test di livello superiore ;-)
Steve Jessop,

1
Piuttosto che una distinzione "pura" vs "libera", che è carica di connotazioni, suggerirei che la distinzione è tra coloro che vedono "unità", "dipendenze", ecc. Dal punto di vista del dominio (ad es. "Autenticazione" conta come unità, il "database" dovrebbe essere considerato come una dipendenza, ecc.) rispetto a coloro che li visualizzano dal punto di vista del linguaggio di programmazione (ad es. i metodi sono unità, le classi sono dipendenze). Trovo che definire ciò che si intende con frasi come "l'unità sotto test" può trasformare gli argomenti ("Ti sbagli!") In discussioni ("È questo il giusto livello di granularità?").
Warbo

1
@EricKing Naturalmente, per la stragrande maggioranza di quelli della tua prima categoria, l' idea stessa di coinvolgere il database in tutti i test unitari è un anatema, sia che tu utilizzi i tuoi DAO o colpisca direttamente il database.
Periata Breatta,

1
@AmaniKilumanga La domanda era sostanzialmente "qualcuno dice che non dovrei fare questa cosa nei test unitari, ma lo faccio nei test unitari e penso che vada bene. Va bene?" E la mia risposta è stata "Sì, ma la maggior parte delle persone chiamerebbe questo test di integrazione anziché un test unitario". Per quanto riguarda la tua domanda, non so cosa intendi con "i test di integrazione possono usare metodi con codice di produzione?".
Eric King,

7

La risposta è sì e no ...

Test unici che si svolgono da soli sono un must assoluto in quanto ti consentono di stabilire il lavoro di base sul corretto funzionamento del codice di basso livello. Ma nel codificare librerie più grandi, troverai anche aree di codice che richiedono test che attraversano le unità.

Questi test tra unità sono utili per la copertura del codice e quando si testano le funzionalità end-to-end, ma presentano alcuni inconvenienti che è necessario conoscere:

  • Senza un test isolato per confermare ciò che sta rompendo, un test "cross-unit" fallito richiederà una risoluzione dei problemi aggiuntiva per determinare cosa non va nel codice
  • Affidarsi troppo ai test tra unità può farti uscire dalla mentalità contrattuale che dovresti sempre avere quando scrivi il codice SOLID orientato agli oggetti. I test isolati in genere hanno senso perché le tue unità dovrebbero eseguire solo un'azione di base.
  • Il test end-to-end è desiderabile ma può essere pericoloso se un test richiede di scrivere su un database o eseguire alcune azioni che non si vorrebbe avere in un ambiente di produzione. Questo è uno dei tanti motivi per cui i framework beffardi come Mockito sono così popolari perché ti permettono di simulare un oggetto e imitare un test end-to-end senza cambiare effettivamente qualcosa che non dovresti.

Alla fine della giornata vuoi avere alcuni di entrambi ... un sacco di test che intrappolano funzionalità di basso livello e alcuni che testano funzionalità end-to-end. Concentrati sul motivo per cui stai scrivendo test in primo luogo. Sono lì per darti la sicurezza che il tuo codice sta funzionando come ti aspetti. Qualunque cosa tu debba fare per realizzarlo, va bene.

Il fatto triste ma vero è che se stai usando dei test automatici, allora hai già un vantaggio su molti sviluppatori. Le buone pratiche di test sono la prima cosa da fare quando un team di sviluppo si trova ad affrontare scadenze rigide. Quindi, fintanto che ti atterrai alle tue pistole e scriverai unit test che riflettono in modo significativo come dovrebbe funzionare il tuo codice, non ossessionerei quanto siano "puri" i tuoi test.


4

Un problema con l'utilizzo di altri metodi oggetto per testare una condizione è la perdita di errori che si annullano a vicenda. Ancora più importante, ti stai perdendo il dolore di un'implementazione di test difficile e quel dolore ti sta insegnando qualcosa sul tuo codice sottostante. Test dolorosi ora significano manutenzione dolorosa in seguito.

Rendi le tue unità più piccole, dividi la classe, il refactor e la riprogettazione fino a quando non è più facile testare senza reimplementare il resto dell'oggetto. Chiedi aiuto se ritieni che il tuo codice o i test non possano essere ulteriormente semplificati. Prova a pensare a un collega che sembra avere fortuna e ottenere incarichi facili da testare in modo pulito. Chiedigli aiuto, perché non è fortuna.


1
La chiave qui è nell'unità. Se è necessario effettuare chiamate ad altre procedure e processi, per me non sarebbe un'unità. Se vengono effettuate più chiamate e devono essere convalidati più passaggi, quindi se si tratta di un pezzo di codice più grande di un'unità, suddividerlo in pezzi più piccoli che coprono un singolo pezzo di logica. Normalmente, anche una chiamata DB verrebbe simulata o un DB in memoria verrebbe utilizzato nel test unit su accesso a un DB reale e la necessità di annullare i dati dopo il test.
dlb,

1

Se l'unità di funzionalità da testare è "sono i dati memorizzati in modo persistente e recuperabile", allora avrei il test di unità test che: archiviare in un database reale, distruggere tutti gli oggetti che contengono riferimenti al database, quindi chiamare il codice per recuperare il oggetti indietro.

Testare se un record viene creato nel database sembra preoccuparsi dei dettagli di implementazione del meccanismo di archiviazione piuttosto che testare l'unità di funzionalità esposta al resto del sistema.

Potresti voler abbreviare il test per migliorare le prestazioni usando un database finto, ma ho avuto appaltatori che hanno fatto esattamente questo e sono usciti alla fine del loro contratto con un sistema che ha superato tali test, ma in realtà non ha archiviato nulla nel database tra i riavvii del sistema.

Puoi discutere se "unità" nel test unitario denota "unità di codice" o "unità di funzionalità", funzionalità forse creata da molte unità di codice in concerto. Non lo trovo una distinzione utile: le domande che terrò a mente sono "il test ti dice qualcosa sul fatto che il sistema offra valore aziendale" e "il test è fragile se l'implementazione cambia?" Test come quello descritto sono utili quando si esegue il TDD del sistema - non è ancora stato scritto "ottieni oggetto dal record del database", ma non è possibile testare l'intera unità di funzionalità, ma sono fragili rispetto alle modifiche di implementazione, quindi rimuoverli una volta verificata l'intera operazione.


1

Lo spirito è corretto

Idealmente, in un unit test , stai testando un'unità (un metodo individuale o una piccola classe).

Idealmente, si eliminerebbe l'intero sistema di database. Vale a dire, eseguiresti il ​​tuo metodo in un ambiente falso e assicurati solo che chiami le API DB corrette nell'ordine corretto. In modo esplicito, positivo, non si desidera testare il DB durante il test di uno dei propri metodi.

I benefici sono molti. Soprattutto, i test diventano rapidamente accecanti perché non è necessario preoccuparsi di impostare un ambiente DB corretto e di ripristinarlo in seguito.

Naturalmente, questo è un obiettivo elevato in qualsiasi progetto software che non lo fa fin dall'inizio. Ma è lo spirito del test unitario .

Si noti che ci sono altri test, come test di funzionalità, test di comportamento, ecc. Che sono diversi da questo approccio - non confondere "test" con "unit test".


"assicurati solo che chiami le API DB corrette nell'ordine corretto" - sì, certo. Fino a quando non avrai scoperto di aver letto male la documentazione e che l'ordine corretto che avresti dovuto utilizzare è completamente diverso. Non deridere i sistemi al di fuori del tuo controllo.
Joker_vD

4
@Joker_vD Ecco a cosa servono i test di integrazione. Nei test unitari, i sistemi esterni dovrebbero assolutamente essere derisi.
Ben Aaronson,

1

Esiste almeno un motivo molto importante per non utilizzare l'accesso diretto al database nei test unitari per il codice aziendale che interagisce con il database: se si modifica l'implementazione del database, è necessario riscrivere tutti questi test unitari. Rinomina una colonna, per il tuo codice devi solo cambiare una singola riga nella definizione del tuo mappatore di dati. Se non usi il tuo mappatore di dati durante il test, tuttavia, scoprirai che devi anche modificare ogni singolo test unitario che fa riferimento a questa colonna . Ciò potrebbe trasformarsi in una straordinaria quantità di lavoro, in particolare per modifiche più complesse che sono meno suscettibili di ricerca e sostituzione.

Inoltre, l'utilizzo del mapper dei dati come meccanismo astratto piuttosto che parlare direttamente al database semplifica la rimozione completa della dipendenza dal database , che ora potrebbe non essere rilevante, ma quando si ottengono fino a migliaia di unit test e tutti loro stanno colpendo un database, ti saremo grati di poterli semplicemente riformattare per rimuovere quella dipendenza perché la tua suite di test passerà da pochi minuti a pochi secondi, il che può avere un enorme vantaggio sulla tua produttività.

La tua domanda indica che stai pensando sulla retta linea. Come sospetti, anche i test unitari sono un codice. È altrettanto importante renderli mantenibili e facili da adattare ai cambiamenti futuri come per il resto della tua base di codice. Cerca di renderli leggibili ed eliminare i duplicati e avrai una suite di test molto migliore per questo. E una buona suite di test è quella che viene utilizzata. E una suite di test che viene utilizzata aiuta a trovare errori, mentre quella che non viene utilizzata è semplicemente inutile.


1

La migliore lezione che ho imparato apprendendo sui test unitari e sui test di integrazione non è quella di testare i metodi, ma testare i comportamenti. In altre parole, cosa fa questo oggetto?

Quando lo guardo in questo modo, un metodo che persiste i dati e un altro metodo che li legge indietro iniziano a essere testabili. Se stai testando i metodi in modo specifico, ovviamente, finisci con un test per ciascun metodo in isolamento come:

@Test
public void canSaveData() {
    writeDataToDatabase();
    // what can you assert - the only expectation you can have here is that an exception was not thrown.
}

@Test
public void canReadData() {
    // how do I even get data in there to read if I cannot call the method which writes it?
}

Questo problema si verifica a causa della prospettiva dei metodi di prova. Non testare i metodi. Comportamenti di test. Qual è il comportamento della classe WidgetDao? Persiste i widget. Ok, come si verifica che persista i widget? Bene, qual è la definizione di persistenza? Significa che quando lo scrivi, puoi rileggerlo di nuovo. Quindi leggi + scrivi insieme diventa un test e, secondo me, un test più significativo.

@Test
public void widgetsCanBeStored() {
    Widget widget = new Widget();
    widget.setXXX.....
    // blah

    widgetDao.storeWidget(widget);
    Widget stored = widgetDao.getWidget(widget.getWidgetId());
    assertEquals(widget, stored);
}

Questo è un test logico, coerente, affidabile e secondo me significativo.

Le altre risposte focalizzano l'importanza dell'isolamento, il dibattito pragmatico contro realistico e se un test unitario può o meno interrogare un database. Questi però non rispondono davvero alla domanda.

Per verificare se è possibile archiviare qualcosa, è necessario memorizzarlo e rileggerlo. Non è possibile verificare se qualcosa è memorizzato se non è consentito leggerlo. Non testare la memorizzazione dei dati separatamente dal recupero dei dati. Finirai con dei test che non ti dicono nulla.


Approccio molto perspicace e, come dici tu, penso che tu abbia davvero colto uno dei dubbi fondamentali che avevo. Grazie!
carlossierra,

0

Un esempio di un caso di errore che dovresti prefiggere di catturare è che l'oggetto sottoposto a test utilizza un livello di memorizzazione nella cache ma non riesce a conservare i dati come richiesto. Quindi, se si esegue una query sull'oggetto, viene visualizzato il messaggio "Sì, ho il nuovo nome e indirizzo", ma si desidera che il test fallisca perché non ha effettivamente fatto ciò che doveva.

In alternativa (e trascurando la violazione della responsabilità singola), supponiamo che sia necessario mantenere una versione della stringa con codifica UTF-8 in un campo orientato al byte, ma in realtà persiste Shift JIS. Qualche altro componente leggerà il database e si aspetta di vedere UTF-8, da cui il requisito. Quindi il viaggio di andata e ritorno attraverso questo oggetto riporterà il nome e l'indirizzo corretti perché lo convertirà nuovamente da Shift JIS, ma l'errore non viene rilevato dal test. Si spera che venga rilevato da alcuni test di integrazione successivi, ma il punto centrale dei test unitari è quello di individuare i problemi in anticipo e sapere esattamente quale componente è responsabile.

Se uno di loro non sta facendo quello che dovrebbe, allora il suo caso di test fallirà e possiamo ripararlo ed eseguire nuovamente la batteria di prova.

Non puoi assumerlo, perché se non stai attento scrivi una serie di test reciprocamente dipendenti. Il "salva?" test chiama il metodo di salvataggio che sta testando, quindi il metodo di caricamento per confermarlo salvato. Il "carica?" test chiama il metodo save per impostare l'apparecchiatura di test e quindi il metodo di caricamento che sta testando per verificare il risultato. Entrambi i test si basano sulla correttezza del metodo che non stanno testando, il che significa che nessuno dei due verifica effettivamente la correttezza del metodo che sta testando.

L'indizio che c'è un problema qui è che due test che presumibilmente stanno testando unità diverse fanno effettivamente la stessa cosa . Entrambi chiamano un setter seguito da un getter, quindi controllano che il risultato sia il valore originale. Ma hai voluto verificare che il setter persista i dati, non che la coppia setter / getter funzioni insieme. Quindi sai che qualcosa non va, devi solo capire cosa e correggere i test.

Se il tuo codice è ben progettato per i test unitari, ci sono almeno due modi in cui puoi testare se i dati sono stati davvero correttamente persi con il metodo sotto test:

  • deride l'interfaccia del database e fa in modo che il tuo mock registri il fatto che sono state chiamate su di esso le funzioni appropriate, con i valori previsti. Questo test il metodo fa quello che dovrebbe, ed è il classico test unitario.

  • passarlo a un database effettivo con esattamente la stessa intenzione , per registrare se i dati sono stati correttamente conservati o meno. Ma invece di avere una funzione derisa che dice semplicemente "sì, ho i dati giusti", il tuo test legge direttamente dal database e conferma che è corretto. Questo potrebbe non essere il test più puro possibile, perché un intero motore di database è una cosa abbastanza grande da usare per scrivere una derisione glorificata, con più possibilità che io trascuri qualche sottigliezza che fa passare un test anche se qualcosa non va (quindi ad esempio non dovrei usare la stessa connessione al database per leggere come è stato usato per scrivere, perché potrei vedere una transazione senza commit). Ma mette alla prova la cosa giusta, e almeno lo sai esattamente implementa l'intera interfaccia del database senza dover scrivere alcun codice fittizio!

Quindi è solo un dettaglio dell'implementazione del test se leggo i dati dal database di test da JDBC o se derido il database. In ogni caso, il punto è che posso testare meglio l'unità isolandola che posso se permetto che cospiri con altri metodi errati sulla stessa classe per apparire bene anche quando qualcosa non va. Pertanto, desidero utilizzare qualsiasi mezzo conveniente per verificare che siano stati mantenuti i dati corretti, oltre a fidarmi del componente di cui sto testando il metodo.

Se il tuo codice non è ben progettato per il unit testing, allora potresti non avere scelta, poiché l'oggetto il cui metodo vuoi testare potrebbe non accettare il database come dipendenza iniettata. Nel qual caso la discussione sul modo migliore per isolare l'unità sotto test, si trasforma in una discussione su quanto è possibile arrivare a isolare l'unità sotto test. La conclusione è la stessa, però. Se riesci ad evitare cospirazioni tra unità difettose, lo fai, in base al tempo disponibile e a qualsiasi altra cosa tu pensi che sarebbe più efficace nel trovare guasti nel codice.


0

È così che mi piace farlo. Questa è solo una scelta personale poiché non influirà sul risultato del prodotto, ma solo sul modo di produrlo.

Ciò dipende dalle possibilità di poter aggiungere valori fittizi nel database senza l'applicazione che si sta testando.

Supponiamo che tu abbia due test: A - lettura del database B - inserimento nel database (dipende da A)

Se A passa, allora sei sicuro che il risultato di B dipenderà dalla parte testata in B e non dalle dipendenze. Se A fallisce, puoi avere un falso negativo per B. L'errore potrebbe essere in B o in A. Non puoi esserne sicuro fino a quando A non restituisce un successo.

Non proverei a scrivere alcune query in un test di unità poiché non dovresti essere in grado di conoscere la struttura del DB dietro l'applicazione (o potrebbe cambiare). Ciò significa che il test case potrebbe non riuscire a causa del codice stesso. A meno che non si scriva un test per il test, ma chi testerà il test per il test ...


-1

Alla fine, si riduce a ciò che si desidera testare.

A volte è sufficiente testare l'interfaccia fornita della classe (ad es. Il record viene creato e archiviato e può essere recuperato in un secondo momento, magari dopo un "riavvio"). In questo caso è bene (e di solito una buona idea) usare i metodi forniti per i test. Ciò consente di riutilizzare i test, ad esempio se si desidera modificare il meccanismo di archiviazione (database diverso, database in memoria per il test, ...).

Nota che questo significa che ti interessa davvero solo che la classe stia rispettando il suo contratto (creando, archiviando e recuperando i record in questo caso), non come lo fa. Se si creano i test unitari in modo intelligente (su un'interfaccia, non direttamente su una classe), è possibile testare diverse implementazioni, poiché devono anche aderire al contratto dell'interfaccia.

D'altra parte, in alcuni casi è necessario verificare effettivamente come vengono mantenute le cose (forse qualche altro processo si basa sul fatto che i dati siano nel formato giusto in quel database?). Ora non puoi semplicemente fare affidamento sul recupero del record corretto dalla classe, invece devi verificare che sia correttamente archiviato nel database. E il modo più diretto per farlo è interrogare il database stesso.

Ora non ti importa solo della classe che aderisce al suo contratto (crea, archivia e recupera), ma anche di come raggiunge questo obiettivo (dato che i dati persistenti devono aderire a un'altra interfaccia). Tuttavia, ora i test dipendono sia dall'interfaccia di classe che dal formato di archiviazione, quindi sarà difficile riutilizzarli completamente. (Alcuni potrebbero chiamare questo tipo di test "test di integrazione".)


@downvoters: potresti dirmi le tue ragioni in modo che io possa migliorare gli aspetti che ritieni manchino la mia risposta?
hoffmale,
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.