Testarea codului intensiv de date cu codul Go, partea 5

Prezentare generală

Aceasta este o parte din cinci din cinci într-o serie de tutori privind testarea codului intensiv de date. În partea a patra, am acoperit stocările de date la distanță, utilizând baze de date comune de testare, utilizând instantanee de date de producție și generând propriile date de testare. În acest tutorial, voi trece peste testul fuzz, testarea cache-ului, testarea integrității datelor, testarea idempotency și lipsa datelor.

Fuzz Testing

Ideea testelor de tip fuzz este de a copleși sistemul cu o mulțime de intrări aleatorii. În loc să încercați să vă gândiți la contribuția care va acoperi toate cazurile, care pot fi dificile și / sau foarte solicitante de muncă, ați lăsat șansa să o faceți pentru dvs. Este conceptual similar cu generarea aleatoare a datelor, dar intenția este aici de a genera intrări aleatorii sau semi-aleatoare în loc de date persistente.

Când Testul Fuzz este util?

Analiza Fuzz este utilă în special pentru a găsi probleme de securitate și de performanță atunci când intrările neașteptate provoacă accidente sau scurgeri de memorie. Dar, de asemenea, se poate asigura că toate intrările nevalide sunt detectate devreme și sunt respinse în mod corespunzător de sistem.

Luați în considerare, de exemplu, o intrare care vine sub forma unor documente JSON adânc imbricate (foarte frecvente în API-urile web). Încercarea de a genera manual o listă cuprinzătoare de cazuri de testare este atât de predispusă la erori și o mulțime de muncă. Dar testarea fuzz este tehnica perfectă.

Folosind Testarea Fuzz 

Există mai multe biblioteci pe care le puteți utiliza pentru testarea fuzz. Preferatul meu este gofuzz ​​de la Google. Iată un exemplu simplu care generează automat 200 de obiecte unice ale unui struct cu mai multe câmpuri, inclusiv un struct imbricat.  

