E o intreaga dezbatere referitoare la limita care defineste “unit testing” (eu aud prima data “teste unitare”; “unitar”, desi are aceeasi radacina cred ca inseamna altceva; daca ar fi sa traducem poate ar fi “testare pe unitati/unitate”?)
In proiectul nostru noi apelam foarte mult un API extern (OpenStack) si atunci nu avem ce testa acolo cu unit testing.
Avem cateva locuri unde putem face unit testing pur sange, cum ar fi calcule legate de billing. Dar acestea sunt foarte putine.
Si atunci facem de fapt mult integration test. Pe noi ne intereseaza sa testam cu diverse versiuni de OpenStack si sunt relevante pentru noi testele reale pe un API real cu o instalare de OpenStack. Astfel ne asiguram ca functioneaza codul nostru cu diverse versiuni si deployment-uri de OpenStack.
Pe backend (Django Rest Framework) avem in total 625 de teste (unit + integration) si ruleaza tot in Gitlab in 7 minute. Cu tot setup: containerul docker pe care il face Gitlab, instalat pachete Python (aici facem ceva cache), migrari etc.
Mai ruleaza automat si testele de coding style (flake8), dar astea sunt rapide.
La fiecare commit pe branch de development ruleaza testele si, optional, se pot buildui si descarca pachete rpm si deb cu aplicatia:
Pe master avem teste sumare acum, se fac pachetele automat si se face automat deploy pe staging (de fapt e un server de dev, e impropriu numit staging).
Mockuim destul de putin:
- unde ne concetram pe partea de billing si nu putem testa izolat de API, dar nici nu ne intereseaza API call-uri catre OpenStack
- trimitere email-uri
- ceva setari supra scrise
- datetime-ul pentru un bug legat de miezul noptii si time zone
- am mai mocuit de curand logging.error pentru ca am vrut output cat mai curat dupa teste ca sa poti vedea mai usor cand sunt probleme cu adevarat. Si cand fortezi diverse situatii extreme e normal sa apara erori in log. Am facut o mica clasa callable si am putut ascunde doar erorile care sunt asteptate, plus le-am transformat in teste avand asertiuni ca (1) s-au logat erorile asteptate si (2) nu au fost alte erori pe care nu le asteptam
Testele pe frontend iau 20 de minute din pacate (cu toata instalatia Gitlab). 32 unit tests + 128 teste e2e cu Selenium.
Mai lucram din cand in cand sa reducem durata de rulare prin diverse optimizati si mai mutam teste e2e in integration pe backend. Testele e2e pe frontend fiind cele mai lente.
Baza de date se face de la zero pentru fiecare test. Se ocupa Django de asta.
Din pacate nu putem acum testat cu baza de date MySQL in RAM (ca avem ceva field-uri Text cu lungime nedefinita, dar rezolvabil daca modificam schema) iar cu SQLite nu merge acum sa testam pentru ca sunt 3 procese (teste, celery si un daemon care primeste notificari pe RabbitMQ de la OpenStack) care acceseaza acelasi fisier SQLite si crapa. Mai avem de mesterit aici la optimizari.
Ce vreau sa facem este sa avem mai multe niveluri de testare:
- sa ruleze automate de la fiecare commit intr-o versiune de setup mai lite si mai rapid; de ex. sa nu mai generam cheie de licenta, poate refolosim DB si/sau reusim sa folosim MySQL in memory sau SQL Lite
- sa avem alte teste pe master complete si in setup de productie (MySQL real, cheie licenta etc.) acestea dureaza mult si atunci sa nu fie blockere pentru un merge request; sa le rulam automat dupa un cron (noaptea si eventual la niste ore fixe ziua) plus declansate manual inainte de release-uri
Nu cred ca au mare importanta delimitarile puriste, mai important e:
- sa (tinzi sa) ai suficiente teste ca sa iti validezi aplicatia
- sa fie deterministice testele; asta e o provocare cand rulezi teste de integrare cu un API extern, plus isi mai baga coada un race condition (pe care inca nu l-ai rezolvat)
- sa ruleze rapid testele
De exemplu, inteleg ca un test care trece prin ORM nu e un unit test pur, dar eu personal nu vreau sa imi bat capul cu mock pe ORM pentru ca nu e rasplatit efortul cu mare lucru.
Evident, e bine sa incerci sa iti faci codul testabil din start si sa fie functii izolate de alte componente si sa foloseasca dependency injection daca se poate.