This shows you the differences between two versions of the page.
|
ss:laboratoare:04 [2025/02/26 00:15] jan.vaduva |
ss:laboratoare:04 [2026/03/23 18:50] (current) ciprian.popescu0411 [Partea 4: Protejarea branch-ului ''main''] |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Laborator 4: Implementarea testării unitare și a acoperirii codului ====== | + | ====== Laborator 4: Implementarea unui pipeline CI/CD pentru un proiect embedded ====== |
| + | |||
| + | În laboratorul anterior am configurat mediul de lucru și am testat comunicarea între ESP32-CAM și clientul Python. În acest laborator vom automatiza procesul de verificare a codului folosind un pipeline **CI/CD (Continuous Integration / Continuous Delivery)** cu **GitHub Actions**. | ||
| ===== Obiective ===== | ===== Obiective ===== | ||
| - | * Implementarea testării unitare pentru funcționalitățile aplicației dezvoltate în laboratoarele anterioare | ||
| - | * Măsurarea și analiza acoperirii codului pentru îmbunătățirea calității software | ||
| - | * Integrarea testelor unitare în pipeline-ul CI/CD | ||
| - | ===== Cerințe tehnologice ===== | + | - Înțelegerea conceptelor de CI/CD și a avantajelor lor în proiecte embedded. |
| - | * **Limbaj și framework de testare**: PyTest (Python) / Jest (JavaScript) / JUnit (Java) | + | - Configurarea unui repository GitHub pentru proiect. |
| - | * **Măsurarea acoperirii codului**: Coverage.py, Istanbul, JaCoCo | + | - Crearea unui workflow GitHub Actions care: |
| - | * **CI/CD Pipeline**: GitHub Actions/GitLab CI/CD/Jenkins pentru rularea testelor automat | + | * Compilează firmware-ul ESP32 automat la fiecare push. |
| - | * **Raportare și analiză**: Codecov/SonarQube pentru interpretarea metricilor de acoperire | + | * Verifică codul Python cu linting și analiză de securitate. |
| + | * Rulează analiza statică pe codul C++ al firmware-ului. | ||
| + | - Salvarea artifactului ''firmware.bin'' pentru deployment. | ||
| + | |||
| + | ===== De ce CI/CD pentru embedded? ===== | ||
| + | |||
| + | Într-un proiect clasic de software, CI/CD este standard. Dar și în embedded este esențial: | ||
| + | |||
| + | * **Preveniți "it compiles on my machine"** — compilarea se face într-un mediu curat, reproductibil. | ||
| + | * **Detectare timpurie** — o eroare de compilare sau un warning critic este prins automat, înainte de upload pe dispozitiv. | ||
| + | * **Artefacte de release** — fișierul ''firmware.bin'' este generat automat și poate fi distribuit fără ca cineva să aibă PlatformIO instalat. | ||
| + | |||
| + | ===== Partea 1: Pregătirea repository-ului de GitHub ===== | ||
| + | |||
| + | ==== 1.1 Inițializare Git ==== | ||
| + | |||
| + | Dacă proiectul nu este deja într-un repository git: | ||
| + | |||
| + | <code bash> | ||
| + | cd /path/to/proiect | ||
| + | git init | ||
| + | </code> | ||
| + | |||
| + | ==== 1.2 Crearea fișierului ''.gitignore'' ==== | ||
| + | |||
| + | Asigurați-vă că fișierul ''.gitignore'' generat de PlatformIO exclude fișierele generate și mediile virtuale: | ||
| + | |||
| + | <code> | ||
| + | # PlatformIO | ||
| + | .pio/ | ||
| + | |||
| + | # Python | ||
| + | .venv/ | ||
| + | __pycache__/ | ||
| + | *.pyc | ||
| + | |||
| + | # IDE | ||
| + | .vscode/ | ||
| + | .idea/ | ||
| + | |||
| + | # OS | ||
| + | .DS_Store | ||
| + | Thumbs.db | ||
| + | </code> | ||
| + | |||
| + | ==== 1.3 Push pe GitHub ==== | ||
| + | |||
| + | Creați un repository nou pe GitHub (fără README), apoi: | ||
| + | |||
| + | <code bash> | ||
| + | git add . | ||
| + | git commit -m "Initial commit" | ||
| + | git branch -M main | ||
| + | git remote add origin https://github.com/UTILIZATOR/NUMELE-REPO.git | ||
| + | git push -u origin main | ||
| + | </code> | ||
| + | |||
| + | <note> | ||
| + | Înlocuiți ''UTILIZATOR'' cu username-ul vostru GitHub și ''NUMELE-REPO'' cu numele repository-ului creat. | ||
| + | </note> | ||
| + | |||
| + | ===== Partea 2: Crearea workflow-ului CI ===== | ||
| + | |||
| + | Workflow-urile GitHub Actions sunt definite ca fișiere YAML în directorul ''.github/workflows/''. | ||
| + | |||
| + | ==== 2.1 Structura fișierului ==== | ||
| + | |||
| + | Creați fișierul ''.github/workflows/ci.yml'': | ||
| + | |||
| + | <file yaml ci.yml> | ||
| + | name: ESP32-CAM CI Pipeline | ||
| + | |||
| + | # Declanșare la push sau pull request pe branch-ul main | ||
| + | on: | ||
| + | push: | ||
| + | branches: [ "main" ] | ||
| + | pull_request: | ||
| + | branches: [ "main" ] | ||
| + | |||
| + | jobs: | ||
| + | # ============================================= | ||
| + | # Job 1: Compilarea firmware-ului ESP32 | ||
| + | # ============================================= | ||
| + | build-firmware: | ||
| + | name: Build ESP32 Firmware | ||
| + | runs-on: ubuntu-latest | ||
| + | |||
| + | steps: | ||
| + | - name: Checkout code | ||
| + | uses: actions/checkout@v4 | ||
| + | |||
| + | - name: Set up Python | ||
| + | uses: actions/setup-python@v5 | ||
| + | with: | ||
| + | python-version: '3.12' | ||
| + | |||
| + | - name: Install PlatformIO | ||
| + | run: pip install platformio | ||
| + | |||
| + | - name: Build Firmware (ESP32-CAM AI-Thinker) | ||
| + | run: pio run -e esp32cam | ||
| + | |||
| + | # Salvăm firmware.bin ca artifact descărcabil | ||
| + | - name: Upload Firmware Artifact | ||
| + | uses: actions/upload-artifact@v4 | ||
| + | with: | ||
| + | name: firmware-esp32cam | ||
| + | path: .pio/build/esp32cam/firmware.bin | ||
| + | |||
| + | # ============================================= | ||
| + | # Job 2: Analiza statică C++ (Firmware) | ||
| + | # ============================================= | ||
| + | analyze-firmware: | ||
| + | name: Static Analysis (C++) | ||
| + | runs-on: ubuntu-latest | ||
| + | |||
| + | steps: | ||
| + | - name: Checkout code | ||
| + | uses: actions/checkout@v4 | ||
| + | |||
| + | - name: Install Cppcheck | ||
| + | run: sudo apt-get install -y cppcheck | ||
| + | |||
| + | - name: Run Cppcheck | ||
| + | run: | | ||
| + | cppcheck --enable=all \ | ||
| + | --inconclusive \ | ||
| + | --std=c++11 \ | ||
| + | -I include/ \ | ||
| + | --suppress=missingIncludeSystem \ | ||
| + | --suppress=missingInclude \ | ||
| + | --suppress=unusedFunction \ | ||
| + | --suppress=cstyleCast \ | ||
| + | --error-exitcode=1 \ | ||
| + | src/ | ||
| + | |||
| + | # ============================================= | ||
| + | # Job 3: Verificarea codului Python (Receiver) | ||
| + | # ============================================= | ||
| + | check-python: | ||
| + | name: Python Lint & Security | ||
| + | runs-on: ubuntu-latest | ||
| + | |||
| + | steps: | ||
| + | - name: Checkout code | ||
| + | uses: actions/checkout@v4 | ||
| + | |||
| + | - name: Set up Python | ||
| + | uses: actions/setup-python@v5 | ||
| + | with: | ||
| + | python-version: '3.12' | ||
| + | |||
| + | - name: Install dependencies | ||
| + | run: | | ||
| + | pip install pylint bandit | ||
| + | # opencv-python-headless se folosește în CI (fără display) | ||
| + | pip install paho-mqtt opencv-python-headless numpy | ||
| + | |||
| + | - name: Lint with Pylint | ||
| + | # || true = soft-fail, pipeline-ul nu eșuează pe warning-uri pylint | ||
| + | run: pylint --disable=C0114,C0115,C0116,C0103 receiver.py || true | ||
| + | |||
| + | - name: Security scan with Bandit | ||
| + | run: bandit receiver.py -ll | ||
| + | </file> | ||
| + | |||
| + | ==== 2.2 Explicarea workflow-ului ==== | ||
| + | |||
| + | Acest workflow definește **3 job-uri independente** care rulează în paralel: | ||
| + | |||
| + | ^ Job ^ Ce face ^ Când eșuează ^ | ||
| + | | ''build-firmware'' | Compilează codul ESP32 cu PlatformIO | Erori de compilare (sintaxă, linkare) | | ||
| + | | ''analyze-firmware'' | Rulează ''cppcheck'' pe codul C++ | Buffer overflows, memory leaks, variabile neinițializate | | ||
| + | | ''check-python'' | Rulează ''pylint'' + ''bandit'' pe receiver | Probleme de stil, parole hardcodate, funcții nesigure | | ||
| + | |||
| + | <code text> | ||
| + | push/PR | ||
| + | │ | ||
| + | ├──► build-firmware ──► firmware.bin (artifact) | ||
| + | │ | ||
| + | ├──► analyze-firmware ──► raport cppcheck | ||
| + | │ | ||
| + | └──► check-python ──► raport pylint + bandit | ||
| + | </code> | ||
| + | |||
| + | ===== Partea 3: Testarea pipeline-ului ===== | ||
| + | |||
| + | ==== 3.1 Commit și push ==== | ||
| + | |||
| + | <code bash> | ||
| + | git add .github/workflows/ci.yml | ||
| + | git commit -m "Add CI pipeline" | ||
| + | git push | ||
| + | </code> | ||
| + | |||
| + | ==== 3.2 Verificare pe GitHub ==== | ||
| + | |||
| + | - Deschideți repository-ul pe GitHub. | ||
| + | - Navigați la tab-ul **Actions**. | ||
| + | - Ar trebui să vedeți workflow-ul ''ESP32-CAM CI Pipeline'' rulând. | ||
| + | - Faceți clic pe el pentru a vedea detaliile fiecărui job. | ||
| + | |||
| + | ==== 3.3 Descărcarea firmware-ului ==== | ||
| + | |||
| + | Dacă build-ul reușește: | ||
| + | |||
| + | - Deschideți run-ul din Actions. | ||
| + | - La secțiunea **Artifacts**, veți vedea ''firmware-esp32cam''. | ||
| + | - Descărcați-l — conține ''firmware.bin'' gata de upload pe dispozitiv. | ||
| + | |||
| + | <note tip> | ||
| + | După descărcare, puteți flash-ui direct pe ESP32-CAM folosind ''esptool'' **fără a avea PlatformIO instalat**: | ||
| + | |||
| + | <code bash> | ||
| + | pip install esptool | ||
| + | esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash 0x10000 firmware.bin | ||
| + | </code> | ||
| + | |||
| + | Pe Windows, înlocuiți ''/dev/ttyUSB0'' cu portul COM corespunzător (ex: ''COM3''). | ||
| + | |||
| + | Acest lucru este util pentru distribuirea firmware-ului către colegi sau pentru deployment pe dispozitive fără a recompila codul sursă. | ||
| + | </note> | ||
| + | |||
| + | ===== Partea 4: Protejarea branch-ului ''main'' ===== | ||
| - | ===== Funcționalități ===== | + | Pentru a preveni merge-ul codului care nu trece CI-ul: |
| - | ==== 1. Scrierea testelor unitare ==== | + | - Pe GitHub: **Settings → Rules → Rulesets → New ruleset → New branch ruleset**. |
| - | * Crearea unui set de teste unitare pentru componentele principale ale aplicației | + | - La **Target branches** adăugați branch-ul ''main''. |
| - | * Utilizarea mocking/stubbing pentru izolarea dependențelor | + | - Activați **Require status checks to pass** și adăugați cele 3 job-uri: ''Build ESP32 Firmware'', ''Static Analysis (C++)'', ''Python Lint & Security''. |
| - | * Testarea funcțiilor critice de procesare a imaginilor, autentificare și interacțiune cu baza de date | + | - Salvați ruleset-ul. |
| - | ==== 2. Măsurarea acoperirii codului ==== | + | <note important> |
| - | * Configurarea unui instrument de măsurare a acoperirii codului | + | Interfața GitHub se poate schimba. Pe unele repository-uri mai vechi, opțiunea se găsește la **Settings → Branches → Add rule**. |
| - | * Generarea rapoartelor de acoperire pentru identificarea zonelor neacoperite de teste | + | </note> |
| - | * Stabilirea unui prag minim de acoperire necesar pentru livrarea codului | + | |
| - | ==== 3. Integrarea testelor în pipeline-ul CI/CD ==== | + | De acum, nimeni nu poate face merge pe ''main'' dacă pipeline-ul eșuează. |
| - | * Configurarea rulării automate a testelor la fiecare commit/pull request | + | |
| - | * Impunerea unui prag minim de acoperire a codului pentru aprobarea unui build | + | |
| - | * Generarea și publicarea rapoartelor de testare și acoperire | + | |
| - | ===== Evaluare ===== | + | ===== Exerciții ===== |
| - | * Implementarea testelor unitare pentru funcționalități cheie (40%) | + | |
| - | * Măsurarea și raportarea acoperirii codului (30%) | + | |
| - | * Integrarea testării și a metricilor de acoperire în pipeline-ul CI/CD (30%) | + | |
| - | ===== Resurse suplimentare ===== | + | - **Provocați un eșec**: Introduceți intenționat o eroare de compilare în ''main.cpp'' (ex: ștergeți un '';''), faceți push pe un branch separat și creați un Pull Request. Observați cum pipeline-ul blochează merge-ul. |
| - | * [https://docs.pytest.org/en/latest/ PyTest Documentation] / [https://jestjs.io Jest Documentation] / [https://junit.org JUnit Documentation] | + | |
| - | * [https://coverage.readthedocs.io Coverage.py] / [https://istanbul.js.org Istanbul Code Coverage] / [https://www.jacoco.org JaCoCo] | + | |
| - | * [https://docs.sonarqube.org/latest/ SonarQube Documentation] / [https://about.codecov.io Codecov] | + | |
| + | - **Adăugați un job de test**: Creați un job nou în workflow care rulează un test simplu Python — de exemplu, verificați că ''receiver.py'' poate fi parsat fără erori de sintaxă: | ||
| + | <code yaml> | ||
| + | - name: Smoke test | ||
| + | run: python -m py_compile receiver.py | ||
| + | </code> | ||
| + | - **Release automat**: Adăugați un pas care creează un GitHub Release cu ''firmware.bin'' atașat, doar când se face push pe un tag (ex: ''v1.0''). Hint: folosiți ''if: startsWith(github.ref, 'refs/tags/')'' și action-ul ''softprops/action-gh-release''. | ||