EX4C Systèmes d’exploitation
Tests unitaires
Utilisation dela librairie CUnit
Sébastien Combéfis mardi 3 mars 2015
Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative CommonsAttribution – Pas d’Utilisation Commerciale – Pas de Modification 4.0 International.
Test unitaire
Test indépendant d’unités de code
Une classe, un module, une procédure/fonction...
Un test par unité, sans dépendre des autres
En les supposant correctes ou en définissant des stubs, mocks...
Plusieurs objectifsTrouver rapidement des erreurs
Sécuriser la maintenance
Documenter le code
Utilisés en TDD, et en Extreme Programming (XP)
3
TDD
Test Driven Development (TDD)
Pilotage du développement par les tests
Tests construits sur base des spécifications des unités de code
Tests fonctionnels en mode black-box
Définition des objectifs clairs avant même l’implémentation
Diminue le risque d’erreurs de conception dues à la précipitation
Augmente la confiance en soi du programmeur
Assurance qu’une réfactorisation préserve les fonctionnalités
4
Quelques principes
KISS, “Keep It Simple, Stupid”
Restez simple, ne foncez pas dans la complexité, up to the point
YAGNI, “You Aren’t Gonna Need It”
Ne pas ajouter des fonctionnalités tant que ce n’est pas nécessaire
RERO, “Release Early, Release Often”
Rétroactions développeur/testeur par courtes phases de release
5
Cycle TDD
1 Écrire un premier test
2 Vérifier qu’il échoue
L’implémentation n’étant pas fournie, il doit échouer
3 Écrire l’implémentation pour passer le test
4 Vérifier que le test passe
5 Réfactoriser le code
Améliorer sa qualité tout en gardant les mêmes fonctionnalités
6
Structure d’un test
1 Initialisation (setup)
Initialiser l’unité de code testée et son environnement
2 Exécution
Exécuter l’unité de code testée, et récupérer ses résultats
3 Validation
Vérifier que les résultats produits sont ceux attendus
4 Nettoyage (cleanup)
Restaurer l’état de l’unité de code testée et de l’environnement
7
Bonnes pratiques
Séparer les initialisations communes des spécifiques
Focus de la validation sur l’unité de code testée
Mêmes exigences pour les tests que le code de production
Code de qualité, gestion des cas positifs et négatifs, maintenable
Produire les tests avant l’implémentation
Forcer une définition claire des spécifications
8
Librairie CUnit
Librairie légère pour écrire et exécuter des tests unitaires en C
Propose plusieurs interfaces utilisateurs flexibles
Librairie statique liée avec le code de test
Framework pour structurer les tests et ensemble d’assertions
Plusieurs interfaces d’exécution des testsNon-interactive (résultat en XML)
Interactive en console (ANSI C) ou graphique (curses)
http://cunit.sourceforge.net/
9
Exemple 1 : Factorielle
Spécifications précises de la fonction factorielle
Bien identifier la valeur de retour et les cas particuliers
1 // Computes the f a c t o r i a l o f an i n t e g e r2 //3 // Retu rns the f a c t o r i a l o f n4 // or −1 i f n < 05 i n t f a c t o r i a l ( i n t n ) ;
10
Procédure de test
Tester différents cas possibles et penser aux cas limites
1 #i n c l u d e " f a c t o r i a l . h"2 #i n c l u d e " CUnit / Bas i c . h"34 vo id t e s t _ f a c t o r i a l ( vo id )5 {6 CU_ASSERT ( f a c t o r i a l (−15) == −1);7 CU_ASSERT ( f a c t o r i a l (−1) == −1);89 CU_ASSERT ( f a c t o r i a l ( 0 ) == 1 ) ;
1011 CU_ASSERT ( f a c t o r i a l ( 1 ) == 1 ) ;12 CU_ASSERT ( f a c t o r i a l ( 2 ) == 2 ) ;13 CU_ASSERT ( f a c t o r i a l ( 6 ) == 7 2 0 ) ;14 }
11
Implémentation de la fonction
1 #i n c l u d e " f a c t o r i a l . h"23 i n t f a c t o r i a l ( i n t n )4 {5 i f ( n < 0)6 {7 r e t u r n −1;8 }9
10 i f ( n == 0)11 {12 r e t u r n 1 ;13 }14 r e t u r n n ∗ f a c t o r i a l ( n − 1 ) ;15 }
12
Lancement du test I
1 i n t main ( )2 {3 CU_pSuite p S u i t e = NULL ;45 // I n i t i a l i s e s the CUnit t e s t r e g i s t r y6 i f ( C U _ i n i t i a l i z e _ r e g i s t r y ( ) != CUE_SUCCESS)7 {8 r e t u r n CU_get_error ( ) ;9 }
1011 // Adds a s u i t e to the r e g i s t r y12 p S u i t e = CU_add_suite ( " S u i t e " , NULL , NULL ) ;13 i f ( p S u i t e == NULL)14 {15 CU_c leanup_reg i s t r y ( ) ;16 r e t u r n CU_get_error ( ) ;17 }
13
Lancement du test II
1 // Adds a t e s t to the s u i t e2 i f ( CU_add_test ( pSu i te , " t e s t o f f a c t o r i a l ( ) " ,3 t e s t _ f a c t o r i a l ) == NULL)4 {5 CU_c leanup_reg i s t r y ( ) ;6 r e t u r n CU_get_error ( ) ;7 }89 // Runs a l l the t e s t s u s i n g the CUnit Bas i c i n t e r f a c e
10 CU_basic_set_mode (CU_BRM_VERBOSE) ;11 CU_bas ic_run_tests ( ) ;12 CU_c leanup_reg i s t r y ( ) ;1314 r e t u r n CU_get_error ( ) ;15 }
14
Exécution de CUnit
15
Structure des tests I
Pour les suite de testssetup appelé avant chaque test
tearDown appelé après chaque test
Test Registry
Test Suite 1 ... Test Suite n
Test 11 ... Test 1m Test n1 ... Test nm
16
Structure des tests
Fonctions de base pour gérer et exécuter des tests
Fonction Description
CU_initialize_registry initialise le test registryCU_add_suite() ajoute une suite de tests dans un test registryCU_add_test() ajoute un test dans une suite de testsCU_console_run_tests exécute les tests en mode console (interactif)CU_cleanup_registry nettoie le test registry
Différents modes d’exécution des tests
Mode Fonction Exécution
Automatique CU_automated_run_tests automatique vers fichier XMLBasique CU_basic_run_tests automatique vers sortie standardConsole CU_console_run_tests interactive en consoleCurse CU_curses_run_tests interactive en interface graphique console
17
Assertions
Test d’une condition qui doit valoir TRUE
La fonction de test continue en cas d’échec sauf avec xxx_FATAL
Fonctions CU_PASS et CU_FAIL
Utilisées pour forcer la réussite ou l’échec d’un test
Fonction Test
CU_ASSERT (int expr) ou CU_TEST (int expr) expr 6= 0CU_ASSERT_TRUE (val) ou CU_ASSERT_FALSE (val) val = TRUE ou val = FALSE
* CU_ASSERT_EQUAL (actual, expected) actual = expected* CU_ASSERT_PTR_EQUAL (actual, expected) actual = expected* CU_ASSERT_PTR_NULL (value) value = NULL* CU_ASSERT_STRING_EQUAL (actual, expected) actual = expected* CU_ASSERT_NSTRING_EQUAL (actual, expected) actual [0 :n − 1] = expected [0 :n − 1]* CU_ASSERT_DOUBLE_EQUAL (actual, expected, epsilon) |actual − expected | ≤ |epsilon|
* Existent en version négative : CU_ASSERT_NOT_EQUAL, CU_ASSERT_PTR_NOT_EQUAL...18
Gestion du test registry
Initialisation CU_ErrorCode CU_initialize_registry (void)
CUE_SUCCESS en cas de succès et CUE_NOMEMORY sinon
Nettoyage void CU_cleanup_registry (void)
À faire pour libérer toute la mémoire allouée pour le test registry
19
Gestion des suites de test
Ajout d’une suite de test CU_pSuite CU_add_suite (const char*
strName, CU_InitializeFunc pInit, CU_CleanupFunc pClean)
CUE_SUCCESS, en cas de succèsCUE_NOREGISTRY, si le registre de test n’est pas initialitéCUE_NO_SUITENAME, si le nom de la suite est NULLCUE_DUP_SUITE, si une suite avec le même nom existe déjàCUE_NOMEMORY, si pas assez de mémoire
Une suite de tests doit porter un nom unique
On peut spécifier des fonctions d’initialisation et de nettoyage
20
Gestion des tests
Ajout d’un test CU_pTest CU_add_test (CU_pSuite pSuite, const
char* strName, CU_TestFunc pTestFunc)
CUE_SUCCESS, en cas de succèsCUE_NOSUITE, si la suite de tests est invalide ou NULLCUE_NO_TESTNAME, si le nom du test est NULLCUE_NO_TEST, si la fonction de test est invalide ou NULLCUE_DUP_TEST, si un test avec le même nom existe déjàCUE_NOMEMORY, si pas assez de mémoire
Un test doit porter un nom unique au sein de sa suite
21
Exemple 2 : Pile I
1 typede f s t r u c t s t a c k {2 i n t s i z e ;3 s t r u c t node ;4 } s t a c k ;5 s t r u c t node {6 i n t v a l u e ;7 s t r u c t node ∗ next ;8 } ;9
10 // C r e a t e s a new s t a c k11 //12 // Retu rns a p o i n t e r to the new s t a c k13 // or NULL i f not enough memory14 s t a c k ∗newStack ( ) ;1516 // Pushes a new v a l u e on the s t a c k17 //18 // Retu rns 1 i f the v a l u e has been pushed on the s t a c k19 // or −1 o t h e r w i s e20 i n t push ( s t a c k ∗ s , i n t v a l u e ) ;
22
Exemple 2 : Pile II
1 // Pops a v a l u e from the s t a c k2 //3 // Retu rns the popped v a l u e from the s t a c k i f any4 // or NULL o t h e r w i s e5 vo id pop ( s t a c k ∗ s ) ;67 // Gets the top v a l u e from the s t a c k8 //9 // Retu rns the top v a l u e from the s t a c k i f not empty
10 // or NULL o t h e r w i s e11 vo id top ( s t a c k ∗ s ) ;1213 // Gets the s i z e o f the s t a c k14 //15 // Retu rns the s i z e o f the s t a c k16 i n t s i z e ( s t a c k ∗ s ) ;1718 // F r e e s the s t a c k19 //20 // Retu rns 0 i f the s t a c k has been s u c c e s s f u l l y f r e e d21 // or −1 o t h e r w i s e22 i n t f r e e S t a c k ( s t a c k ∗ s ) ;
23
Initialisation et nettoyage de la suite
1 #i n c l u d e " s t a c k . h"2 #i n c l u d e " CUnit / Bas i c . h"34 s t a t i c s t a c k ∗ s = NULL ;56 i n t i n i t _ s u i t e ( vo id )7 {8 i f ( ( s = newStack ( ) ) != NULL)9 {
10 r e t u r n 0 ;11 }12 r e t u r n −1;13 }1415 i n t c l e a n _ s u i t e ( vo id )16 {17 f r e e S t a c k ( s ) ;18 r e t u r n 0 ;19 }
24
Procédure de test
1 vo id t e s t _ s i z e ( vo id )2 {3 CU_ASSERT_EQUAL ( s i z e ( s ) , 0 ) ;45 i f ( push ( s , " H e l l o " ) )6 CU_ASSERT_EQUAL ( s i z e ( s ) , 1 ) ;7 i f ( push ( s , " I " ) && push ( s , "am" ) && push ( s , "God" ) )8 CU_ASSERT_EQUAL ( s i z e ( s ) , 4 ) ;9
10 top ( s ) ;11 CU_ASSERT_EQUAL ( s i z e ( s ) , 4 ) ;1213 i f ( pop ( s ) != NULL)14 CU_ASSERT_EQUAL ( s i z e ( s ) , 3 ) ;15 i f ( pop ( s ) != NULL && pop ( s ) != NULL16 && pop ( s ) != NULL)17 CU_ASSERT_EQUAL ( s i z e ( s ) , 0 ) ;18 i f ( pop ( s ) == NULL)19 CU_ASSERT_EQUAL ( s i z e ( s ) , 0 ) ;20 }
25