Vale la pena scrivere unit test per i codici di ricerca scientifica?


89

Sono fermamente convinto del valore dell'utilizzo di test che verificano un programma completo (ad esempio test di convergenza), compreso un set automatizzato di test di regressione . Dopo aver letto alcuni libri di programmazione, ho avuto la fastidiosa sensazione di "dover" scrivere unit test (ovvero test che verificano la correttezza di una singola funzione e non equivalgono a eseguire l'intero codice per risolvere un problema) . Tuttavia, i test unitari non sembrano sempre adattarsi ai codici scientifici e finiscono per sembrare artificiali o come una perdita di tempo.

Dovremmo scrivere unit test per i codici di ricerca?


2
Questa è una domanda un po 'aperta, vero?
qubyte

2
Come per tutte le "regole", si applica sempre una dose di pensiero critico. Chiediti se una determinata routine ha un modo ovvio di essere testata dall'unità. Altrimenti, o un test unitario non ha senso a quel punto, o il design del codice era scadente. Idealmente, una routine esegue un'attività il più indipendente possibile dalle altre routine, ma deve essere scambiata occasionalmente.
Lagerbaer,

C'è una buona discussione in una prospettiva simile su una domanda su StackOverflow .
naught101

Risposte:


85

Per molti anni ho avuto il malinteso di non avere abbastanza tempo per scrivere test unitari per il mio codice. Quando scrivevo i test, erano cose gonfie e pesanti che mi incoraggiavano solo a pensare che avrei dovuto scrivere test unitari solo quando sapevo che erano necessari.

Quindi ho iniziato a utilizzare Test Driven Development e l'ho trovato una rivelazione completa. Ora sono fermamente convinto di non avere il tempo di non scrivere unit test .

Nella mia esperienza, sviluppando pensando ai test, si ottengono interfacce più pulite, classi e moduli più mirati e in genere più codice SOLID , testabile.

Ogni volta che lavoro con un codice legacy che non ha test unitari e devo testare manualmente qualcosa, continuo a pensare "questo sarebbe molto più veloce se questo codice avesse già dei test unitari". Ogni volta che devo provare ad aggiungere funzionalità di unit test al codice con elevato accoppiamento, continuo a pensare "sarebbe molto più facile se fosse stato scritto in modo disaccoppiato".

Confronto e contrasto delle due stazioni sperimentali che io sostengo. Uno è in circolazione da un po 'e ha una grande quantità di codice legacy, mentre l'altro è relativamente nuovo.

Quando si aggiunge funzionalità al vecchio laboratorio, spesso si tratta di scendere in laboratorio e passare molte ore a lavorare sulle implicazioni della funzionalità di cui hanno bisogno e su come posso aggiungere quella funzionalità senza influire su nessuna delle altre funzionalità. Il codice semplicemente non è impostato per consentire i test off-line, quindi praticamente tutto deve essere sviluppato on-line. Se provassi a sviluppare off-line, finirei con più oggetti finti di quanto sarebbe ragionevole.

Nel laboratorio più recente, di solito posso aggiungere funzionalità sviluppandola off-line sulla mia scrivania, prendendo in giro solo le cose che sono immediatamente necessarie e quindi trascorrendo solo un breve periodo in laboratorio, risolvendo eventuali problemi rimanenti non risolti -linea.

Per chiarezza, e poiché @ naught101 ha chiesto ...

Tendo a lavorare su software di controllo sperimentale e acquisizione dati, con alcune analisi dei dati ad hoc, quindi la combinazione di TDD con controllo di revisione aiuta a documentare sia i cambiamenti nell'hardware dell'esperimento sottostante sia i cambiamenti nei requisiti di raccolta dei dati nel tempo.

Tuttavia, anche nella situazione di sviluppo del codice esplorativo, ho potuto vedere un vantaggio significativo dall'avere codificato i presupposti, insieme alla capacità di vedere come tali presupposti si evolvono nel tempo.


7
Mark, di che tipo di codice stai parlando qui? Modello riutilizzabile? Trovo che questa logica non sia così applicabile a cose come il codice di analisi dei dati esplorativi, in cui è davvero necessario saltare molto, e spesso non ci si aspetta mai di riutilizzare il codice altrove.
naught101

35

