Automatisera programtest i embeddedsystem

Kvaliteten hos ett embeddedsystem beror framför allt på kvaliteten hos programvaran. Ändå brister det ofta i programvarutesten. Magnus Unemyr, från Atollic AB i Jönköping, tittar här både på problemen och på de verktyg som hans företag har utvecklat för att förenkla programvarutesten.

 

Att lansera en produkt som innehåller programvarufel kan vara mycket kostsamt, speciellt om man tar hänsyn till kostnader för fältuppgraderingar, returnerade produkter från missnöjda kunder, och mjukvaruuppdateringar i serievolymer. Mindre mätbart, men väl så allvarligt, kan företagets skadade rykte vara. Trots detta levereras många inbyggda system utan tillräcklig testning. Det är därför naturligt att mjukvarukvalitet får en allt tydligare fokus vid utveckling av inbyggda system.

Kör i målsystemet
Många av de verktyg för mjukvarutestning som idag finns på marknaden exekverar tester av inbyggd programkod på en PC, vilket är av litet värde då det faktiska målsystemet har många skillnader, såsom andra hårdvaruinterface, timingfaktorer och minnesbegränsningar. Fullt legal ANSI-C kod kan dessutom bete sig annorlunda på en PC-processor, jämfört med när samma kod körs på en microcontroller, t ex en ARM Cortex-M3. Vissa datatyper kan t ex vara signed istället för unsigned som default, vilket kan påverka hur samma programkod beter sig på olika plattformar. Det är därför av stor vikt att köra så många av testerna som möjligt på det faktiska målsystemet för att undvika att tester på en PC går bra, när samma tester inte skulle gjort det i det faktiska målsystemet.
Det har traditionellt varit svårt att hitta mjukvarutestverktyg som kör testerna i målsystemet och har en smidig integration med andra viktiga verktyg, primärt C/C++ kompilator och debugger IDE’n. Ett bra verktyg för test av inbyggd mjukvara bör kunna analysera applikationens källkod, autogenerera en testsvit och sedan ladda ner och köra den automatiskt i målsystemet.


Fig 1. En trivial C-funktion kan testas genom att anropa den många gånger med olika kombinationer av parametervärden. Val av vilka parametervärden som skall väljas för att driva testen på ett optimalt görs av verktyget, även om det manuellt går att redigera (samt importera och exportera) testvektorerna.

När testerna sedan körts i målsystemet, kan en testrapport med hundra procent lyckade tester visa sig vara mindre värdefull än man först tror. Om testsviten bara täcker en liten del av programvarans exekveringsflöde är det ganska meningslöst att den delen av koden testats framgångsrikt, när en majoritet av koden inte testats alls (dvs testfallen täcker bara en del koden). Förståelse för testprocedurernas kvalitet är därför viktig inför produktlansering, vid sidan av de faktiska testresultaten. Lyckligtvis börjar nya avancerade verktyg för testautomation och mätning av testkvalitet för inbyggd mjukvara nu komma på marknaden.

Enhetstester
Ett viktigt element vid testing av mjukvara är enhetstester (“unit tests”). Dessa anropar C-funktioner många gånger med olika kombinatoriska fall av parameter-värden, för att testa ”corner cases” och så många olika kombinationer av exekveringsflöden i funktionen som möjligt.
Med undantag för triviala funktioner, tar det mycket arbetstid att skriva enhetstester manuellt, varför det är ett både dyrt och tråkigt arbete. Dessutom är det inte sannolikt att manuellt skrivna enhetstester verkligen driver tillräckligt många ”corner cases” och kombinationer av exekveringsflöden i funktionen. Stora delar av funktionens möjliga exekveringsflöden kan därför vara otestade, trots en stor manuell arbetsinsats.
Ytterligare ett problem är att mjukvaruprojekt ofta blir försenade, med tidspress som följd mot slutet av projektet. I ett sådant läge fokuserar man oftast på att få färdigt koden, och underhåll av testsviterna kan komma i andra hand. Därvid blir testsviten inte längre synkroniserad med koden den skall testa, och hela testsviten tappar i värde, speciellt i det pressade läget då den skulle varit till störst hjälp.
Det finns många verktyg för enhetstestning av PC-mjukvara, men dessa har begränsat värde för utvecklare av inbyggd mjukvara, då de oftast inte hanterar kompilering, nedladdning till målsystem via JTAG-debugger, eller exekvering av djupt inbäddad kod i det kretskort den är skriven för.
Den önskvärda lösningen är verktyg som skapar testsviter för enhetstester automatiskt, korskompilerar dem för målprocessorn, och därefter laddar ner dem via en JTAG-debugger till målsystemet helt automatiskt. Ännu bättre blir det om denna typ av testsystem integreras väl i den vanliga utvecklingsmiljön (C/C++ – kompilator och debugger IDE:n). Denna typ av verktyg har hittills inte varit vanliga, men nya kraftfulla verktyg som möter dessa krav finns nu på marknaden. Därmed erbjuds utvecklare av mjukvara för inbyggda system lättanvända och helintegrerade lösningar för effektiv testautomation i målsystemet.