(fmt "" github.com/google/gofuzz ​​") func SimpleFuzzing () tip SomeType struct A șir B șir C int D struct E float64 f: = fuzz.New ()  uniqueObjects: = map [SomeType] int  pentru i: = 0; eu < 200; i++  f.Fuzz(&object) uniqueObjects[object]++  fmt.Printf("Got %v unique objects.\n", len(uniqueObjects)) // Output: // Got 200 unique objects.  

Testarea cache-ului

Destul de mult, orice sistem complex care se ocupă de o mulțime de date are o memorie cache sau, mai probabil, mai multe nivele de cache ierarhice. După cum se spune, există doar două lucruri dificile în informatică: numirea lucrurilor, invalidarea cache-ului și oprirea de către o eroare.

Bancnotele deoparte, gestionarea strategiei dvs. de cache și a implementării acestora pot complica accesul la date, dar au un impact extraordinar asupra costului și performanței accesului la date. Testarea cache-ului nu se poate face din afară, deoarece interfața dvs. se ascunde de unde provin datele și mecanismul de cache este un detaliu al implementării.

Să vedem cum să testați comportamentul cache al stratului de date hibrid Songify.

Cache Hits și Misses

Cache-urile trăiesc și mor de performanța lor de succes / dor. Funcționalitatea de bază a unei cache-uri este că, dacă datele solicitate sunt disponibile în cache (o lovitură), atunci acestea vor fi extrase din memoria cache și nu din memoria primară. În designul original al HybridDataLayer, accesul la cache a fost realizat prin metode private.

Normele privind vizibilitatea fac imposibil să le apelați direct sau să le înlocuiți dintr-un alt pachet. Pentru a permite testarea cache-ului, voi schimba aceste metode la funcțiile publice. Acest lucru este bine deoarece codul de aplicație actual funcționează prin dataLayer interfață, care nu expune aceste metode.

Cu toate acestea, codul de testare va putea să înlocuiască aceste funcții publice după cum este necesar. Mai întâi, să adăugăm o metodă pentru a avea acces la clientul Redis, astfel încât să putem manipula memoria cache:

func (m * HybridDataLayer) GetRedis () * redis.Client retur m.redis 

Îl voi schimba getSongByUser_DB () metode pentru o variabilă a funcției publice. Acum, în test, pot înlocui GetSongsByUser_DB () variabilă cu o funcție care ține evidența de câte ori a fost apelată și apoi o transmite la funcția inițială. Asta ne permite să verificăm dacă este apelat la GetSongsByUser () a preluat melodiile din memoria cache sau din DB. 

Să-l rupem în bucăți. În primul rând, obținem stratul de date (care șterge, de asemenea, DB și redis), a crea un utilizator și a adăuga o melodie. AddSong () metoda de populare, de asemenea, redis. 

func TestGetSongsByUser_Cache (t * test.T) acum: = time.Now () u: = Utilizator Nume: "Gigi", Email: "[email protected]", RegisteredAt: nowLastLogin: : = getDataLayer () dacă err! = nil t.Error ("Nu a reușit să creeze un nivel de date hibrid") err = dl.CreateUser (u)  lm, err: = NewSongManager (u, dl) în cazul în care err! = nil t.Error ("NewSongManager () returnat" nil " err = lm.AddSong (testSong, nil) .Error ("AddSong () nu a reușit") 

Aceasta este partea răcoroasă. Pastrez functia initiala si definesc o noua functie instrumentata care incrementeaza functia locala callCount variabilă (totul este închis) și sună funcția inițială. Apoi, atribuie funcția instrumentată variabilei GetSongsByUser_DB. De acum înainte, fiecare apel de la stratul de date hibrid la GetSongsByUser_DB () va merge la funcția instrumentată.     

 CallCount: = 0 originalFunc: = GetSongsByUser_DB instrumentedFunc: = func (m * HybridDataLayer, șir de e-mail, melodii * [] Song) (eroare err) callCount + = 1 return originalFunc (m, email, melodii) GetSongsByUser_DB = 

În acest moment, suntem gata să testați operația de cache. În primul rând, testul solicită GetSongsByUser () din SongManager care o transmite spre stratul de date hibrid. Cache-ul ar trebui să fie populat pentru acest utilizator pe care tocmai l-am adăugat. Deci rezultatul așteptat este că funcția noastră instrumentată nu va fi apelată, și callCount va rămâne la zero.

 _, err = lm.GetSongsByUser (u) dacă err! = nil t.Error ("GetSongsByUser ()") // Verificați că DB nu a fost accesat deoarece cache-ul ar trebui să fie // populat de AddSong > 0 t.Error ('GetSongsByUser_DB () apelat când nu ar trebui să aibă') 

Ultimul caz test este să se asigure că, dacă datele utilizatorului nu sunt în memoria cache, acestea vor fi preluate corect din DB. Testarea o realizează prin înroșirea lui Redis (ștergerea tuturor datelor sale) și efectuarea unui alt apel GetSongsByUser (). De data aceasta, funcția instrumentată va fi apelată, iar testul verifică dacă callCount este egal cu 1. În cele din urmă, originalul GetSongsByUser_DB () funcția este restabilită.

 // Eliminați memoria cache dl.GetRedis () FlushDB () // Descărcați melodiile din nou, acum trebuie să mergeți la DB // deoarece cache-ul este gol _, err = lm.GetSongsByUser (u) dacă err! = Nul t.Error ("GetSongsByUser () nu a reușit") // Verificați că DB a fost accesat deoarece memoria cache este goală dacă callCount! = 1 t.Error ('GetSongsByUser_DB ()  GetSongsByUser_DB = originalFunc

Invalidarea cache-ului

Cache-ul nostru este foarte simplu și nu face nici o invalidare. Acest lucru funcționează destul de bine, atâta timp cât toate melodiile sunt adăugate prin AddSong () care se ocupă de actualizarea Redis. Dacă adăugăm mai multe operații, cum ar fi eliminarea melodiilor sau ștergerea utilizatorilor, atunci aceste operațiuni ar trebui să aibă grijă să actualizeze în mod corespunzător Redis.

Această memorie cache foarte simplă va funcționa chiar dacă avem un sistem distribuit în care mai multe mașini independente pot rula serviciul nostru Songify - atâta timp cât toate instanțele lucrează cu aceleași instanțe DB și Redis.

Cu toate acestea, dacă DB și cache-ul pot să se sincronizeze din cauza operațiilor de întreținere sau a altor instrumente și aplicații care schimbă datele noastre, atunci trebuie să găsim o politică de invalidare și actualizare pentru memoria cache. Acesta poate fi testat folosind aceleași tehnici - înlocuiți funcțiile țintă sau accesați direct DB și Redis în testul dvs. pentru a verifica starea.

LRU Caches

De obicei, nu puteți lăsa cache-ul să crească infinit. O schemă comună pentru a păstra cele mai utile date în memoria cache este cache-urile LRU (cel mai recent utilizate). Cele mai vechi date sunt lovite din memoria cache atunci când aceasta atinge capacitatea.

Testarea implică stabilirea capacității la un număr relativ mic în timpul testului, depășirea capacității și asigurarea faptului că cele mai vechi date nu mai sunt în cache și că accesarea acestuia necesită acces DB. 

Testarea integrității datelor dvs.

Sistemul dvs. este la fel de bun ca și integritatea datelor dvs. Dacă ai corupt date sau lipsesc date, atunci ești în formă rea. În sistemele din lumea reală, este dificil să se mențină integritatea perfectă a datelor. Schimbarea schemelor și a formatelor, datele sunt ingerate prin canale care nu pot verifica toate constrângerile, bug-urile lăsate în date proaste, administratorii încearcă rezolvările manuale, copii de rezervă și restaurările ar putea fi nesigure.

Având în vedere această realitate aspră, trebuie să testați integritatea datelor sistemului. Testarea integrității datelor este diferită de testele automate regulate după fiecare modificare a codului. Motivul este că datele pot merge rău chiar dacă codul nu sa schimbat. Cu siguranță doriți să executați verificări de integritate a datelor după modificările de cod care ar putea altera stocarea sau reprezentarea datelor, dar și să le executați periodic.

Testarea constrângerilor

Constrângerile sunt baza modelării datelor. Dacă utilizați un DB relațional, puteți defini anumite constrângeri la nivelul SQL și le permiteți DB să le aplice. Nulitatea, lungimea câmpurilor de text, unicitatea și relațiile 1-N pot fi definite cu ușurință. Dar SQL nu poate verifica toate constrângerile.

De exemplu, în Desongcious, există o relație N-N între utilizatori și cântece. Fiecare melodie trebuie să fie asociată cu cel puțin un utilizator. Nu există o modalitate bună de a impune acest lucru în SQL (bine, puteți avea o cheie străină de la un cântec la un utilizator și au punctul de cântare către unul dintre utilizatorii asociați cu acesta). O altă constrângere ar putea fi faptul că fiecare utilizator poate avea cel mult 500 de melodii. Din nou, nu există nici o modalitate de ao reprezenta în SQL. Dacă utilizați stocări de date NoSQL, de obicei există și mai puțin suport pentru declararea și validarea constrângerilor la nivelul magazinului de date.

Aceasta vă oferă câteva opțiuni:

  • Asigurați-vă că accesul la date se face numai prin intermediul interfețelor verificate și a instrumentelor care pun în aplicare toate constrângerile.
  • Scanați periodic datele, încălcați restricțiile de vânătoare și remediați-le.    

Testarea Idempotency

Idempotency înseamnă că efectuarea aceleiași operații de mai multe ori la rând va avea același efect ca și efectuarea o dată. 

De exemplu, setarea variabilei x la 5 este idempotent. Puteți seta x la 5 o dată sau un milion de ori. Acesta va fi în continuare 5. Cu toate acestea, creșterea X cu 1 nu este idempotent. Fiecare increment consecutiv își schimbă valoarea. Idempotency este o proprietate foarte dorită în sistemele distribuite cu partiții temporare de rețea și protocoale de recuperare care reîncercă să trimită un mesaj de mai multe ori dacă nu există nici un răspuns imediat.

Dacă proiectați idempotency în codul dvs. de acces la date, trebuie să îl testați. Aceasta este de obicei foarte ușoară. Pentru fiecare operațiune idempotent se extind pentru a efectua operația de două ori sau mai multe la rând și a verifica dacă nu există erori și starea rămâne aceeași.   

Rețineți că proiectarea idempotent poate uneori să ascundă erori. Luați în considerare ștergerea unei înregistrări dintr-un DB. Este o operațiune idempotentă. După ce ștergeți o înregistrare, înregistrarea nu mai există în sistem și încercarea de ao șterge din nou nu o va aduce înapoi. Aceasta înseamnă că încercarea de a șterge o înregistrare inexistentă este o operație validă. Dar ar putea masca faptul că tasta greșită a înregistrării a fost trecută de apelant. Dacă întoarceți un mesaj de eroare, atunci nu este idempotent.    

Testarea migrărilor de date

Migrațiile de date pot fi operațiuni foarte riscante. Uneori, executați un script peste toate datele sau părțile critice ale datelor dvs. și efectuați o intervenție chirurgicală gravă. Ar trebui să fii pregătit cu planul B în cazul în care ceva nu merge bine (de exemplu, reveniți la datele originale și aflați ce a mers prost).

În multe cazuri, migrarea datelor poate fi o operație lentă și costisitoare, care poate necesita două sisteme simultan pe durata migrării. Am participat la mai multe migrații de date care au durat câteva zile sau chiar săptămâni. Atunci când se confruntă cu o migrare masivă a datelor, merită să investească timpul și să testați migrarea în sine pe un subset mic (dar reprezentativ) al datelor dvs. și apoi să verificați dacă datele recent migrate sunt valide și sistemul poate funcționa cu acesta. 

Testarea datelor lipsă

Datele lipsă reprezintă o problemă interesantă. Uneori, datele lipsă vor încălca integritatea datelor dvs. (de exemplu, o melodie al cărei utilizator lipsește) și, uneori, lipsește (de exemplu, cineva elimină un utilizator și toate melodiile acestuia).

Dacă datele lipsă provoacă o problemă de integritate a datelor, atunci o veți detecta în testele privind integritatea datelor. Cu toate acestea, dacă unele date lipsesc, atunci nu există o modalitate ușoară de a le detecta. Dacă datele nu au făcut-o niciodată în stocare persistentă, poate că există o urmă în jurnalele sau alte magazine temporare.

În funcție de cât de mult lipsesc datele de risc, puteți scrie câteva teste care elimină în mod deliberat unele date din sistemul dvs. și verificați dacă sistemul se comportă conform așteptărilor.

Concluzie

Testarea codului intensiv de date necesită o planificare deliberată și o înțelegere a cerințelor dvs. de calitate. Puteți testa la mai multe niveluri de abstractizare, iar alegerile dvs. vor afecta cât de detaliate și cuprinzătoare sunt testele dvs., cât de multe aspecte ale nivelului actual de date pe care îl testați, cât de repede se efectuează testele și cât de ușor este să modificați testele atunci când modificările stratului de date.

Nu există un singur răspuns corect. Trebuie să vă găsiți locul dulce de-a lungul spectrului, din teste super-cuprinzătoare, lente și cu forță de muncă, la teste rapide și ușoare.  

Cod