I codici scientifici tendono ad avere costellazioni di funzioni di interblocco più spesso dei codici aziendali su cui ho lavorato, di solito a causa della struttura matematica del problema. Quindi, non credo che i test unitari per le singole funzioni siano molto efficaci. Tuttavia, penso che ci sia una classe di unit test che sono efficaci e sono ancora abbastanza diversi dai test di tutto il programma in quanto mirano a funzionalità specifiche.

Definisco brevemente cosa intendo per questo tipo di test. Il test di regressione cerca modifiche nel comportamento esistente (convalidato in qualche modo) quando vengono apportate modifiche al codice. Il test unitario esegue un codice e verifica che fornisca l'output desiderato in base a una specifica. Non sono così diversi, poiché il test di regressione originale era un test unitario poiché ho dovuto determinare che l'output era valido.

Il mio esempio preferito di test unitario numerico è il test del tasso di convergenza di un'implementazione ad elementi finiti. Non è sicuramente semplice, ma richiede una soluzione nota a un PDE, presenta diversi problemi nel ridurre la dimensione della mesh , quindi adatta la norma di errore alla curva C h r dove rhChrr è il tasso di convergenza. Faccio questo per il problema di Poisson in PETSc usando Python. Non sto cercando una differenza, come nella regressione, ma in particolare un rate specificato per l'elemento dato.r

Altri due esempi di test unitari, provenienti da PyLith , sono la posizione del punto, che è una singola funzione per la quale è facile produrre risultati sintetici e la creazione di celle coesive a volume zero in una mesh, che coinvolge diverse funzioni ma si rivolge a un pezzo circoscritto di funzionalità nel codice.

Esistono molti test di questo tipo, inclusi i test di conservazione e coerenza. L'operazione non è così diversa dalla regressione (si esegue un test e si controlla l'output rispetto a uno standard), ma l'output standard proviene da una specifica piuttosto che da una precedente esecuzione.


4
Wikipedia dice "Test unitari, noto anche come test dei componenti, si riferisce a test che verificano la funzionalità di una specifica sezione di codice, generalmente a livello di funzione". I test di convergenza in un codice ad elementi finiti chiaramente non possono essere test unitari in quanto implicano molte funzioni.
David Ketcheson,

Questo è il motivo per cui ho chiarito in cima al post che prendo l'ampia visione dei test unitari e "di solito" significa esattamente questo.
Matt Knepley,

La mia domanda era intesa nel senso della definizione più ampiamente accettata di unit test. Ora l'ho reso completamente esplicito nella domanda.
David Ketcheson,

Ho chiarito la mia risposta
Matt Knepley il

I tuoi ultimi esempi sono rilevanti per ciò che intendevo.
David Ketcheson,

28

Da quando ho letto dello sviluppo test-driven in Code Complete, 2a edizione , ho usato un framework di unit testcome parte della mia strategia di sviluppo, e ha notevolmente aumentato la mia produttività riducendo la quantità di tempo che ho trascorso nel debug perché i vari test che scrivo sono diagnostici. Come vantaggio collaterale, sono molto più fiducioso nei miei risultati scientifici e ho usato i miei test unitari in diverse occasioni per difendere i miei risultati. Se si verifica un errore in un test unitario, di solito riesco a capire perché abbastanza rapidamente. Se la mia applicazione si arresta in modo anomalo e tutti i test delle unità superano, eseguo un'analisi della copertura del codice per vedere quali parti del mio codice non vengono esercitate, oltre a scorrere il codice con un debugger per individuare l'origine dell'errore. Quindi scrivo un nuovo test per assicurarmi che il bug rimanga corretto.

Molti dei test che scrivo non sono test unitari puri. Rigorosamente definiti, i test unitari dovrebbero esercitare la funzionalità di una funzione. Quando riesco a testare facilmente una singola funzione usando dati fittizi, lo faccio. Altre volte, non riesco facilmente a deridere i dati di cui ho bisogno per scrivere un test che eserciti la funzionalità di una determinata funzione, quindi testerò quella funzione insieme ad altre in un test di integrazione. Test di integrazionetestare il comportamento di più funzioni contemporaneamente. Come sottolinea Matt, i codici scientifici sono spesso una costellazione di funzioni di interblocco, ma spesso volte, determinate funzioni vengono chiamate in sequenza e è possibile scrivere test unitari per testare l'output a passi intermedi. Ad esempio, se il mio codice di produzione chiama in sequenza cinque funzioni, scriverò cinque test. Il primo test chiamerà solo la prima funzione (quindi è un test unitario). Quindi il secondo test chiamerà la prima e la seconda funzione, il terzo test chiamerà le prime tre funzioni e così via. Anche se potessi scrivere test unitari per ogni singola funzione nel mio codice, scriverei comunque test di integrazione, perché i bug possono insorgere quando si combinano vari elementi modulari di un programma. Alla fine, dopo aver scritto tutti i test unitari e i test di integrazione penso di aver bisogno, Avvolgerò i miei casi studio nei test unitari e li userò per i test di regressione, perché voglio che i miei risultati siano ripetibili. Se non sono ripetibili e ottengo risultati diversi, voglio sapere perché. Il fallimento di un test di regressione potrebbe non essere un vero problema, ma mi costringerà a capire se i nuovi risultati sono affidabili almeno quanto quelli precedenti.

Vale anche la pena unitamente al test unitario sono l'analisi del codice statico, i debugger di memoria e la compilazione con flag di avviso del compilatore per rilevare semplici errori e codice inutilizzato.



Considereresti abbastanza i test di integrazione o pensi di dover anche scrivere unit test separati?
siamii,

Scriverei unit test separati ovunque sia possibile e fattibile farlo. Semplifica il debug e applica il codice disaccoppiato (che è quello che vuoi).
Geoff Oxberry,

19

Nella mia esperienza, all'aumentare della complessità dei codici di ricerca scientifica, è necessario avere un approccio molto modulare nella programmazione. Questo può essere doloroso per i codici con una base grande e antica ( f77chiunque?) Ma è necessario andare avanti. Man mano che un modulo viene costruito attorno a un aspetto specifico del codice (per le applicazioni CFD, pensate a Condizioni al contorno o Termodinamica), i test unitari sono molto preziosi per convalidare la nuova implementazione e isolare i problemi e ulteriori sviluppi del software.

Questi test unitari dovrebbero essere un livello al di sotto della verifica del codice (posso recuperare la soluzione analitica della mia equazione d'onda?) E 2 livelli al di sotto della convalida del codice (posso prevedere i valori corretti di picco RMS nel mio flusso turbolento di tubi), semplicemente assicurando che la programmazione (gli argomenti sono passati correttamente, i puntatori indicano la cosa giusta?) e la "matematica" (questa subroutine calcola il coefficiente di attrito. Se inserisco un insieme di numeri e computo la soluzione a mano, la routine produce lo stesso risultato?) sono corretti. Fondamentalmente andare di un livello sopra ciò che i compilatori possono individuare, ovvero errori di sintassi di base.

Lo consiglierei sicuramente per almeno alcuni moduli cruciali nella tua applicazione. Tuttavia, bisogna rendersi conto che è estremamente noioso e richiede molto tempo, quindi a meno che tu non abbia una forza di lavoro illimitata, non lo consiglierei per il 100% di un codice complesso.


Hai qualche esempio o criterio specifico per scegliere quali pezzi testare (e quali no)?
David Ketcheson,

@DavidKetcheson La mia esperienza è limitata dall'applicazione e dalla lingua che utilizziamo. Quindi, per il nostro codice CFD per uso generale con circa 200k linee per lo più F90, abbiamo cercato negli ultimi due anni di isolare davvero alcune funzionalità del codice. Creare un modulo e utilizzarlo in tutto il codice non sta raggiungendo questo obiettivo, quindi è necessario confrontare veramente questi moduli e renderli praticamente delle librerie. Quindi solo pochissime istruzioni USE e tutte le connessioni con il resto del codice vengono eseguite tramite chiamate di routine. Routine che puoi disfare naturalmente, così come il resto della biblioteca.
FrenchKheldar,

@DavidKetcheson Come ho detto nella mia risposta, le condizioni al contorno e la termodinamica sono stati 2 aspetti del nostro codice che siamo riusciti a isolare davvero e così poco impegnativi avevano senso. In un modo più generale, inizierei con qualcosa di piccolo e proverei a farlo in modo pulito. Idealmente questo è un lavoro per 2 persone. Una persona scrive le routine e la documentazione che descrive l'interfaccia, un'altra dovrebbe scrivere il test unitario, idealmente senza guardare il codice sorgente e andare solo dalla descrizione dell'interfaccia. In questo modo viene testato l'intento della routine ma mi rendo conto che non è una cosa facile da organizzare.
FrenchKheldar,

1
Perché non includere altri tipi di test del software (integrazione, sistema) oltre ai test unitari? Oltre a tempi e costi, questa non sarebbe la soluzione tecnica più completa? I miei riferimenti sono 1 (Sec 3.4.2) e 2 (pagina 5). In altre parole, il codice sorgente non dovrebbe essere testato dai tradizionali livelli di test del software 3 ("Test dei livelli")?
ximiki,

14

I test unitari per i codici scientifici sono utili per una serie di motivi.

Tre in particolare sono:

  • I test unitari aiutano altre persone a comprendere i vincoli del tuo codice. Fondamentalmente, i test unitari sono una forma di documentazione.

  • I test unitari verificano che una singola unità di codice restituisca risultati corretti e che il comportamento di un programma non cambi quando i dettagli vengono modificati.

  • L'uso dei test unitari semplifica la modularizzazione dei codici di ricerca. Questo può essere particolarmente importante se inizi a provare a indirizzare il tuo codice su una nuova piattaforma, ad esempio sei interessato a parallelizzarlo o eseguirlo su un computer GPGPU.

Soprattutto, i test unitari ti danno la certezza che i risultati della ricerca che stai producendo usando i tuoi codici sono validi e verificabili.

Noto che nella tua domanda menzioni il test di regressione. In molti casi, i test di regressione vengono eseguiti attraverso l'esecuzione automatica e regolare di unit test e / o test di integrazione (che testano che pezzi di codice funzionano correttamente quando combinati; nel calcolo scientifico, questo viene spesso fatto confrontando l'output con i dati sperimentali o il risultati di programmi precedenti affidabili). Sembra che tu stia già usando con successo test di integrazione o unit test a livello di componenti complessi di grandi dimensioni.