Automatgenerering av testsviter
De bästa verktygen för testautomation av mjukvara för inbyggda system kan analysera applikationens källkod och automatgenerera testsviter i C-kod, som sedan automatkompileras och laddas ner till målsystemet med en JTAG-debugger helt automatiskt. Ett exempel är Atollic TrueVERIFIER, som automatgenererar testsviter som anropar C-funktionerna många gånger, med olika kombinationer av parametervärden, för att driva ett stort antal olika varianter av exekveringsflöden.
Fig 1 visar hur en trivial C-funktion kan testas, genom att anropa den många gånger med olika kombinationer av parametervärden. Val av vilka parametervärden som skall väljas för att driva testen på ett optimalt görs av verktyget, även om det manuellt går att redigera (samt importera och exportera) testvektorerna.

Automatisk testexekvering
När testsviten har automatgenererats (i form av C-kod) måste den kompileras, länkas, laddas ner och köras i målsystemet. Många billiga verktyg för enhetstester finns på PC, men de flesta automatgenererar inte testerna och kan bara exekvera testerna ”simulerat” på en PC, vilket har lägre värde för inbyggd programvara.
Då de flesta av dessa verktyg inte integrerar sig med andra centrala verktyg för utveckling av inbyggd mjukvara, kan de vanligen inte användas för smidig testning med koppling till målsystemet.


Fig 2. Ett exempel på hur Atollic TrueANALYZER fungerar.

Ett undantag är Atollic TrueVERIFIER, som integrerar sig sömlöst i C/C++ -kompilator och debuggermiljön (IDE:n), och därmed erbjuder en lättanvänd testlösning där testerna enkelt hålls synkroniserad med koden, många testfall kan automat-genereras för bästa testtäckning, och integrationen ner mot JTAG-debugger och målsystemet är helautomatiserad.

