TBeXtreme de LDRA TestBed®
Le contexte :
Les applications critiques nécessitant une sécurité élevée, telles que les contrôleurs de vol, les systèmes de freinage des voitures ou les dispositifs médicaux, exigent des techniques de qualité logicielle, d'intégration et de test à la pointe de la technologie. Toutefois, les meilleures pratiques en matière de développement de logiciels embarqués ne devraient pas s’appliquer qu’aux dispositifs critiques, mais également lorsqu’on souhaite améliorer la sécurité de son logiciel ainsi qu’optimiser le temps de développement et les budgets. Les phases de test des logiciels sont des postes importants ayant un fort impact sur ces points clés. Il est donc nécessaire de bien définir sa stratégie de tests.
Qu'est-ce qu'un test de logiciel ?
Il y a différents sens dans le mot « test » suivant le contexte dans lequel on l’utilise. Pour certains, « test de logiciel » implique l’exécution du logiciel et la confirmation que son exécution est conforme – ou non - aux attentes de l’équipe de développement. Une telle définition considère l’inspection ou l’analyse du code source comme un champ différent.
Pour d’autres, le terme « test » signifie « Procédure destinée à établir la qualité, la performance ou la fiabilité de quelque chose, en particulier avant que son utilisation ne soit généralisée ». Toute activité répondant à cette définition peut donc être considérée comme un test logiciel, qu’elle implique ou non l’exécution de code.
Le terme générique « analyse statique » est utilisé pour décrire une branche du test logiciel impliquant l'analyse du code sans l'exécuter (cliquez ici pour plus d’informations sur l’analyse statique). Inversement, « analyse dynamique » décrit une branche de test logiciel dans laquelle le code est effectivement exécuté et c’est sur ce type de test que nous nous focaliserons ici.
Il existe bon nombre de méthodes ou techniques de test des logiciels, ainsi qu’une pléthore d'outils de test pour les automatiser et le choix de la stratégie de test et des outils associés dépend, du moins en partie, des objectifs de l'exercice :
Il faut avoir à l’esprit que le test demeure une activité couteuse. Par conséquent, la décision de choisir quoi, comment et jusqu’où tester devient une question de coût par rapport à l’impact de risques identifiés. Ces risques ou plutôt leurs impacts incluent non seulement le risque de défaillance logicielle, mais également des facteurs tels que le risque de retarder le lancement d'un produit commercial ou de céder l'initiative sur le marché à un concurrent. Et il faut considérer la conséquence d'un logiciel défectueux, en analysant quel serait le résultat d’une défaillance ? Pourrait-elle mettre en péril l’intégrité d’un être humain ou occasionner un simple inconfort temporaire ? Pourrait-elle donner le contrôle de l'application à des individus mal intentionnés ou rendre accessibles des informations personnelles ou sensibles ?
Qu'est-ce qu'un test de logiciel ?
Il y a différents sens dans le mot « test » suivant le contexte dans lequel on l’utilise. Pour certains, « test de logiciel » implique l’exécution du logiciel et la confirmation que son exécution est conforme – ou non - aux attentes de l’équipe de développement. Une telle définition considère l’inspection ou l’analyse du code source comme un champ différent.
Pour d’autres, le terme « test » signifie « Procédure destinée à établir la qualité, la performance ou la fiabilité de quelque chose, en particulier avant que son utilisation ne soit généralisée ». Toute activité répondant à cette définition peut donc être considérée comme un test logiciel, qu’elle implique ou non l’exécution de code.
Le terme générique « analyse statique » est utilisé pour décrire une branche du test logiciel impliquant l'analyse du code sans l'exécuter (cliquez ici pour plus d’informations sur l’analyse statique). Inversement, « analyse dynamique » décrit une branche de test logiciel dans laquelle le code est effectivement exécuté et c’est sur ce type de test que nous nous focaliserons ici.
Il existe bon nombre de méthodes ou techniques de test des logiciels, ainsi qu’une pléthore d'outils de test pour les automatiser et le choix de la stratégie de test et des outils associés dépend, du moins en partie, des objectifs de l'exercice :
- Doit-on prouver la conformité à une norme particulière ?
- Est-ce une imposition contractuelle du client ?
- Ou bien le principal moteur est-il un facteur interne, avec par exemple une fiabilité et une maintenabilité améliorées comme objectifs principaux ?
Il faut avoir à l’esprit que le test demeure une activité couteuse. Par conséquent, la décision de choisir quoi, comment et jusqu’où tester devient une question de coût par rapport à l’impact de risques identifiés. Ces risques ou plutôt leurs impacts incluent non seulement le risque de défaillance logicielle, mais également des facteurs tels que le risque de retarder le lancement d'un produit commercial ou de céder l'initiative sur le marché à un concurrent. Et il faut considérer la conséquence d'un logiciel défectueux, en analysant quel serait le résultat d’une défaillance ? Pourrait-elle mettre en péril l’intégrité d’un être humain ou occasionner un simple inconfort temporaire ? Pourrait-elle donner le contrôle de l'application à des individus mal intentionnés ou rendre accessibles des informations personnelles ou sensibles ?
Il est clair que le niveau de risque acceptable pour la santé, la sécurité et la sûreté dans chacun de ces scénarios est très différent, et l'analyse est encore compliquée s'il existe également des facteurs de risques commerciaux à ajouter à cette équation. L’analyse des risques est donc essentielle pour connaitre et mesurer les impacts d’une défaillance et donc en estimer les coûts résultants. Cela permet d’adapter son effort de test vis-à-vis des risques. Certaines normes comme l’IEC61508 définissent des degrés de sévérité (SIL -Software Integrity Level), en fonction des risques, et associe des niveaux de tests à réaliser en fonction de ces niveaux de SIL. Mais dans tous les cas, plus tôt les tests sont effectués, moins le coût pour corriger les défauts sera important.
L’Analyse Dynamique ?
Les tests dynamiques, qui réclament l’exécution du code, sont complexes à appliquer, même dans des applications relativement triviales. Les combinaisons et permutations possibles des valeurs de données et des chemins d'exécution peuvent être infinies donc virtuellement impossible à prouver en totalité. Pour éviter ou limiter cela, se fixer des règles de codage et limiter la complexité du code sont des mesures primordiales afin de retirer le meilleur retour sur investissement des tests dynamiques. En effet comparativement à l’analyse statique, les tests dynamiques permettent non seulement de valider le fonctionnel du logiciel mais permettent également de mesurer réellement la couverture structurelle. Cela permet de détecter d’éventuelles zones de code non exécutées résultant soit d’une mauvaise implémentation du code ou d’une erreur de codage et pouvant être une source de défaillance ou brèche de sécurité. La couverture structurelle qu’amène les tests dynamiques permet également de prouver que le code s’exécute avec le bon séquencement prévu, et que ce n’est pas le cumul heureux de plusieurs erreurs qui amène à un résultat correct.
Les tests dynamiques sont donc un élément essentiel si on veut prouver le bon fonctionnement et le bon comportement de son logiciel mais suivant le niveau de tests que l’on recherche à atteindre le nombre de scénarios à écrire peut être important. L’automatisation par des outils et la capacité de générer automatiquement ces scénarios est donc quasi-nécessaires.
Les tests dynamiques, qui réclament l’exécution du code, sont complexes à appliquer, même dans des applications relativement triviales. Les combinaisons et permutations possibles des valeurs de données et des chemins d'exécution peuvent être infinies donc virtuellement impossible à prouver en totalité. Pour éviter ou limiter cela, se fixer des règles de codage et limiter la complexité du code sont des mesures primordiales afin de retirer le meilleur retour sur investissement des tests dynamiques. En effet comparativement à l’analyse statique, les tests dynamiques permettent non seulement de valider le fonctionnel du logiciel mais permettent également de mesurer réellement la couverture structurelle. Cela permet de détecter d’éventuelles zones de code non exécutées résultant soit d’une mauvaise implémentation du code ou d’une erreur de codage et pouvant être une source de défaillance ou brèche de sécurité. La couverture structurelle qu’amène les tests dynamiques permet également de prouver que le code s’exécute avec le bon séquencement prévu, et que ce n’est pas le cumul heureux de plusieurs erreurs qui amène à un résultat correct.
Les tests dynamiques sont donc un élément essentiel si on veut prouver le bon fonctionnement et le bon comportement de son logiciel mais suivant le niveau de tests que l’on recherche à atteindre le nombre de scénarios à écrire peut être important. L’automatisation par des outils et la capacité de générer automatiquement ces scénarios est donc quasi-nécessaires.
Analyse Dynamique & Génération de tests Automatique
Test Unitaires, Test de Modules, Test d’Intégration
Les tests unitaires, de modules et d’intégration (dénommés « tests unitaires » ci-après) font partie des tests dynamiques dans lesquels des parties de code logiciel sont compilées afin que des données de test (ou « vecteurs ») puissent être spécifiées puis appliquées lors de l’exécution de ces morceaux de code afin de voir si les résultats sont conformes aux attentes.
Les tests unitaires, de modules et d’intégration (dénommés « tests unitaires » ci-après) font partie des tests dynamiques dans lesquels des parties de code logiciel sont compilées afin que des données de test (ou « vecteurs ») puissent être spécifiées puis appliquées lors de l’exécution de ces morceaux de code afin de voir si les résultats sont conformes aux attentes.
Ces vecteurs de tests sont définis lors de la phase de conception du logiciel. Au même titre que le code est l’implémentation d’une spécification logicielle, elle-même provenant des exigences sur le système, les vecteurs de tests sont l’implémentation d’une spécification de test, elle-même provenant des exigences de tests du système. Couplée à la couverture structurelle, cette approche « bottom-up » permet de tester non seulement le code source mais également la chaine de compilation puisque le code est réellement exécuté. L’autre avantage de cette approche est qu’il n’est pas nécessaire d’avoir l’intégralité du code pour démarrer les tests. En effet puisque les tests unitaires portent sur des portions de code et peuvent être exécutés indépendamment les uns des autres, on peut les démarrer dès l’écriture de la première fonction. Cela permet de valider au plus tôt les modules élémentaires et donc de réduire le temps et le coût des tests.
Les tests unitaires sont généralement effectués pour démontrer le respect des exigences, pour montrer que des éléments du code remplissent la fonction pour laquelle ils ont été conçus.
Les tests unitaires sont généralement effectués pour démontrer le respect des exigences, pour montrer que des éléments du code remplissent la fonction pour laquelle ils ont été conçus.
Génération Automatique des cas de Test
En règle générale, les données de sortie générées par les tests unitaires constituent donc une fin importante, mais ce n'est pas toujours le cas.
Il peut arriver que le fait que les tests unitaires se soient bien déroulés soit plus important que les données de test elles-mêmes. Pour traiter ces situations aussi efficacement que possible, les outils de test unitaire plus sophistiqués peuvent générer automatiquement des scénarios de test, sur la base des informations récupérées au moyen de l'analyse statique initiale du logiciel testé. Par exemple :
Les outils de test dynamique offrant cette capacité de génération automatique de cas de test, offrent une gamme d'options permettant de prendre en compte différents aspects du code. C’est le cas de TBeXtreme de la suite LDRA TestBed® qui permet entre autres :
En règle générale, les données de sortie générées par les tests unitaires constituent donc une fin importante, mais ce n'est pas toujours le cas.
Il peut arriver que le fait que les tests unitaires se soient bien déroulés soit plus important que les données de test elles-mêmes. Pour traiter ces situations aussi efficacement que possible, les outils de test unitaire plus sophistiqués peuvent générer automatiquement des scénarios de test, sur la base des informations récupérées au moyen de l'analyse statique initiale du logiciel testé. Par exemple :
- Une plage de données peut devoir être étendue pour couvrir des cas non fonctionnels mais obligatoire pour réussir des tests de robustesse en sécurité par exemple et pour lequel le code source est nécessaire (normalement les vecteurs de test proviennent des spécifications de test et indépendamment du code source).
- La fonctionnalité du code source peut déjà être prouvée, mais le niveau requis de couverture de code insatisfait
- Un « profil » du code source peut être requis avant la modification du code source. Des séquences de tests peuvent être générées sur la base du code non modifié, puis exercées à nouveau lorsque la source a été modifiée pour prouver qu'il n'y a pas eu d’impacts sur les fonctionnalités existantes
- La génération de cas de tests permettant d’exercer des valeurs limites supérieure et inférieure
- La génération de valeurs minimales / moyennes / maximales
- La génération du nombre optimal de tests élémentaires pour maximiser la couverture de code
La génération automatique de cas de test permet d’étendre le nombre de vecteurs de test et donc la couverture de test. Comme cette génération est basée sur l’analyse du code source, donc de la réelle implémentation qui a été faite, l’effet direct est d’améliorer la robustesse du logiciel et donc sa qualité. Mais comme ces tests sont basés sur le code source et non sur les exigences du système, ils prouvent seulement que le code fait exactement ce pour quoi il a été écrit. Ils ne prouvent pas obligatoirement que le code respecte les exigences fonctionnelles du logiciel.
Conclusion :
L’intérêt majeur de cette génération automatique de vecteurs de test est qu’elle étend considérablement la plage de test, au-delà de ce qu’exige généralement la validation du fonctionnel. C’est une des différences entre la sûreté de fonctionnement et la sécurité. La sûreté fonctionnelle demande à prouver que le logiciel se comporte comme prévu dans tous les cas de fonctionnement spécifiés. L’environnement de fonctionnement est « fini ou maitrisé ».
En sécurité, l’environnement n’est pas maitrisé car on ne connait pas tous les types d’attaques que peut subir le système, et ce, que ce soit au moment où on le conçoit et encore moins dans le futur. Etendre la couverture des tests est donc un moyen essentiel pour améliorer la résilience du logiciel. Et comme certains vecteurs d’attaques peuvent varier en fonction de l’implémentation logicielle choisie, la génération automatique à partir de l’analyse du code source est donc nécessaire. Le corollaire est qu’en améliorant la robustesse pour la sécurité du logiciel on augmente de facto la sûreté fonctionnelle.
Mais comme il a été dit, pour être efficace, cette approche doit être combiné à une démarche qualité pour réduire la complexité du code, implémenter des règles de codage afin de limiter les potentiels d’erreur et permettre de mesurer la couverture structurelle. Les outils d’automatisation, tel que la suite LDRA TestBed®, doivent pouvoir couvrir ces différents domaines afin d’amener le meilleur ROI.
Contact Produit :
Fréderic MARAVAL – Responsable Produits Systèmes embarqués et Qualité logicielle – fmaraval@isit.fr
En sécurité, l’environnement n’est pas maitrisé car on ne connait pas tous les types d’attaques que peut subir le système, et ce, que ce soit au moment où on le conçoit et encore moins dans le futur. Etendre la couverture des tests est donc un moyen essentiel pour améliorer la résilience du logiciel. Et comme certains vecteurs d’attaques peuvent varier en fonction de l’implémentation logicielle choisie, la génération automatique à partir de l’analyse du code source est donc nécessaire. Le corollaire est qu’en améliorant la robustesse pour la sécurité du logiciel on augmente de facto la sûreté fonctionnelle.
Mais comme il a été dit, pour être efficace, cette approche doit être combiné à une démarche qualité pour réduire la complexité du code, implémenter des règles de codage afin de limiter les potentiels d’erreur et permettre de mesurer la couverture structurelle. Les outils d’automatisation, tel que la suite LDRA TestBed®, doivent pouvoir couvrir ces différents domaines afin d’amener le meilleur ROI.
Contact Produit :
Fréderic MARAVAL – Responsable Produits Systèmes embarqués et Qualité logicielle – fmaraval@isit.fr