Quello che vorrei dire è che man mano che i codici di ricerca diventano sempre più complessi e si basano sul codice e sulle librerie di altre persone, è importante capire dove si verifica l'errore quando si verifica. Il test unitario consente all'errore di essere individuato molto più facilmente.

È possibile trovare la descrizione, le prove e i riferimenti nella sezione 7 "Pianificare gli errori" dell'articolo che ho scritto su Best Practices for Scienti fi c Computing utile - introduce anche il concetto complementare di programmazione difensiva.


9

Nelle mie classi deal.II insegno che il software che non ha i test non funziona correttamente (e andare a stress che ho volutamente detto " non non funziona correttamente", non " potrebbe non funzionare correttamente).

Certo che vivo dal mantra - che è come affare.II è arrivato a eseguire 2.500 test con ogni commit ;-)

Più seriamente, penso che Matt definisca già bene le due classi di test. Scriviamo unit test per roba di livello inferiore e in qualche modo progredisce naturalmente verso test di regressione per roba di livello superiore. Non penso che potrei tracciare un chiaro confine che separerebbe i nostri test da una parte o dall'altra, ce ne sono certamente molti che percorrono la linea in cui qualcuno ha guardato l'output e lo ha trovato ampiamente ragionevole (unit test?) senza averlo guardato fino all'ultima precisione (test di regressione?).


Perché proponi questa gerarchia (unità per inferiore, regressione per superiore) rispetto ai livelli tradizionali nei test software?
ximiki,

@ximiki - Non intendo. Sto dicendo che i test esistono su uno spettro che includerebbe tutte le categorie elencate nel tuo link.
Wolfgang Bangerth,

8

Sì e no. Sicuramente unittest per le routine fondamentali del set di strumenti di base che usi per semplificarti la vita, come routine di conversione, mappature di stringhe, fisica e matematica di base, ecc. Quando si tratta di classi o funzioni di calcolo, possono generalmente richiedere tempi di esecuzione lunghi e in realtà potresti preferire testarli come test funzionali, anziché come unità. Inoltre, unittest e stressano molto quelle classi ed entità il cui livello e utilizzo cambieranno molto (ad es. Per scopi di ottimizzazione) o i cui dettagli interni verranno cambiati per qualsiasi motivo. L'esempio più tipico è una classe che avvolge una matrice enorme, mappata dal disco.