Mätning av testkvalitet
När koden har utvecklats och testats ändras fokus till att förstå vad som faktiskt hände under testningen. Som nämndes här ovan är ett testresultat med hundra procent lyckade testfall relativt meningslöst om testerna t ex bara täcker tjugo procent av koden. Åttio procent av koden är i så fall ändå helt otestad, varvid de lyckade testresultaten därmed får begränsat värde.
Mätning av kodtäckning (”code coverage”) görs med dynamisk exekveringsflödesanalys och kan användas för att studera kodexekveringen under testsessionen. Därmed kan man bedöma hur stor del av det möjliga exekveringsflödet som har körts (eller inte körts) under ett testfall. Man kan därmed kvantitativt mäta kvaliteten på testfallet, med faktiska numeriska mätvärden.
Det finns många olika typer av kodtäckningsanalys, från enkla upp till mycket stringenta typer, såsom MC/DC (modified condition/decision coverage), som ofta används för mätning av testkvalitet vid testning av säkerhetskritisk programvara, t ex i styrsystem till flygplan. Alla typer av projekt kan dock dra nytta av att mäta test-kvaliteten på ett stringent sätt. Med information om var testfallen är dåliga kan man förbättra testprocedurerna tills man får en hög testkvalitet för hela programvaran.
Några olika typer av kodtäckningsanalys är:
* Statement (block) coverage, som enbart mäter vilka eller hur många C-statements (statement blocks) som har körts under en test. Denna typ av analys mäter inte testkvaliteten avseende ändringar i exekveringsflödet.
* Function coverage, som enbart mäter om en funktion har anropats eller inte under en test. Denna typ av analys mäter inte hur många av de olika funktionsanropen som har exekverats, och den säger ingenting om testkvaliteten för funktionen i sig.
* Function call coverage, som mäter vilka eller hur många av de tillgängliga funktionsanropen som faktiskt har exekverats under en test.
* Branch coverage, som är en mer stringent typ av analys. Den kräver att alla kodblock och alla alternativa exekveringstrådar (t ex både if-true och if-false delen i en if-sats) måste ha blivit exekverade under en test. Branch coverage kräver ofta att en kodsektion måste exekveras flera gånger, så att alla alternativa exekveringsriktningar har provats i t ex if- och switch- satser.
Branch coverage kräver visserligen att alla hoppriktningar (t ex både if-true och if-false delen) testas, men tar inte ställning till hur uttrycket evaluerades till true eller false.
* MC/DC (Modified Condition/Decision Coverage), som är en mycket avancerad typ av kodtäckningsanalys. Testkvalitet på MC/DC-nivå krävs i produkter med mycket höga kvalitetskrav. MC/DC utökar kraven från Branch coverage med ytterligare krav, såsom att alla deluttryck i komplexa hoppvillkor måste testas på så sätt att alla deluttryck, oberoende av andra deluttryck, har varit den dominanta faktorn i det sammantagna hoppbeslutet. Detta innebär att kodsektioner vanligen måste exekveras många gånger, så att en sanningstabell med olika kombinatoriska fall av deluttryck blir uppfylld.

Nytt verktyg
Fram tills nyligen har det inte funnits många verktyg tillgängliga för mätning av testkvalitet för inbyggda system. De som har funnits har ofta haft någon av följande problem; testning med enbart svaga typer av kodtäckningsanalys, simulerad testning i PC och inte på det faktiska målsystemet, svårt att använda, mycket kostsamma samt avsaknad av bra integration med övriga verktyg för utveckling av inbyggda system.
Ett exempel på en ny typ av verktyg som ändrar på detta är Atollic TrueANALYZER, som är ett helintegrerat verktyg som stödjer alla typer av kodtäckningsanalys som beskrivits ovan, inklusive MC/DC. En av verktygets starkaste sidor är att analysen körs i det faktiska målsystemet. Nedladdning av kod till målsystemet sker via en vanlig JTAG-debugger, och applikationen körs på det riktiga kretskortet med exekveringsflödesanalys, varvid rigorös kodtäcknings-information sparas för senare studier och kvantitativ analys.
Ett exempel på hur Atollic TrueANALYZER fungerar visas i Fig 2. En trivial kodsektion innehåller tre kodblock: ett rött kodblock körs alltid, ett grönt kodblock körs ibland beroende på hoppbeslutet i if-satsen, och ett blått kodblock som alltid körs. Koden kan visualiseras som ett exekveringsflödesdiagram, vilket underlättar förståelsen.


Fig 3. Med Atollic TrueANALYZER kan de olika typerna av kodtäckningsmätning köras i målsystemet med endast två musklick.

Hoppbeslutet i if-satsen styr vilken av de två möjliga exekveringstrådarna som väljs; antingen direkt från det röda kodblocket till det blå; eller från det röda kodblocket, via det gröna kodblocket, till det blå kodblocket.
Fig 3 visar hur lätt det är att använda Atollic TrueANALYZER, då de olika typerna av kodtäckningsmätning kan köras i målsystemet med endast två musklick, inklusive statement/block coverage, function coverage, function call coverage, branch coverage och MC/DC coverage.
Atollic TrueVERIFIER och Atollic TrueANALYZER är helintegrerade i Atollic’s C/C++ kompilator och debugger IDE; Atollic TrueSTUDIO.
Magnus Unemyr, Atollic AB
 

Comments are closed.