Știm cu toții că în lumea programării nu există proiecte perfecte sau fără buguri. În ciuda eforturilor noastre de a scrie cod de înaltă calitate și de a testa meticulos aplicațiile noastre, greșelile și neglijențele sunt, până la urmă, inevitabile. Dar asta nu înseamnă că ar trebui să renunțăm la dorința de perfecțiune. Există mai multe tehnici de care ne putem folosi pentru a ne dezvolta proiectele mai bine și mai fiabil.
O abordare ar fi să ne concentrăm pe scrierea de cod "curat", ușor de înțeles și de întreținut. Folosind principiile de Clean Code, putem minimiza probabilitatea de a introduce erori, făcând astfel procesul de înțelegere și modificare a codului mai ușor atât pentru noi cât și pentru alții.
O altă tehnică pe care o putem folosi pentru a îmbunătăți experiența noastră, indiferent de proiectul la care lucrăm, este aceea de a aplica Behavior Driven Development (Dezvoltarea Bazată pe Comportament) sau, pe scurt, BDD. Această tehnică pune accent pe colaborarea și comunicarea dintre dezvoltatori, testeri și membrii non-tehnici ai echipei. BDD ne ajută să ne asigurăm că toți cei implicați în proiect au înțelegere deplină a ceea ce software-ul trebuie să facă, iar codul și testele sunt aliniate cu aceste cerințe.
Observăm că undeva la intersecția acestor două concepte putem găsi loc pentru ideea de testare automată. Prin scrierea de teste automate care pot fi rulate rapid și ușor, putem identifica erorile în stadiile incipiente și, totodată, să ne asigurăm că funcționalitatea codului nostru se încadrează în parametrii necesari chiar și atunci când facem schimbări.
În acest articol, vom explora fiecare dintre aceste tehnici în detaliu și vom discuta cum pot fi utilizate împreună pentru a dezvolta proiecte robuste și bug-free (pe cât de multe se poate).
Înainte de a merge mai departe trebuie să stabilim un concept de bază legat de Clean Code și cum ne-ar afecta în această situație. Desigur, fiecare developer știe că aplicarea principiilor de Clean Code reduce costurile de dezvoltare și îmbunătățește calitatea produsului final. Din păcate, există foarte multe reguli pe care ar trebui să le aplicăm în timp ce analizăm sau proiectăm flow-uri complexe în cod. Uneori, putem fi prea copleșiți de taskul respectiv pentru a ne concentra cu adevărat pe regulile și directivele învățate acum mult timp, "in a galaxy far far away …". Prin urmare, în scopul acestui articol, trebuie să avem în minte o singură regulă, respectiv unul din principiile de bază ale Clean Code: "Pot să-l citesc?". Atunci când codul conține orice lucru care necesită o explicație suplimentară sau muncă de detectiv, înseamnă că nu-l putem citi cu adevărat, prin urmare nu este human-friendly. În final, exact asta dorim să obținem: un cod cu care este ușor de lucrat.
O altă parte esențială pentru îmbunătățirea codului este testarea. Cu toții putem fi de acord că testarea este un pas esențial în asigurarea faptului că diferitele componente ale aplicației funcționează împreună așa cum trebuie. Adesea, testele pe care le scriem pentru diferite flow-uri se bazează exclusiv pe înțelegerea noastră asupra business logicului. Iar asta se dovedește a fi o logică ușor eronată, în unele cazuri. Ca și scrierea codului, scrierea testelor poate duce la același proces confuz descris mai devreme. Pornim de undeva, doar că, uneori, complexitatea componentei testate ne face să ne abatem de la scopul nostru principal. În acest punct, posibilele rezultate și scenarii sunt trecute cu vederea, iar bugurile pot rămâne nedescoperite. Aici intervine utilizarea metodologiei de Behavior Driven Development (BDD).
BDD este o abordare de tip Agile în care o aplicație este proiectată în jurul comportamentului pe care utilizatorul se așteaptă să-l aibă în timpul interacțiunii cu ea. În BDD, cerințele de software sunt exprimate ca user stories (diverse scenarii ipotetice din punct de vedere al utilizatorului care folosește aplicația), scrise într-un limbaj simplu și concis, care este ușor de înțeles atât de membrii tehnici, cât și de cei non-tehnici.
A avea scenarii bine exprimate, folosind logica "Given - When - Then" este un punct de plecare excelent pentru crearea scenariilor de testare automată. Acest lucru încurajează o strânsă colaborare între dezvoltatori, testeri și membrii non-tehnici ai echipei, ceea ce produce o înțelegere mai clară a comportamentului aplicației, precum și a criteriilor de acceptare necesare. În plus, acest lucru adaugă valoare integrării continue (continuous integration) și a verificării calității. De ce? De exemplu, cu integrarea continuă, testăm automat proiectul cu fiecare nouă modificare care este făcută sau feature adăugat. Acest lucru previne regresia și oferă feedback cu privire la posibilele modificări care pot afecta alte părți ale aplicației.
În continuare, vom observa cum putem descrie cerințele și logica software-ului în termeni de scenarii exprimate într-un limbaj simplu și ușor de înțeles.
Aici putem vedea cum se desfășoară structura "Given - When - Then". De asemenea, observăm că acesta este punctul de plecare în aplicarea principiului metaforic de Clean Code: "Can I read it?". Folosind o explicare simplă și lizibilă a funcționalității, evităm confuzia și posibilitatea de a introduce cod duplicat sau de a crea faimosul "spaghetti code".
Testele scrise folosind Cucumber sunt implementate într-o sintaxă simplă și declarativă care utilizează limbajul natural pentru a descrie comportamentul software-ului în termeni de scenarii și pași. Aceste scenarii sunt definite folosind limbajul Gherkin care constă într-un set de cuvinte cheie folosite pentru a oferi structură și semnificație specificațiilor executabile. Fiecare pas în Gherkin este asociat cu o metodă care execută pasul respectiv, realizat prin utilizarea de adnotări și regex care le leagă împreună.
Cucumber suportă o mulțime de limbaje de programare și este destul de ușor de instalat. Pentru pași specifici referitor la modul de a începe testarea pe un proiect cu aceasta, vă rugăm să accesați https://cucumber.io/docs/installation/, selectați limbajul de programare dorit și urmați instrucțiunile.
Cu toate acestea, simpla definire a unui scenariu și instalarea Cucumber nu va face magia transformării unui text simplu în teste automate. Trebuie să mergem un pas mai departe în procesul de definire a pașilor. În esență, acest lucru înseamnă luarea fiecărui fragment de text din descrierea scenariului și interpretarea acestuia în metode sau funcții independente.
În timp ce scenariile descriu cerințele, definițiile pasului reprezintă o formă de divide et impera ( dezbină și cucerește). Beneficiul împărțirii cerinței în sarcini mici și independente este că acestea vor deveni reutilizabile în alte scenarii. Acest layer de definiție a pasului apelează layerul de automatizare a testului și rulează efectiv metodele necesare.
Urmează câțiva pași de bază necesari rulării unui exemplu folosind Cucumber și JUnit 5 într-un proiect Java:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit-jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>${junit-platform-suite.version}
</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine
</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin
</artifactId>
<version>
${maven-surefire-plugin.version}
</version>
</plugin>
</plugins>
</build>
Pe lângă dependința cucumber-java, ar fi necesară și dependința cucumber-junit-platform-engine pentru a rula folosind JUnit 5.
În JUnit 4, punctul de acces pentru folosire Cucumber ar fi fost definit prin utilizarea @RunWith(Cucumber.class). Pentru integrarea cu JUnit 5, a fost folosită adnotare @Cucumber. Cu toate acestea, deoarece integrarea completă cu JUnit5 este posibilă, adnotarea este acum depreciată și trebuie să utilizăm @Suite împreună cu alte adnotări.
package com.dlupu.cucumber.demo;
import org.junit.platform.suite.api
.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api
.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine
.Constants.GLUE_PROPERTY_NAME;
@Suite
@IncludeEngines("cucumber")
//tells JUnit 5 to use Cucumber test engine to run
//features
@SelectClasspathResource("features")
//where the feature files are located
@ConfigurationParameter(key = GLUE_PROPERTY_NAME,
value = "com.dlupu.cucumber.demo")
//^this annotation specifies the path to steps
// definitions
public class CucumberTest {
}
Un fișier de caracteristici, care are extensia feature, cuprinde unul sau mai multe scenarii destinate testării unei anumite funcționalități, fiecare scenariu reprezentând un singur caz de testare. Acesta este locul în care este utilizat limbajul Gherkin. Așadar, trebuie să ne asigurăm că avem instalat pluginul pentru limbajul Gherkin.
Feature: Shopping functionalities
Scenario: Earning a 10% online voucher when
shopping of 100 dollars
Given user selects products in value of
100 dollars
When user completes the purchase
Then user should receive a 10% online voucher
În funcție de IDE-ul utilizat, pașii care nu au fost implementați încă în fișierul de Steps vor fi highlighted.
Clasa Steps creează conexiunea între fiecare pas al scenariului descris în fișierul feature și o metodă corespunzătoare care trebuie executată. Când Cucumber execută un pas din scenariul pe care l-am descris în fișierul feature, el scanează, de fapt, clasa Steps și determină ce funcție ar trebui invocată.
package com.dlupu.cucumber.demo;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
public class ShoppingSteps {
@Given("user selects products in value of {int}"+
" dollars")
public void userSelectsProductsInValueOfDollars(
int amount) {
System.out.println("user selects products in "
+"value of " + amount + " dollars");
}
@When("user completes the purchase")
public void userCompletesThePurchase() {
System.out.println("user completes the purchase");
}
@Then("user should receive a {int}% online voucher")
public void userShouldReceiveAOnlineVoucher(
int voucherAmount) {
System.out.println("user should receive a " +
voucherAmount + "% online voucher");
}
}
Testele pot fi rulate folosind comanda mvn clean test
sau mvn clean install
.
Observăm că, prin utilizarea BDD și Cucumber, echipele pot identifica buguri sau inconsistențe logice în stadiile incipiente ale procesului de dezvoltare, ceea ce ajută la reducerea costurilor asociate cu remedierea defectelor de mai târziu în proiect. De asemenea, se asigură că proiectul îndeplinește cerințele și așteptările clientului, rezultând într-un proiect dezvoltat cu succes. Chiar dacă partea mai spectaculoasă este realizată de frameworkul de testare, trebuie să observăm că, fără baza principiilor de Clean Code și fără utilizarea unui business logic clar definit, explicat într-un mod "human-friendly" (indiferent dacă este cod sau doar documentație), acest proces de automatizare a testelor nu ar fi posibil.
Garcia, Boni. Mastering Software Testing with Junit 5. Packt Publishing, 2017.
Martin, Robert C., et al. Clean Code A Handbook of Agile Software Craftsmanship. Pearson Education, Inc, 2016.
Rose, Seb, et al. The Cucumber for Java Book: Behaviour-Driven Development for Testers and Developers. The Pragmatic Bookshelf, 2015.
Smart, John Ferguson, and Dan North. BDD in Action: Behavior-Driven Development for the Whole Software Lifecycle. Manning, 2015.
de Ovidiu Mățan
de Andrei Miron
de Ovidiu Mățan
de Ovidiu Mățan