7

Assolutamente!

Cosa, non è abbastanza per te?

Nella programmazione scientifica più di ogni altro tipo, stiamo sviluppando sulla base del tentativo di abbinare un sistema fisico. Come fai a sapere se lo hai fatto oltre a testare? Prima ancora di iniziare a scrivere codice, decidi come usare il codice e calcola alcune esecuzioni di esempio. Prova a catturare eventuali casi limite. Fallo in modo modulare - ad esempio, per una rete neurale potresti fare una serie di test per un singolo neurone e una serie di test per una rete neurale completa. In questo modo quando inizi a scrivere codice puoi assicurarti che il tuo neurone funzioni prima di iniziare a lavorare sulla rete. Lavorare in fasi come questa significa che quando si verifica un problema si ha solo la 'fase' di codice più recente da testare, le fasi precedenti sono già state testate.

Inoltre, una volta che hai i test, se hai bisogno di riscrivere il codice in una lingua diversa (convertendolo in CUDA, per esempio), o anche se lo stai semplicemente aggiornando, hai già i testcase e puoi usarli per creare assicurati che entrambe le versioni del tuo programma funzionino allo stesso modo.


+1: "Lavorare in fasi come questa significa che quando si verifica un problema si ha solo la 'fase' di codice più recente da testare, le fasi precedenti sono già state testate."
ximiki,

5

Sì.

L'idea che qualsiasi codice sia scritto senza unit test è un anatema. A meno che tu non provi il tuo codice corretto e poi dimostri che la prova è corretta = P.


3
... e poi dimostri che la prova che la prova è corretta, e ... ora quella è una profonda tana di coniglio.
JM il

2
Le tartarughe fino in fondo rendono orgoglioso Dijkstra!
Aterrel

2
Risolvi il caso generale e poi fai in modo che le tue prove si dimostrino corrette! Torus di tartarughe!
Aesin,

5

Affronterei questa domanda in modo pragmatico piuttosto che dogmatico. Ponetevi la domanda: "Cosa potrebbe andare storto nella funzione X?" Immagina cosa succede all'output quando introduci alcuni bug tipici nel codice: un prefattore sbagliato, un indice sbagliato, ... E poi scrivi test unit che potrebbero rilevare quel tipo di bug. Se per una determinata funzione non c'è modo di scrivere tali test senza ripetere il codice della funzione stessa, allora non farlo - ma pensa ai test al prossimo livello superiore.

Un problema molto più importante con i test unitari (o di fatto con qualsiasi test) nel codice scientifico è come affrontare le incertezze dell'aritmetica in virgola mobile. Per quanto ne so non ci sono ancora buone soluzioni generali.


Sia che testiate manualmente o automaticamente usando i test unitari, avete esattamente gli stessi problemi con la rappresentazione in virgola mobile. Mi raccomando Richard Harris' eccellente serie di articoli in ACCU 's rivista di sovraccarico .
Mark Booth,

"Se per una determinata funzione non c'è modo di scrivere tali test senza ripetere il codice della funzione stessa, allora non farlo". Puoi elaborare? Un esempio mi chiarirebbe questo.
ximiki,

5

Mi dispiace per Tangurena - da queste parti, il mantra è "Il codice non testato è un codice non funzionante" e proviene dal capo. Piuttosto che ripetere tutti i buoni motivi per fare unit test, voglio solo aggiungere alcuni dettagli.

  • L'utilizzo della memoria deve essere testato. Ogni funzione che alloca memoria dovrebbe essere testata assicurandosi che le funzioni che memorizzano e recuperano i dati in quella memoria stiano facendo la cosa giusta. Questo è ancora più importante nel mondo della GPU.
  • Sebbene brevemente menzionato prima, testare casi limite è estremamente importante. Pensa a questi test nello stesso modo in cui testi il ​​risultato di qualsiasi calcolo. Assicurati che il codice si comporti ai margini e fallisca con garbo (comunque lo definisci nella tua simulazione) quando parametri di input o dati non rientrano nei limiti accettabili. Il pensiero coinvolto nello scrivere questo tipo di test aiuta ad affinare il tuo lavoro e può essere uno dei motivi per cui raramente trovi qualcuno che ha scritto unit test ma non trova utile il processo.
  • Usa un framework di test (come menzionato da Geoff che ha fornito un bel link). Ho usato il framework di test BOOST insieme al sistema CTest di CMake e posso raccomandarlo come un modo semplice per scrivere rapidamente unit test (così come test di validazione e regressione).

