UiPath Platform: The Backend Engine Powering Your Automation - Session 1
Tdd Primer
1. Test-driven development primer
Test-driven development primer
Robert Munteanu
http://robert.muntea.nu
http://robert.muntea.nu
@rombert
2. Who I am
$DAYJOB
FOSS
Adobe Experience
MantisBT
Management
Mylyn Connector for
− Apache Sling MantisBT
− Apache Jackrabbit
Mylyn Connector for
Review Board
− Apache Felix
Apache Sling
http://robert.muntea.nu
@rombert
3. Agenda
●
Intro to automated testing of software
●
Test-driven development
●
Top-down Test-driven development
●
General industry practices around testing
http://robert.muntea.nu
@rombert
4. Not on the agenda
●
Performance testing
●
Fuzzing
●
Security testing
●
Internationalization and localisation testing
●
GUI-driven testing ( play and record )
http://robert.muntea.nu
@rombert
5. I. Role of testing in software products
Testing workflows
http://robert.muntea.nu
@rombert
6. Testing workflows
Developer ships code
to tester for validation
http://robert.muntea.nu
@rombert
8. Testing workflows
Developer and QE
developer and QE
tester work together
http://robert.muntea.nu
@rombert
9. Testing workflows
My code works,
dammit
http://robert.muntea.nu
@rombert
10. Testing automation
●
Supported by a testing framework
●
Your favourite language probably has it
●
Makes it easy to separate the test code from the
production code
http://robert.muntea.nu
@rombert
12. Testing automation
This is what you
usually get at
development time
http://robert.muntea.nu
@rombert
13. Types of tests
The testing pyramid
is a popular way of
classifying tests
http://robert.muntea.nu
@rombert
14. Unit tests
●
Small, self-contained
●
No external dependencies
●
Fastest type of test ( under 1 second )
●
Should be the bulk of your test
http://robert.muntea.nu
@rombert
15. Service tests
●
Larger tests
●
Usually interact with external systems
●
SQL-based tests are a good example
http://robert.muntea.nu
@rombert
16. UI tests
●
Application instance up and running
●
Interacts with the UI rather than in-process
●
Most programatic UI testing uses Selenium
http://robert.muntea.nu
@rombert
17. Q&A
End of first session, your questions?
http://robert.muntea.nu
@rombert
18. Test-Driven development
●
Rapid feedback for your changes
●
Ensures implementation and API are minimal
●
Safety net when refactoring or making other
changes (YAGNI)
●
Saves everyone time and money
http://robert.muntea.nu
@rombert
29. Q&A
End of third session, your questions?
http://robert.muntea.nu
@rombert
30. Code quality
Test code quality is the same as production code
quality
●
Fast
●
Stable
●
Meaningful
http://robert.muntea.nu
@rombert
31. Code coverage
●
Measure it, know your weak points
●
Don't obsess over percentages
http://robert.muntea.nu
@rombert
32. Automate, automate, automate
●
Don't rely only on your IDE for tests
●
Make sure it works with a build tool
●
Make sure it works on other machines as well
http://robert.muntea.nu
@rombert
38. Thank you
Robert Munteanu
http://robert.muntea.nu
http://robert.muntea.nu
@rombert
Notas do Editor
Bun venit la prezentare Repet titlul prezentării
Contribuitor la proiecte open source - partial for fun, parțial ca să keep up to date și sa încerc alte tehnologii - CS la Adobe, lucrez cu platforma CQ - baza open source a platformei este Apache Sling și astfel am avut contact Hai să vedem despre voi – câți aveți o minimă experiență în limbaje de programare high-level ( Java, PHP, Ruby etc ) ; dar C/C++ ; dar ASM? - câți ați testat automat un program de calculator ( dacă da, cu ce, share un pic la experiență )
Acestea sunt cele patru puncte pe care vreau să le ating. Avem destul timp la dispoziție și putem devia oricând. Dacă sunt lucruri pe care vreți să le discutăm mai în detaliu opriți-mă și întrebați. Prefer să fiu întrerupt, sigur nu uit poezia :-) Nu este necesar să folosiți calculatorul sau un program pentru a lucra. Eu voi avea și câteva demo-uri ; daca vreți să le încercați și voi e OK ( aveți nevoie de acces la rețea ) dar nu este obligatoriu. Demo-urile vor fi în Java, dar nu este necesar decât să puteți să citiți Java.
Testarea ca și domeniu de discuție e fantastic de mare și o să ne limităm la testarea automată legată de funcționarea codului în condiții normale să-i spunem. Accentul va fi pe test-driven development ca și specializare a practicii de testare automată. Dacă vreți să discutăm despre subiectele de mai sus sau despre alte subiecte de testare o putem face în ultima dintre sesiuni, cea de practici generale.
Când avem un flow de tipul eu scriu cod, tu testezi, de multe ori e un semn al unui proces de tip waterfall care s-a propagat în stilul de lucru al echipei. Am încercat să fiu subtil cu poza :-) Este cineva familiar cu modelul de dezoltare waterfall? Pe scurt, avem Cerințe → Design → Implementare → Verificare → Mentenanță. Fără buclă de feedback. Ce înseamnă asta în practică ? Eu ca dezvoltator primesc rezultatele testării cu întârziere ( eventual în timp ce lucrez la altceva ) ; tester-ul e o resursă care probabil nu e alocată mereu echipei ; o viteză scăzută de reacție și timp de dezvoltare scăzut.
Putem să avem și altă extremă, cea în care nu există tester, iar dezvoltatorul testează totul automat. Cum vă sună asta, e bine sau rău? Evident, e bine și rău :-) Bine pentru că arată o înclinație înspre testare, și clar bucla de feedback pe care o am de la un test rulat local pe calculatorul meu ( < 1 secundă ) este mult mai scurtă decât câteva ore ( în cazul bun ) în cazul unui tester. Multe probleme vor fi rezolvate foarte rapid. Rău este că dezvoltatorul nu poate testa tot – el a scris codul și inconștient va testa ce a scris ; dacă ar fi știut ce nu merge n-ar mai fi scris codul astfel. În plus, un dev nu poate face ca un tester fuzzing, security testing etc
Aici e cumva o situație ideală, în care avem un dezvoltator, un dezvoltator QE ( sau SET etc ) și un tester, fiecare cu rolul lui. QE dev-ul se ocupă de framework-uri de testare, 'promoveză' ideea de software testabil și de calitate internă și poate să scrie anumite teste. Dev-ul își scrie și el testele. La final un tester face partea manuală, fie pentru că e prea complicat de automatizat, fie pentru că nu trebuie automatizată ( fuzzing, penetrating testing etc ) Totul pare bine și frumos aici dar … banii :-)
Nu am pus acest slide doar pentru valoarea artistică :-) Se întâmlă mai des decât ați crede. Și înainte de acest meme am întâlnit expresia “cel mai bun test este producția”. Și chiar asta era abordarea. Ce ar fi bun aici? Timpul dezvoltatorului, lipsă de stres :-) Ce ar fi rău? Calitatea se duce de râpă, dificultatea în testare, nu există teste de regresie, nu există pași clar de reproducere... Cam tot e rău aici.
Testarea automată se face în general cu sprijinul unui framework de testare. Fiecare limbaj de programare are unul ; am găsit de la Java la C la PHP la PL/SQL Nu mai avem nevoie de a scrie cod de infrastructură pentru teste, e suficient să includem o bibliotecă externă în proiectul nostru și putem să scriem teste automate.
Aici avem un foarte simplu exemplu de o clasă de testat și de un test. Este timpul de cod pe care nu îl scriem niciodată, dar este un exemplu perfect pentru demonstrație. În clasa de producție avem o funcție, iar în clasa de test avem un caz de test. Cazul de test este concis, ușor de înțeles, și chiar se poate citi cu voce tare: assertThat add(1,1) is (2). Testul se poate rula din linie de comandă sau dintr-un IDE, spre exemplu.
În timp de dezvolt nu am decât de dat un click de dreapta pe clasa de test și Run As → Junit test . Voi vedea toate metodele testate și apoi văd care au trecut, care au picat, motivele pentru care au picat și un stack trace dacă e cazul. De asta prefer un IDE față de un editor de text simplu. Ăsta e un sfat general pe care vi l-aș da, nu neapărat legat de testare. Alegeți-vă cu grijă uneltele cu care lucrați, vă pot ține în loc sau vă pot ajuta să fiți mult, mult mai productivi.
Piramida testării este o modalitate utilă de a vizualiza ce fel de teste putem scrie și mai ales cum să le împărțim. Compartimentarea sugerează că avem o bază de teste unitare, un strat de testare a serviciilor și un vârf al testelor care interacționează cu interfața. Ca și volum al testelor recomandarea este să avem cât mai multe teste unitare, un număr redus de teste care interacționează cu serviciilor și un număr minim de teste care interacționează cu interfața. Explicația este că pe verticală crește complexitatea, timpul de execuție și instabilitatea.
Testele unitare sunt poate cel mai la îndemână mod de teste pe care îl avem. Clasa (trivială) pe care am văzut-o mai devreme este un exemplu de test unitar. Testele unitare testează o unitate de cod, fără a lua în calcul alte unități/sisteme. În Java, unitatea este în mod normal o clasă. Interacțiunile cu alte sisteme 1) introduc elemente aleatoare în test ( cum ar fi un test unitate care transferă 250 MB prin FTP? ) și 2) reduc viteza de rulare. Teste extrem de rapide duc la posibilitatea de a rula întreaga baterie de teste foarte rapid. Cum ar fi să rulezi 10,000 de teste în mai puțin de o secundă?
De cele mai multe ori nu este posibil să nu interacționezi cu alte sisteme. O bază de date ( SQL sau nu ) , Web Service-uri, un ORM ca Hibernate sunt foarte dificil de testat în izolare. Mai mult, de cele mai multe ori preferi să testezi un SELECT și să fi sigur că întoarce datele corecte, chiar dacă nu e la fel de ușor de scris ca un unit test. De genul ai o bază de date in-memory ( H2 ) pe care o creezi special pentru testul tău unitate, o populezi cu date și apoi rulezi un SELECT. La sfârșit cureți baza de date. Clar este mai încet, dar este foarte util.
De multe ori este util să ai teste care validează aplicația end-to-end, de la interfață la baza de date la rutinele low-level. De asemenea, sunt singurele teste care validează că aplicația chiar merge. Degeaba ai testele unitare care trec toate dacă ai o eroare de javascript pe home page. Sunt lente, lente, LENTE. De asemenea, sunt instabile. E o problemă dificilă să controlezi programatic un browser, să inspectezi controalele de pe pagină, sa lansezi acțiuni etc. Plus că mai nou toate aplicațiile sunt javascript-enhanced sau chiar single-page application, ceea ce complică și mai mult efortul de testare. Au rolul lor, dar trebuie ținute sub control ca număr, timp de execuție și complexitate de implementare.
Unele din avantaje nu sunt exclusive TDD, țin și de partea de automated testing. Clar cu TDD ai cel mai rapid feeback pentru schimbările tale. Un alt punct bun este că ne ajută să avem implementări și API-uri ( ce sunt API-urile? ) minimale. Ca programatori avem tendința să avem implementări complicate, care pe termen lung sunt un bagaj inutil. YAGNI – you ain't gonna need it. Timpul și costul necesare reparării unui bug sunt cele mai mici la începutul procesului de dezvoltare. Un bug prins în ciclul de testare automată de dezvoltator este cel mai ieftin. Un bug în faza de testare e ceva mai OK, dar în producție poate fi dezastruos de scump. Un algoritm de criptare slab duce la probleme de securitate, să nu mai vorbim dacă e cazul de aparatură medicală sau de soft pentru autovehicule.
În caz că nu v-am convins logic, poate cu benzi desenate merge ;-)
Un demo de câteva minute în care trecem prin pașii clasici ai TDD 1. adaug un test 2. rulez toate testele și văd dacă pică vreunul 3. scriu cod 4. rulez toate testele și văd dacă pică vreunul 5. refactor 6. GOTO 1
Un demo de câteva minute în care trecem prin pașii clasici ai TDD 1. adaug un test 2. rulez toate testele și văd dacă pică vreunul 3. scriu cod 4. rulez toate testele și văd dacă pică vreunul 5. refactor 6. GOTO 1
Într-adevăr, clasele care nu interacționează cu nimeni și nimic sunt foarte simplu de testat. Dar ce facem cu clasele care au colaboratori? Dacă folosim colaboratorii în test, nu mai este un test unitar, cu toate problemele care pot apare ( instabilitate, timp de execuție crescut, complexitate ). Abordarea preferată este de a înlocui un colaborator real cu unul fals, care reacționează așa cum îl instruim noi. Astfel de colaboratori se numesc în general stubs sau mocks, în funcție de cât de deștepți sunt. Un stub are răspunsuri predefinite. Un mock are comportament mai complex, poate să arunce excepții în anumite condiții etc
Un demo despre stub-uri.
Acum că am trecut prin tehnicile uzuale pentru testele unitare e vremea să vedem cum punem cap la cap un test care interacționează cu un serviciu extern, în cazul nostru o bază de date. Nu voi folosi un mock sau un stub, pentru că API-ul de JDBC nu e chiar simplu de mock-uit, și mă interesează dacă am scris bine un SELECT. Testul va folosi H2 – o bază de data Java, care poate fi pornită și oprită în același proces. Are avantajul de a fi foarte rapidă și ușor de folosit în teste. Dezavantajul este că de multe ori nu este folosită în producție, doar în teste, și atunci dacă sintaxa bazei de date folosite în producție diferă, apar probleme. Cum se pot rezolva? 1. Cu atenție, 2. cu teste rulate și pe baza de date de prod 3. Cu un ORM cum este hibernate.
Demo cu DAO.
Vârful piramidei e rezervat pentru testele care exersează UI-ul. Aceste teste sunt lente, mai puțin stabile, dar verifică aplicația pe de-a întregul. Folosirea acestor teste în model TDD se mai numește și ATDD – Acceptance-Driven Test development. Practic testele de UI pot fi folosite pentru a verifica în ce măsură aplicația dezvoltată corespunde cerințelor – dacă este acceptată de către client sau nu. Vom vedea cum putem scrie și executa teste de UI folosing Selenium și un wrapper peste aceasta – Thucydides.
Demo cu Selenium + Thucydides De review - Page object - Selenium model - Testul în sine - Rapoartele Thucydides
În aplicații mari testele pot ajunge la dimensiuni respectabile, poate chiar să depășească dpdv al liniilor codul productiv. Atunci este esențial ca testele să aibă o calitate ridicată. Testele lente nu vor fi rulate suficient de des. Testele instabile vor fi ignorate. Testele care nu testează nimic important vor trece tot timpul și nu aduc valoare.
Code coverage-ul ne indică în ce măsură testele noastre acoperă tot codul de producție. Sunt mai mulți indicatori – line, statement, branch . Important este să tratăm code coverage-ul ca o informație calitativă pentru a gestiona riscul, și nu ca un prag care trebuie atins.
Dacă testele sunt legate de mașina ta sau de un IDE, sunt inutile. Automatizează tot procesul, de la compilare aplicației până la împachetare ( make, ant, maven, gradle, sbt, whatever ).
Jenkins e un sistem de continuous integration ; pe scurt – la fiecare commit ( sau periodic, sau manual, sau … ) rulează un set de comenzi pe un proiect software. Cel mai adesea setul standard – compilare, testare, împachetare. Poate ține evoluția în timp a anumitor indicatori – număr de teste, teste trecute/picate etc. Extensibil până la absurd. Testele automatizate pe Jenkins au o vizibilitate sporită – mai ales când pică build-ul și vin mailuri :-)
Jenkins permite constuirea unui 'build pipeline', o înseriere de job-uri, care pot să separe pașii pentru un feedback mai rapid. Unii pași pot fi manuali, alții automați. Testele de acceptanță, mai lente, sunt un candidat ideal pentru un job separat care să fie rulat numai după ce trec testele unitare.
Din nou, Jenkins are un plugin :-) care permite configurarea unui job în funcție de un parametru variabil – axa. Este ideal – spre exemplu – pentru a putea testa aplicația pe mai multe browsere sau pe mai multe tipuri de baze de date.