This shows you the differences between two versions of the page.
|
poo-ca-cd:laboratoare:programare-avansata-java [2026/01/11 22:03] florian_luis.micu |
poo-ca-cd:laboratoare:programare-avansata-java [2026/01/12 01:22] (current) florian_luis.micu [[Optional] Software Development Methodologies] |
||
|---|---|---|---|
| Line 7: | Line 7: | ||
| =====Obiective===== | =====Obiective===== | ||
| - | =====Serializare și Deserializare===== | + | Scopul acestui laborator este introducerea studenților în concepte mai avansate care permit crearea framework-urilor și a aplicațiilor enterprise. |
| + | |||
| + | Aspectele urmărite sunt: | ||
| + | * înțelegerea conceptelor de testare. | ||
| + | * utilizarea corectă a mecanismului de reflection. | ||
| + | * exersarea serializărilor și deserealizărilor. | ||
| + | * implementarea și utilizarea corectă a adnotărilor. | ||
| + | * exersarea folosirii aserților. | ||
| + | * familiarizarea cu dependențe populare cum ar fi JUnit, Mockito, Jackson. | ||
| + | |||
| + | Aspectele **bonus** urmărite sunt: | ||
| + | * înțelegerea mecanismelor de logging. | ||
| + | * aprofundarea metodologiilor de dezvoltare software. | ||
| + | * Diferența dintre getClass() și instanceof. | ||
| + | * Organizarea memoriei pentru moștenire în Java. | ||
| + | |||
| + | <note warning> | ||
| + | * În acest laborator există mai multe secțiuni marcate **[Optional]**. Aceste secțiuni cuprind informații **bonus** care vă pot fi prezentate în **timpul laboratorului** sau pe care le puteți aprofunda **în afara** acestuia, ele nefiind necesare pentru laboratoarele viitoare sau pentru teme. | ||
| + | * De asemenea, veți întâlni câteva secțiuni marcate **[Nice to know]**. Vă recomandăm ca acestea să aibă **prioritate** în parcurgerea secțiunilor de tip **[Optional]**, deoarece vă pot oferi informații bonus care să fie și foarte probabil utile pentru **teme** sau **laboratoare viitoare**. | ||
| + | </note> | ||
| + | |||
| + | =====🔄 Serializare și Deserializare===== | ||
| ====Ce este serializarea?==== | ====Ce este serializarea?==== | ||
| Line 174: | Line 195: | ||
| ===De ce este Jackson preferat în aplicațiile moderne?=== | ===De ce este Jackson preferat în aplicațiile moderne?=== | ||
| - | De ce este Jackson preferat în aplicațiile moderne? | ||
| * JSON este lizibil și ușor de debugat, | * JSON este lizibil și ușor de debugat, | ||
| * este interoperabil cu alte limbaje, | * este interoperabil cu alte limbaje, | ||
| Line 339: | Line 359: | ||
| </code> | </code> | ||
| - | =====Adnotări în Java===== | + | =====🏷️ Adnotări în Java===== |
| ====Ce sunt adnotările?==== | ====Ce sunt adnotările?==== | ||
| Line 524: | Line 544: | ||
| </spoiler> | </spoiler> | ||
| + | ====[Optional] Când sunt procesate adnotările?==== | ||
| + | ===Cele trei momente de procesare=== | ||
| + | Java definește **trei momente distincte** în care adnotările pot fi citite sau folosite: | ||
| + | **1. La compilare (compile-time)** | ||
| + | Adnotările cu: | ||
| + | <code java> | ||
| + | @Retention(RetentionPolicy.SOURCE) | ||
| + | </code> | ||
| + | sunt: | ||
| + | * disponibile doar în timpul compilării | ||
| + | * eliminate complet din bytecode | ||
| + | * folosite de compilator sau tool-uri | ||
| + | Exemplu: | ||
| + | <code java> | ||
| + | @Override | ||
| + | </code> | ||
| + | Rol: | ||
| + | * verificare de corectitudine | ||
| + | * warning-uri / erori de compilare | ||
| + | <note important> | ||
| + | Nu există acces la aceste adnotări la runtime. | ||
| + | </note> | ||
| + | **2. După compilare, înainte de rulare (build-time / processing)** | ||
| + | Unele adnotări sunt procesate prin **Annotation Processors** (APT – Annotation Processing Tool). | ||
| + | Acestea: | ||
| + | * rulează după compilare | ||
| + | * pot genera cod | ||
| + | * pot valida structura codului | ||
| + | Exemple reale: | ||
| + | * Lombok (''@Getter'', ''@Setter'') | ||
| + | * MapStruct | ||
| + | * AutoValue | ||
| + | |||
| + | <note tip> | ||
| + | * Conceptual există un **pas de preprocesare**, dar **nu la nivelul JVM**, ci la nivelul **build-ului**. | ||
| + | * Acesta NU este un preprocesor ca în C (''#define''), ci un mecanism controlat de compilator. | ||
| + | </note> | ||
| + | |||
| + | 3. La runtime (execuție) | ||
| + | |||
| + | Adnotările cu: | ||
| + | <code java> | ||
| + | @Retention(RetentionPolicy.RUNTIME) | ||
| + | </code> | ||
| + | |||
| + | sunt: | ||
| + | * păstrate în bytecode | ||
| + | * disponibile prin **Reflection** | ||
| + | * interpretate de framework-uri | ||
| + | |||
| + | Exemple: | ||
| + | * Jackson (''@JsonProperty'') | ||
| + | * JUnit (''@Test'') | ||
| + | * Spring (''@Autowired'') | ||
| + | |||
| + | Acesta este **cel mai important caz** pentru Java modern. | ||
| + | |||
| + | |||
| + | =====🪞 Reflection în Java===== | ||
| + | |||
| + | ====Ce este Reflection?==== | ||
| + | |||
| + | Reflection este mecanismul prin care un program Java poate: | ||
| + | * inspecta **structura claselor** la runtime | ||
| + | * accesa **câmpuri, metode, constructori** | ||
| + | * crea obiecte dinamic | ||
| + | * invoca metode fără a le cunoaște la compilare | ||
| + | |||
| + | Cu alte cuvinte, Reflection permite unui program să se **auto-analizeze** și să se **auto-manipuleze** în timpul execuției. | ||
| + | |||
| + | ====De ce există Reflection?==== | ||
| + | |||
| + | Reflection a fost introdus pentru a permite: | ||
| + | * framework-uri generice (Spring, Hibernate, Jackson, JUnit) | ||
| + | * extensibilitate fără modificarea codului | ||
| + | * comportament configurabil prin metadate (adnotări) | ||
| + | |||
| + | Fără Reflection: | ||
| + | * multe framework-uri moderne nu ar fi posibile | ||
| + | * codul ar fi rigid și greu de extins | ||
| + | |||
| + | ====Reflection ≠ programare normală==== | ||
| + | |||
| + | Reflection **nu înlocuiește** programarea clasică. Este: | ||
| + | * mai lentă | ||
| + | * mai greu de citit | ||
| + | * mai periculoasă | ||
| + | |||
| + | De aceea este folosită: | ||
| + | * **rar** | ||
| + | * **controlat** | ||
| + | * **în infrastructură**, nu în logica de business | ||
| + | |||
| + | ====Obiectul Class – punctul de intrare==== | ||
| + | |||
| + | Orice operație de Reflection pornește de la un obiect de tip ''Class<?>''. | ||
| + | |||
| + | Există trei modalități principale de a obține un ''Class'': | ||
| + | |||
| + | <code java> | ||
| + | Class<String> c1 = String.class; | ||
| + | Class<?> c2 = "hello".getClass(); | ||
| + | Class<?> c3 = Class.forName("java.lang.String"); | ||
| + | </code> | ||
| + | |||
| + | Toate cele trei referințe indică **aceeași clasă la runtime**. | ||
| + | |||
| + | ====Inspectarea structurii unei clase==== | ||
| + | |||
| + | Presupunem următoarea clasă: | ||
| + | <code java> | ||
| + | public class Person { | ||
| + | private String name; | ||
| + | private int age; | ||
| + | |||
| + | public Person(String name, int age) { } | ||
| + | |||
| + | public String getName() { return name; } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | **Câmpuri** | ||
| + | <code java> | ||
| + | Field[] fields = Person.class.getDeclaredFields(); | ||
| + | |||
| + | for (Field f : fields) { | ||
| + | System.out.println(f.getName()); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <spoiler output> | ||
| + | <code bash> | ||
| + | name | ||
| + | age | ||
| + | </code> | ||
| + | </spoiler> | ||
| + | |||
| + | **Metode** | ||
| + | <code java> | ||
| + | Method[] methods = Person.class.getDeclaredMethods(); | ||
| + | |||
| + | for (Method m : methods) { | ||
| + | System.out.println(m.getName()); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | **Constructori** | ||
| + | <code java> | ||
| + | Constructor<?>[] constructors = Person.class.getDeclaredConstructors(); | ||
| + | |||
| + | for (Constructor<?> c : constructors) { | ||
| + | System.out.println(c); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ====Accesarea membrilor private==== | ||
| + | |||
| + | Reflection permite accesarea membrilor ''private'', **ocolind encapsularea**. | ||
| + | |||
| + | <code java> | ||
| + | Person p = new Person("Ana", 30); | ||
| + | |||
| + | Field field = Person.class.getDeclaredField("name"); | ||
| + | field.setAccessible(true); | ||
| + | |||
| + | String value = (String) field.get(p); | ||
| + | System.out.println(value); | ||
| + | </code> | ||
| + | |||
| + | <note warning>Acest lucru rupe principiile OOP și trebuie folosit **doar în infrastructură sau framework-uri**. </note> | ||
| + | |||
| + | ====Invocarea metodelor prin Reflection==== | ||
| + | |||
| + | <code java> | ||
| + | Method m = Person.class.getDeclaredMethod("getName"); | ||
| + | String result = (String) m.invoke(p); | ||
| + | |||
| + | System.out.println(result); | ||
| + | </code> | ||
| + | |||
| + | Reflection permite: | ||
| + | * apelarea metodelor necunoscute la compilare | ||
| + | * decuplarea codului | ||
| + | |||
| + | ====Crearea obiectelor dinamic==== | ||
| + | |||
| + | <code java> | ||
| + | Constructor<Person> ctor = | ||
| + | Person.class.getConstructor(String.class, int.class); | ||
| + | |||
| + | Person p = ctor.newInstance("Ion", 25); | ||
| + | </code> | ||
| + | |||
| + | Acesta este mecanismul folosit de: | ||
| + | * Jackson | ||
| + | * Hibernate | ||
| + | * Spring | ||
| + | |||
| + | ====Reflection și Adnotări==== | ||
| + | |||
| + | Reflection este mecanismul prin care **adnotările RUNTIME sunt citite**. | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | @Retention(RetentionPolicy.RUNTIME) | ||
| + | @interface Info { | ||
| + | String value(); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <code java> | ||
| + | @Retention(RetentionPolicy.RUNTIME) | ||
| + | @interface Info { | ||
| + | String value(); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Citire la runtime: | ||
| + | <code java> | ||
| + | Info info = Person.class.getAnnotation(Info.class); | ||
| + | System.out.println(info.value()); | ||
| + | </code> | ||
| + | |||
| + | ====Reflection în framework-uri reale==== | ||
| + | |||
| + | Reflection este folosit pentru: | ||
| + | * scanarea claselor | ||
| + | * identificarea adnotărilor | ||
| + | * apelarea metodelor automat | ||
| + | |||
| + | Exemple: | ||
| + | * JUnit → caută metode cu @Test | ||
| + | * Jackson → caută câmpuri și constructori | ||
| + | * Spring → injectează dependențe | ||
| + | |||
| + | ====Costuri și riscuri==== | ||
| + | |||
| + | Reflection: | ||
| + | * este mai lentă decât apelurile directe | ||
| + | * elimină verificările de tip la compilare | ||
| + | * poate produce erori doar la runtime | ||
| + | |||
| + | <note tip> | ||
| + | Reflection este o unealtă de **framework**, nu de business logic. În practică, o vom folosi **doar la testarea codului nostru**. | ||
| + | </note> | ||
| + | |||
| + | |||
| + | =====🔍 Assertions===== | ||
| + | |||
| + | ====Ce sunt assertions?==== | ||
| + | |||
| + | **Assertions** sunt afirmații folosite pentru a verifica **ipoteze interne** ale programului în timpul execuției. Ele exprimă condiții care **ar trebui să fie mereu adevărate** dacă programul funcționează corect. | ||
| + | |||
| + | Aserțiile **nu sunt mecanisme de tratare a erorilor** și **nu înlocuiesc excepțiile**. | ||
| + | |||
| + | ====Scopul assertions==== | ||
| + | |||
| + | Assertions sunt folosite pentru: | ||
| + | * detectarea bug-urilor logice | ||
| + | * validarea presupunerilor interne | ||
| + | * documentarea implicită a codului | ||
| + | |||
| + | ====Sintaxa de bază==== | ||
| + | |||
| + | <code java> | ||
| + | assert condition; | ||
| + | </code> | ||
| + | |||
| + | Sau cu mesaj: | ||
| + | |||
| + | <code java> | ||
| + | assert condition : "Mesaj de eroare"; | ||
| + | </code> | ||
| + | |||
| + | <note important> | ||
| + | Dacă ''condition'' este ''false'', JVM aruncă un ''AssertionError''. | ||
| + | </note> | ||
| + | |||
| + | ====Exemplu simplu==== | ||
| + | |||
| + | <code java> | ||
| + | public int divide(int a, int b) { | ||
| + | assert b != 0 : "b must not be zero"; | ||
| + | return a / b; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Dacă ''b'' este ''0'', programul va eșua **doar dacă assertions sunt activate**. | ||
| + | |||
| + | ====Activarea assertions==== | ||
| + | |||
| + | Assertions sunt **dezactivate implicit**. | ||
| + | |||
| + | Se activează la rulare: | ||
| + | <code java> | ||
| + | java -ea Main | ||
| + | </code> | ||
| + | |||
| + | <note important> Dacă assertions nu sunt activate, **codul assert nu se execută deloc**. </note> | ||
| + | |||
| + | ====Assertions vs Excepții==== | ||
| + | |||
| + | ^ Assertions ^ Excepții ^ | ||
| + | | pentru bug-uri interne | pentru erori de runtime | | ||
| + | | pot fi dezactivate | sunt mereu active | | ||
| + | | nu se folosesc pentru input user | validează input extern | | ||
| + | |||
| + | |||
| + | =====🔬 Unit Testing===== | ||
| + | |||
| + | ====Ce este Unit Testing?==== | ||
| + | |||
| + | **Unit testing** reprezintă procesul de testare a **celor mai mici unități de cod** (de regulă metode sau clase) în **izolare** față de restul aplicației. | ||
| + | |||
| + | Scopul este verificarea faptului că: | ||
| + | * o metodă produce rezultatul corect | ||
| + | * pentru aceleași intrări, comportamentul este determinist | ||
| + | * modificările ulterioare nu introduc regresii | ||
| + | |||
| + | Unit testing-ul se concentrează pe **corectitudinea logicii**, nu pe integrarea componentelor. | ||
| + | |||
| + | ====Ce este o „unitate”?==== | ||
| + | |||
| + | În Java, o unitate este de obicei: | ||
| + | * o metodă publică | ||
| + | * o clasă cu responsabilitate clară | ||
| + | * o funcție pură (fără dependențe externe) | ||
| + | |||
| + | Exemplu de unitate bună: | ||
| + | <code java> | ||
| + | int add(int a, int b) | ||
| + | </code> | ||
| + | |||
| + | Exemplu de unitate slabă: | ||
| + | <code java> | ||
| + | void saveUserAndSendEmailAndLog() | ||
| + | </code> | ||
| + | |||
| + | ====Caracteristicile unui test unitar bun==== | ||
| + | |||
| + | Un test unitar corect este: | ||
| + | * rapid (milisecunde) | ||
| + | * determinist (același rezultat de fiecare dată) | ||
| + | * independent (nu depinde de alte teste) | ||
| + | * izolat (fără DB, rețea, fișiere) | ||
| + | |||
| + | <note important> Dacă testul depinde de o bază de date sau de o cerere HTTP către orice resursă online, atunci **nu mai este unit test**. </note> | ||
| + | |||
| + | ====Framework standard: JUnit==== | ||
| + | |||
| + | În Java, standardul de facto pentru unit testing este JUnit. | ||
| + | |||
| + | Un test JUnit este: | ||
| + | * o metodă | ||
| + | * marcată cu @Test | ||
| + | * care validează un comportament folosind assertions | ||
| + | |||
| + | ====Structura unui test JUnit==== | ||
| + | |||
| + | <code java> | ||
| + | class CalculatorTest { | ||
| + | |||
| + | @Test | ||
| + | void additionWorks() { | ||
| + | Calculator c = new Calculator(); | ||
| + | int result = c.add(2, 3); | ||
| + | |||
| + | assertEquals(5, result); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Elemente cheie: | ||
| + | * ''@Test'' marchează metoda ca test | ||
| + | * ''assertEquals'' verifică rezultatul | ||
| + | * dacă assert-ul eșuează → testul eșuează | ||
| + | |||
| + | ====Assertions în JUnit==== | ||
| + | |||
| + | JUnit oferă propriul set de assertions: | ||
| + | <code java> | ||
| + | assertEquals(expected, actual); | ||
| + | assertTrue(condition); | ||
| + | assertFalse(condition); | ||
| + | assertNotNull(object); | ||
| + | assertThrows(Exception.class, () -> code); | ||
| + | </code> | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | assertThrows(IllegalArgumentException.class, | ||
| + | () -> calculator.divide(10, 0)); | ||
| + | </code> | ||
| + | |||
| + | ====Pattern-ul AAA (Arrange – Act – Assert)==== | ||
| + | |||
| + | Majoritatea testelor respectă structura: | ||
| + | <code java> | ||
| + | @Test | ||
| + | void exampleTest() { | ||
| + | // Arrange | ||
| + | Calculator c = new Calculator(); | ||
| + | |||
| + | // Act | ||
| + | int result = c.add(2, 3); | ||
| + | |||
| + | // Assert | ||
| + | assertEquals(5, result); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Această structură: | ||
| + | * clarifică intenția testului | ||
| + | * îmbunătățește lizibilitatea | ||
| + | * ușurează mentenanța | ||
| + | |||
| + | ====Setări comune: @BeforeEach / @AfterEach==== | ||
| + | |||
| + | Pentru inițializare și cleanup: | ||
| + | <code java> | ||
| + | @BeforeEach | ||
| + | void setup() { | ||
| + | calculator = new Calculator(); | ||
| + | } | ||
| + | |||
| + | @AfterEach | ||
| + | void clear() { | ||
| + | calculator.reset(); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Aceste metode: | ||
| + | * rulează **înainte / după fiecare test** | ||
| + | * evită duplicarea codului | ||
| + | * păstrează testele independente | ||
| + | |||
| + | ====Unit testing vs Assertions==== | ||
| + | |||
| + | ^ Unit Testing ^ Assertions ^ | ||
| + | | testare automată | verificări interne | | ||
| + | | rulează mereu | pot fi dezactivate | | ||
| + | | parte din CI | doar pentru debug | | ||
| + | | validează API | validează ipoteze | | ||
| + | |||
| + | <note warning> | ||
| + | Interacțiunile cu: | ||
| + | * baze de date | ||
| + | * rețea | ||
| + | * filesystem | ||
| + | * servicii externe | ||
| + | |||
| + | Acestea aparțin: | ||
| + | * integration testing | ||
| + | * system testing | ||
| + | </note> | ||
| + | |||
| + | =====🧪 Integration Testing & Mocking===== | ||
| + | |||
| + | ====Ce este Integration Testing?==== | ||
| + | |||
| + | **Integration testing** verifică dacă **mai multe componente ale aplicației funcționează corect împreună**, nu doar în izolare. | ||
| + | |||
| + | Spre deosebire de unit testing: | ||
| + | * nu testăm o metodă izolată | ||
| + | * testăm colaborarea dintre clase, module sau layere | ||
| + | |||
| + | Exemple: | ||
| + | * service + repository | ||
| + | * controller + service | ||
| + | * service + client HTTP | ||
| + | |||
| + | ====Unit Tests vs Integration Tests==== | ||
| + | |||
| + | ^ Unit Testing ^ Integration Testing ^ | ||
| + | | testează o unitate | testează colaborarea | | ||
| + | | izolat | dependențe reale | | ||
| + | | foarte rapid | mai lent | | ||
| + | | fără infrastructură | poate necesita DB / config | | ||
| + | |||
| + | <note important> Integration testing **nu înseamnă** „testăm tot sistemul”. </note> | ||
| + | |||
| + | ====Niveluri de integrare==== | ||
| + | |||
| + | În practică există mai multe grade: | ||
| + | * integrare **parțială** (mock pentru unele dependențe) | ||
| + | * integrare **reală** (DB real, filesystem, config) | ||
| + | |||
| + | Laboratoarele se concentrează de obicei pe **integrare parțială**, pentru control și viteză. | ||
| + | |||
| + | ====De ce avem nevoie de Mocking?==== | ||
| + | |||
| + | Mocking permite **simularea comportamentului unei dependențe**, fără a o executa efectiv. | ||
| + | |||
| + | Folosim mocking când: | ||
| + | * dependența este lentă | ||
| + | * dependența este externă | ||
| + | * vrem să controlăm exact comportamentul | ||
| + | |||
| + | ====Mockito – framework de mocking==== | ||
| + | |||
| + | **Mockito** este standardul de facto în Java pentru mocking. | ||
| + | |||
| + | Cu Mockito putem: | ||
| + | * crea obiecte mock | ||
| + | * defini comportamentul lor | ||
| + | * verifica interacțiunile | ||
| + | |||
| + | ====Exemplu simplu de mocking==== | ||
| + | |||
| + | <code java> | ||
| + | UserRepository repo = mock(UserRepository.class); | ||
| + | |||
| + | when(repo.findById(1L)) | ||
| + | .thenReturn(new User("Ana")); | ||
| + | </code> | ||
| + | |||
| + | Ce se întâmplă: | ||
| + | * ''repo'' NU este implementare reală | ||
| + | * metoda ''findById'' este simulată | ||
| + | * nu există DB implicat | ||
| + | |||
| + | ====Integrare parțială cu Mockito==== | ||
| + | |||
| + | <code java> | ||
| + | class UserServiceTest { | ||
| + | |||
| + | UserRepository repo = mock(UserRepository.class); | ||
| + | UserService service = new UserService(repo); | ||
| + | |||
| + | @Test | ||
| + | void returnsUserName() { | ||
| + | when(repo.findById(1L)) | ||
| + | .thenReturn(new User("Ana")); | ||
| + | |||
| + | String name = service.getUserName(1L); | ||
| + | |||
| + | assertEquals("Ana", name); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Aici testăm: | ||
| + | * logica din ''UserService'' | ||
| + | * integrarea cu repository-ul (simulat) | ||
| + | |||
| + | ====Verificarea interacțiunilor==== | ||
| + | |||
| + | Mockito permite verificarea apelurilor: | ||
| + | <code java> | ||
| + | verify(repo).findById(1L); | ||
| + | </code> | ||
| + | |||
| + | Acest lucru este util când: | ||
| + | * ordinea apelurilor contează | ||
| + | * vrem să verificăm efecte indirecte | ||
| + | |||
| + | ====Observații importante==== | ||
| + | |||
| + | * Nu se face mock pentru **clasa testată** | ||
| + | * Se fac mock doar pentru **dependințe** | ||
| + | * Mock-urile nu trebuie suprafolosite | ||
| + | * Testele cu prea multe mock-uri devin fragile | ||
| + | |||
| + | ====[Nice to know] Folosirea lui var în teste==== | ||
| + | |||
| + | În Java, ''var'' permite **inferarea tipului la compilare**, reducând zgomotul sintactic, fără a pierde siguranța tipurilor. | ||
| + | |||
| + | |||
| + | <note important> ''var'' **nu este tip dinamic**. Tipul este determinat **o singură dată**, la compilare. </note> | ||
| + | |||
| + | ===Exemplu cu Mockito + var=== | ||
| + | |||
| + | Fără ''var'': | ||
| + | <code java> | ||
| + | UserRepository repository = mock(UserRepository.class); | ||
| + | UserService service = new UserService(repository); | ||
| + | </code> | ||
| + | |||
| + | Cu var: | ||
| + | <code java> | ||
| + | var repository = mock(UserRepository.class); | ||
| + | var service = new UserService(repository); | ||
| + | </code> | ||
| + | |||
| + | Codul este: | ||
| + | * mai scurt | ||
| + | * la fel de sigur | ||
| + | * mai lizibil în contextul testelor | ||
| + | |||
| + | <note important> | ||
| + | Deși ''var'' poate fi util în scrierea rapidă a codului, trebuie să avem grijă să nu îl folosim în următoarele situații: | ||
| + | * câmpuri | ||
| + | * parametri | ||
| + | * API public | ||
| + | * când tipul nu e clar din context | ||
| + | </note> | ||
| + | |||
| + | =====[Nice to know] 🪵 Logging în Java ===== | ||
| + | |||
| + | ==== Ce este logging-ul? ==== | ||
| + | |||
| + | Logging-ul reprezintă mecanismul prin care o aplicație înregistrează informații despre: | ||
| + | * execuția codului | ||
| + | * evenimente importante | ||
| + | * erori sau situații neașteptate | ||
| + | |||
| + | Spre deosebire de ''System.out.println'', logging-ul: | ||
| + | * poate fi configurat (nivel, format, destinație) | ||
| + | * poate fi dezactivat fără a modifica codul | ||
| + | * este standard în aplicații profesionale | ||
| + | |||
| + | ====De ce NU folosim System.out.println?==== | ||
| + | |||
| + | Instrucțiunea ''System.out.println'': | ||
| + | * nu are niveluri (info, warning, error) | ||
| + | * nu poate fi filtrat pe baza nivelelor | ||
| + | * nu este potrivit pentru producție | ||
| + | |||
| + | ====Logging standard în Java (java.util.logging)==== | ||
| + | |||
| + | Java oferă un mecanism de logging în JDK, fără biblioteci externe ''java.util.logging''. | ||
| + | |||
| + | Exemplu minimal: | ||
| + | <code java> | ||
| + | import java.util.logging.Logger; | ||
| + | |||
| + | public class Example { | ||
| + | private static final Logger logger = | ||
| + | Logger.getLogger(Example.class.getName()); | ||
| + | |||
| + | void run() { | ||
| + | logger.info("Application started"); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Niveluri de logging (cele mai uzuale) | ||
| + | |||
| + | * ''SEVERE'' / ''ERROR'' – erori critice | ||
| + | * ''WARNING'' – situații problematice | ||
| + | * ''INFO'' – informații generale | ||
| + | * ''FINE'' / ''DEBUG'' – detalii de execuție | ||
| + | |||
| + | <note important> Logging-ul NU trebuie să schimbe comportamentul aplicației, **doar să-l observe**. </note> | ||
| + | |||
| + | ====Logging folosind Log4j==== | ||
| + | |||
| + | Pe lângă mecanismul de logging din JDK (java.util.logging), în practică este foarte des întâlnită biblioteca Apache Log4j. | ||
| + | |||
| + | Log4j este: | ||
| + | * o bibliotecă externă de logging (nu face parte din JDK) | ||
| + | * mai flexibilă și mai configurabilă decât logging-ul standard din Java | ||
| + | * folosită frecvent în aplicații enterprise și framework-uri | ||
| + | |||
| + | Un exemplu de inițializare: | ||
| + | <code java> | ||
| + | import org.apache.logging.log4j.LogManager; | ||
| + | import org.apache.logging.log4j.Logger; | ||
| + | |||
| + | public class Example { | ||
| + | private static final Logger logger = | ||
| + | LogManager.getLogger(Example.class); | ||
| + | |||
| + | void run() { | ||
| + | logger.info("Application started"); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <note warning> Log4j a fost implicat în vulnerabilitatea **Log4Shell (2021)**, una dintre cele mai grave din istoria Java. Acest lucru NU înseamnă că logging-ul este periculos, ci că **bibliotecile trebuie actualizate și verificate pentru vulnerabilități**. În prezent, există utilitare care fac acest lucru automat cum ar fi [[https://snyk.io/|Snyk]]</note> | ||
| + | |||
| + | <note tip> | ||
| + | * ''java.util.logging'' → suficient pentru laborator și aplicații simple | ||
| + | * Log4j → standard de facto în aplicații mari | ||
| + | * ''System.out.println'' → nu este logging | ||
| + | </note> | ||
| + | |||
| + | =====[Optional] 🗺️ Software Development Methodologies===== | ||
| + | |||
| + | În urma paradigmei OOP, au fost concepute mai multe metodologii care pot augmenta performanța sau anumite abilități ale unui proiect conform necesităților. | ||
| + | |||
| + | De exemplu, **Data Oriented Design** (DOD) este o paradigmă care ajută programarea folosind obiecte prin folosirea **cache-ului**. Practic, clasele sunt refactorizate astfel încât să țină cont de mărimea cache-ului din procesor pentru a scădea drastic timpii de acces. Această paradigmă începe să fie destul de folosită în mediile în care viteza este importantă, cum ar fi programarea jocurilor video, drept urmare chiar și engine-ul Unity a fost rescris astfel încât să folosească DOD (găsiți articolul [[https://unity.com/dots|aici]]. | ||
| + | |||
| + | <note tip> | ||
| + | Vă recomandăm să parcurgeți secvențe din acest videoclip de la [[https://www.youtube.com/watch?v=yy8jQgmhbAU|CppCon]]. | ||
| + | </note> | ||
| + | |||
| + | O altă metodologie utilă este **Test Driven Development** (TDD) în care accentul este pus pe dezvoltarea testelor înainte de a dezvolta codul propriu-zis. Practic, după etapa de ideație, putem trece la crearea testelor care vor defini comportamentul corect al programului nostru, apoi vom dezvolta codul necesar pentru a putea trece cu succes peste testele scrise de noi. Puteți citi mai multe despre această abordare [[https://agilealliance.org/glossary/tdd/|aici]]. | ||
| + | |||
| + | De asemenea, o altă metodologie folosită este **Domain Driven Design** (DDD) care este o abordare de proiectare software în care structura aplicației este ghidată de domeniul de business, iar logica esențială este modelată prin concepte clare (entități, valori, agregate) definite împreună cu experți din domeniu, folosind un limbaj comun (ubiquitous language) pentru a menține codul aliniat cu realitatea problemei. Această abordare este detaliată în articolul scris de Redis [[https://redis.io/glossary/domain-driven-design-ddd/|aici]]. | ||
| + | =====Exerciții===== | ||
| + | |||
| + | <note tip> | ||
| + | Pentru exercițiile de mai jos va trebui să vă creați propriile proiecte Maven. Vă recomandăm să luați dependențele necesare de pe [[https://mvnrepository.com/repos/central|Maven Repository]], concret JUnit și Mockito. | ||
| + | |||
| + | De asemenea, vă recomandăm să parcurgeți documentația Mockito și JUnit sau să folosiți ghiduri de pe site-uri de specialitate cum ar fi [[https://www.baeldung.com/mockito-series|Baeldung Mockito series]]. | ||
| + | </note> | ||
| + | |||
| + | <note important> | ||
| + | Acest laborator nu va fi încărcat pe Devmind Code, deoarece va fi verificat manual de către laborantul vostru. | ||
| + | </note> | ||
| + | ====Task 1 – Unit Testing cu coverage ≥ 80% (6p)==== | ||
| + | |||
| + | Se dă următoarea clasă de producție, care modelează un serviciu simplu de procesare a comenzilor. Codul conține: | ||
| + | * ramificații (if) | ||
| + | * excepții | ||
| + | * assertions | ||
| + | * logică dependentă de input | ||
| + | |||
| + | <code java> | ||
| + | public class OrderService { | ||
| + | |||
| + | public double calculateTotal(double price, int quantity) { | ||
| + | assert price >= 0 : "Price must be non-negative"; | ||
| + | assert quantity > 0 : "Quantity must be positive"; | ||
| + | |||
| + | return price * quantity; | ||
| + | } | ||
| + | |||
| + | public double applyDiscount(double total, double discountPercent) { | ||
| + | if (discountPercent < 0 || discountPercent > 50) { | ||
| + | throw new IllegalArgumentException("Invalid discount"); | ||
| + | } | ||
| + | |||
| + | return total - (total * discountPercent / 100); | ||
| + | } | ||
| + | |||
| + | public String categorizeOrder(double total) { | ||
| + | if (total < 100) { | ||
| + | return "SMALL"; | ||
| + | } else if (total < 500) { | ||
| + | return "MEDIUM"; | ||
| + | } else { | ||
| + | return "LARGE"; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public boolean isFreeShipping(double total) { | ||
| + | return total >= 200; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Scrieți teste unitare folosind JUnit 5 astfel încât: | ||
| + | * coverage-ul clasei să fie ≥ 80% | ||
| + | * toate ramurile importante să fie testate | ||
| + | * testele să fie clare, independente și deterministe | ||
| + | |||
| + | <note tip> | ||
| + | În industrie, testarea este importantă, iar o metrică consacrată în acest sens este ca 80% din cod să fie acoperit de teste. Pentru a verifica cât cod este acoperit de testele voastre puteți folosi [[https://www.jetbrains.com/help/idea/code-coverage.html|ghidul oficial IntelliJ]] sau să întrebați laborantul. | ||
| + | </note> | ||
| + | |||
| + | <note important> | ||
| + | Pentru a testa cât mai mult cod, trebuie să generați teste valide și invalide pentru codul dat, astfel încât să testați toate ramurile logice. | ||
| + | </note> | ||
| + | |||
| + | ====Task 2 - Integration Test + Mocking (4p)==== | ||
| + | |||
| + | Se dau următoarele clase: | ||
| + | <code java User.java> | ||
| + | public class User { | ||
| + | private final String name; | ||
| + | |||
| + | public User(String name) { | ||
| + | this.name = name; | ||
| + | } | ||
| + | |||
| + | public String getName() { | ||
| + | return name; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <code java UserRepository.java> | ||
| + | public interface UserRepository { | ||
| + | User findById(long id); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <code java UserService.java> | ||
| + | public class UserService { | ||
| + | |||
| + | private final UserRepository repository; | ||
| + | |||
| + | public UserService(UserRepository repository) { | ||
| + | this.repository = repository; | ||
| + | } | ||
| + | |||
| + | public String getUserName(long id) { | ||
| + | User user = repository.findById(id); | ||
| + | return user.getName(); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | - Scrieți un test pentru UserService folosind Mockito. | ||
| + | - Mock-uiți UserRepository astfel încât: | ||
| + | * pentru id = 1, să returneze un User("Ana") | ||
| + | - Verificați că metoda getUserName(1) returnează "Ana". | ||
| + | | ||
| + | Puteți folosi ''var'' pentru: | ||
| + | * repository | ||
| + | * service | ||
| + | * rezultatul final | ||
| + | <note tip>Denumirea și scopul claselor de mai sus respectă un standard folosit de framework-ul Spring.</note> | ||
| + | ===== Resurse și link-uri utile ===== | ||
| + | * [[https://site.mockito.org/ | Mockito]] | ||
| + | * [[https://www.atlassian.com/continuous-delivery/software-testing/types-of-software-testing | Different types of software testing]] | ||
| + | * [[https://www.baeldung.com/java-logging-intro | Introduction to Java logging - Baeldung]] | ||
| + | * [[https://softwareengineering.stackexchange.com/questions/123956/why-should-i-use-reflection | Why should I use reflection and when? - StackOverflow]] | ||