+1: "Assicurati che il codice si comporti ai margini e fallisca con garbo (comunque lo definisci nella tua simulazione) quando parametri di input o dati non rientrano nei limiti accettabili."
ximiki,

5

Ho usato i test unitari per ottenere buoni risultati su diversi codici su piccola scala (ad esempio un singolo programmatore), inclusa la terza versione del mio codice di analisi della tesi di laurea in fisica delle particelle.

Le prime due versioni erano crollate sotto il loro stesso peso e la moltiplicazione delle interconnessioni.

Altri hanno scritto che l'interazione tra i moduli è spesso il luogo in cui si interrompe la codifica scientifica e hanno ragione. Ma è molto più facile diagnosticare quei problemi quando si può dimostrare in modo conclusivo che ogni modulo sta facendo quello che dovrebbe fare.


3

Un approccio leggermente diverso che ho usato durante lo sviluppo di un risolutore chimico (per domini geologici complessi) era quello che potevi chiamare Unit Testing di Copy and Paste Snippet .

La costruzione di un cablaggio di prova per il codice originale incorporato in un grande modellatore di sistemi chimici non era fattibile nel tempo.

Tuttavia, sono stato in grado di elaborare una serie sempre più complessa di frammenti che mostrano come funzionava il parser (Boost Spirit) per le formule chimiche, come test unitari per espressioni diverse.

Il test unitario finale, il più complesso, è stato molto vicino al codice necessario nel sistema, senza dover modificare quel codice per renderlo beffardo. Sono stato quindi in grado di copiare il codice testato sulla mia unità.

Ciò che rende questo più di un semplice esercizio di apprendimento e una vera suite di regressione sono due fattori: i test unitari mantenuti nella fonte principale ed eseguiti come parte di altri test per quell'applicazione (e sì, hanno ottenuto un effetto collaterale da Boost Spirito che cambia 2 anni dopo) - poiché il codice copiato e incollato è stato minimamente modificato nella vera applicazione, potrebbe avere commenti che rimandano ai test unitari per aiutare qualcuno a mantenerli sincronizzati.


2

Per basi di codice più grandi, i test (non necessariamente unit test) per le cose di alto livello sono utili. I test unitari per alcuni algoritmi più semplici sono utili anche per assicurarsi che il tuo codice non stia dando senso perché la tua funzione di supporto sta usando sininvece di cos.

Ma per il codice di ricerca complessivo è molto difficile scrivere e mantenere i test. Gli algoritmi tendono ad essere grandi senza risultati intermedi significativi che possono avere ovvi test e spesso impiegano molto tempo per essere eseguiti prima che ci sia un risultato. Ovviamente puoi testare su esecuzioni di riferimento che hanno avuto buoni risultati, ma questo non è un buon test nel senso del test unitario.

I risultati sono spesso approssimazioni della vera soluzione. Mentre puoi testare le tue semplici funzioni se sono accurate fino ad alcuni epsilon, sarà molto difficile verificare se alcune mesh dei risultati sono corrette o meno, che sono state valutate da un'ispezione visiva da parte dell'utente (tu) in precedenza.

In tali casi, i test automatici hanno spesso un rapporto costi / benefici troppo elevato. Consiglio qualcosa di meglio: scrivere programmi di prova. Ad esempio, ho scritto uno script Python di medie dimensioni per creare dati sui risultati, come istogrammi di dimensioni dei bordi e angoli di una mesh, area del triangolo più grande e più piccolo e il loro rapporto, ecc.

Posso usarlo per valutare le mesh di input e output durante il normale funzionamento e utilizzarlo per un controllo di integrità dopo aver modificato l'algoritmo. Quando cambio l'algoritmo non sempre so se il nuovo risultato è migliore, perché spesso non esiste una misura assoluta quale approssimazione sia la migliore. Ma generando tali metriche posso dire su alcuni fattori cosa è meglio come "La nuova variante alla fine ha un rapporto angolare migliore ma un tasso di convergenza peggiore".

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.