ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 52
Abonament PDF

O strategie de testare pentru aplicații C embedded în timp real

Alexandru Bolboacă
Agile Coach and Trainer, with a focus on technical practices
@Mozaic Works



PROGRAMARE

În ultimele șase luni, am lucrat cu un client care dezvoltă dispozitive hardware. Provocarea cu care s-a confruntat a constat în plângerile clienților, din cauza bugurilor apărute după câteva luni de la instalare la unul dintre produsele lor. Bineînțeles, cauza respectivelor buguri necesită o investigare minuțioasă.

Contextul

Dispozitivul hardware rulează un sistem de operare în timp real, care este în momentul de față în tranziție de la un sistem proprietar la FreeRTOS. Majoritatea codului este scris în C și utilizează apeluri de sistem pentru crearea și sincronizarea firelor de execuție. Codul este vechi, deoarece a rulat aproape 10 ani.

Evaluarea

Primul pas a fost efectuarea unei evaluări tehnice a codului. Aceasta a fost realizată printr-o combinație de: inspecție vizuală, rularea unor instrumente de analiză și intervievarea membrilor echipei. Ca urmare a evaluării, s-a stabilit că a fost bine scris codul, singura problemă fiind implementarea inconsistentă a comunicării dintre procese. Fiecare membru al echipei a recunoscut lipsa testării și faptul că ar fi fost foarte utilă. Așadar, a venit timpul pentru pasul următor.

Strategia de testare

Atunci când definim strategia de testare, începem de obicei de la piramida testelor. Din cauza structurii neobișnuite a programului, strategia a fost mai puțin evidentă decât de obicei.

OS în timp real au o caracteristică: în loc de a scrie programe, dezvoltatorii scriu așa-numitele "taskuri" care rulează în funcție de diverse politici. Taskurile discută utilizând mecanisme IPC (comunicare inter-proces). Am decis să încercăm să scriem teste unitare pentru un task și să vedem dacă putem identifica modele pe care le putem utiliza ulterior în întreaga aplicație.

Dar chiar dacă toate taskurile sunt corecte, pot apărea probleme legate de concurență. Probleme de tip deadlock, starvation etc. nu sunt prevenite de testarea taskurilor. Avem nevoie de alt tip de teste pentru a găsi aceste tipuri de probleme.

O soluție este scrierea unor teste de stres. Ele ar trimite din ce în ce mai multe solicitări, sau ar accelera ceasul sistemului, până în punctul în care ceva cedează. Vom avea apoi șansa de a le investiga. Mă gândesc la tehnica aceasta ca "accelerând timpul", astfel încât să putem găsi problemele care apar în mod normal după șase luni, într-o săptămână sau mai puțin.

Am implementat cu succes testele taskurilor, după cum veți vedea în continuare. Testele de stres sunt deocamdată o idee de lucru, care se poate schimba în timpul implementării.

Testarea taskurilor

Am colaborat cu unul dintre dezvoltatorii clientului, patru ore pe săptămână, în două sesiuni de câte două ore. Am început prin configurarea suitei de teste, utilizând CppUTest, un framework de testare unitară, care poate testa codul C/C++ embedded. Avantajul său constă în consumul foarte scăzut de memorie. Alternativa este GoogleTest și este frameworkul pe care echipa a decis să îl adopte în final. Fiecare dintre ele funcționează bine în contextul dat.

Noi am utilizat abordarea clasică pentru scrierea testelor de caracterizare. Mai întâi, scriem un test care inițializează taskul și vedem ce nu funcționează. Apoi, apelăm o funcție din același test și vedem ce nu funcționează etc. . Ori de câte ori testul nu a reușit, găsim metode de a reduce dependențele, astfel încât testul să fie executat cu succes.

Din fericire, dependențele de OS au fost extrase deja în macro-uri. Diminuarea acestor dependențe a însemnat doar crearea unui fișier .h, includerea lui în test și înlocuirea macro-ului cu orice am avut nevoie pentru test. Acesta a fost un caz fericit; se consumă mult mai mult timp atunci când apelurile de funcții din OS sunt răspândite în cod.

Un alt tip de dependență pe care am întâlnit-o a fost legat de pornirea, sincronizarea și oprirea threadurilor. O opțiune a fost înlocuirea metodelor de pornire, oprire și sincronizare cu funcții fără implementare (execuția rămânând astfel în același thread) sau cu o implementare dummy. Problema a fost că sincronizarea threadurilor este o parte importantă a sistemului.

În final, am decis să separăm codul executat de un thread de codul care sincronizează prin extragere de funcții. Apoi, am înlocuit pentru teste toate funcțiile primitive cu funcții din librăria pthread. Astfel, am putut testa separat coregrafia de implementare.

În linii mari, scrierea testelor pentru taskuri s-a dovedit a fi destul de simplă. Am identificat, de asemenea, modele pe care le putem utiliza pentru a testa toate celelalte taskuri din sistem. Mă aștept încă la câteva probleme particulare, dar le vom rezolva pe fiecare în parte.

Beneficii raportate

După testarea taskului, colaboratorul meu a prezentat tehnica și rezultatele celorlalți membri ai echipei. Aceștia și-au dat seama că există câteva avantaje:

Ei au decis, de asemenea, să utilizeze Google Test în locul CppUTest, deoarece acesta simplifică anumite taskuri și dispozitivul are suficiente resurse pentru a-l executa.

Ce urmează

Execuția testelor pe calculatorul personal este deosebit de importantă pentru feedback, dar nu este suficientă. Acest lucru este adevărat, în deosebi când se utilizează diferite librării pentru testare față de cel de producție (pthread pe computerul local, funcțiile primitive OS pe dispozitiv). Astfel, pasul următor va fi găsirea unei metode de deployment automat al software-ului pe un dispozitiv și execuția testelor taskurilor pe acesta. După ce vom face acest lucru, este destul de ușor să avem un sistem de integrare continuă care compilează, face deployment și execută testele pe dispozitiv. Acest sistem va permite dezvoltatorilor care nu au acces la dispozitiv să își testeze codul în câteva ore, ceea ce este un lucru grozav.

Pentru a finaliza strategia de testare, trebuie să scriem și testele de stres și să efectuăm un orar de execuție. Și, pentru a îmbunătăți conceptul, va trebui să refactorizăm IPC, astfel încât să fie consistent în tot codul.

Concluzii

Scrierea de teste automate pentru aplicații embedded C în timp real este nu doar posibilă, ci și foarte utilă. Tehnicile obișnuite se aplică: piramida testelor, teste de caracterizare și tehnici pentru ruperea dependențelor. O complexitate suplimentară este dată de nevoia de avea două configurații de testare: una pentru calculatorul dezvoltatorului și una pentru dispozitiv, dar este ușor să rezolvăm această problemă cu un sistem bun de compilare.

Cel mai important factor pentru adoptarea testelor este designul existent. Un layer de integrare cu dependențele externe și un mod ușor de injectare a dublelor de testare duc la o diferență mare de eficiența în scrierea primelor teste. C/C++ are un avantaj în acest context: macro-urile reprezintă o metodă de rupere și injectare a dependențelor indisponibilă în alte limbaje. Experiența mea arată tendința C/C++ de a conduce la o separare mai clară a dependentelor comparat cu Java, PHP sau C#, datorită complexității inerente a limbajului, simplificând astfel scrierea testelor pe cod existent.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects