Partager via


Model Based Testing

Présentation du Model-Based Testing et de Spec Explorer

Sergio Mera
Yiming Cao

La création d'un logiciel de grande qualité exige des efforts importants en termes de tests, ce qui est probablement l'un des éléments les plus coûteux et intensifs du processus de développement de logiciels. Des tests boîte noire fonctionnels les plus simples jusqu'aux méthodes haute densité impliquant des démonstrateurs de théorème et des propriétés de spécifications formelles, de nombreuses approches ont été mises en œuvre pour améliorer l'efficacité et la fiabilité des tests. Néanmoins, les tests n'incluent pas systématiquement le niveau de minutie nécessaire. De plus, la discipline et la méthodologie sont souvent absentes.

Depuis plus de dix ans maintenant, Microsoft applique avec succès le model-based testing (MBT) à son processus de développement interne. Le MBT s'est avéré être une technique fructueuse pour un ensemble de produits logiciels internes et externes. Son adoption s'est accrue de manière régulière au cours des années. Il a somme toute été bien reçu dans la communauté de test, et plus particulièrement si on le compare à d'autres méthodologies évoluant du côté « formel » du spectre des tests.

Spec Explorer est un outil MBT de Microsoft qui étend Visual Studio, en fournissant un environnement de développement ultra-intégré permettant de créer des modèles de comportement, ainsi qu'un outil d'analyse graphique qui permet de vérifier la validité de ces modèles et de générer des cas de test en s'appuyant sur eux. Nous pensons que cet outil a marqué un tournant qui a permis l'application du MBT comme technique efficace dans l'industrie informatique, atténuant la courbe d'apprentissage naturelle et fournissant un environnement de création de pointe.

Dans cet article, en présentant Spec Explorer via une étude de cas afin d'illustrer ses principales fonctions, nous proposons une vue d'ensemble des principaux concepts sous-jacents au MBT et à Spec Explorer. Nous voulons également que cet article serve de collecte de règles générales pratiques permettant de comprendre quand envisager le MBT comme méthodologie d'assurance qualité pour un problème de test spécifique. Évitez d'utiliser aveuglément le MBT dans tous les scénarios de test. Bien souvent, une autre technique (telle que le test traditionnel) peut s'avérer être un choix plus judicieux.

Quel est le fondement d'un modèle pouvant être testé dans Spec Explorer ?

Même si les outils MBT n'offrent pas tous les mêmes fonctionnalités et présentent parfois de légers écarts conceptuels, il existe un consensus quant à la signification de l'expression « faire du MBT ».

Les modèles sont généralement créés manuellement et incluent une configuration requise et un comportement attendu. En ce qui concerne Spec Explorer, les cas de test sont automatiquement générés à partir d'un modèle orienté état. Ils incluent à la fois des séquences de test et l'oracle de test. Les séquences de test, déduites du modèle, ont pour responsabilité d'amener le système testé (ST) à atteindre différents états. L'oracle de test suit l'évolution du ST et détermine s'il est conforme au comportement spécifié par le modèle, en émettant un verdict.

Le modèle est un des principaux éléments d'un projet Spec Explorer. Il est spécifié dans une construction appelée programmes de modèles. Vous pouvez utiliser n'importe quel langage .NET (tel que C#) pour l'écriture des programmes de modèles. Ceux-ci se composent d'un ensemble de règles qui interagissent avec un état défini. Les programmes de modèles sont associés à un langage de script appelé Cord, le deuxième élément clé d'un projet Spec Explorer. Cela permet de préciser des descriptions comportementales qui configurent la manière dont le modèle est exploré et testé. L'association du programme de modèle et du script Cord crée un modèle testable pour le ST.

Bien entendu, le troisième élément important dans le projet Spec Explorer est le ST. Il n'est pas obligatoire de le fournir à Spec Explorer pour générer le code de test (qui est le mode par défaut de Spec Explorer). En effet, le code généré est directement déduit du modèle testable, sans aucune interaction avec le ST. Vous pouvez exécuter des cas de tests « hors ligne », découplés de l'évaluation du modèle et de stades de génération de cas de test. Néanmoins, si le ST est fourni, Spec Explorer peut valider que les liaisons entre le modèle et l'implémentation sont bien définies.

Étude de cas : un système de conversation

Étudions un exemple permettant d'illustrer de quelle manière vous pouvez développer un modèle testable dans Spec Explorer. Le ST dans ce cas va être un système de conversation simple avec une salle de conversation unique où les utilisateurs peuvent ouvrir et fermer une session. Lorsqu'un utilisateur est connecté, il peut demander la liste des utilisateurs connectés et envoyer des messages de diffusion à tous les utilisateurs. Le serveur de conversation reconnaît toujours les demandes. Les demandes et les réponses ont un comportement asynchrone, ce qui signifie qu'elles peuvent être mélangées. Comme on peut s'y attendre avec un système de conversation, plusieurs messages envoyés par un utilisateur sont reçus dans l'ordre.

Un des avantages liés à l'utilisation du MBT repose sur le fait qu'en appliquant le besoin de formalisation du modèle comportemental, vous pouvez recevoir de nombreux commentaires concernant les spécifications. L'ambiguïté, les contradictions et le manque de contexte peuvent se manifester dès les premiers stades. Il est donc important d'être précis et de formaliser la configuration requise, comme suit :

R1. Users must receive a response for a logon request.
R2. Users must receive a response for a logoff request.
R3. Users must receive a response for a list request.
R4. List response must contain the list of logged-on users.
R5. All logged-on users must receive a broadcast message.
R6. Messages from one sender must be received in order.

Les projets Spec Explorer utilisent des actions permettant de décrire l'interaction avec le ST à partir du point de vue test-système. Ces actions peuvent être des actions d'appel, représentant un stimulus du système de test au ST ; des actions de renvoi, capturant la réponse du ST (le cas échéant) et des actions d'événement, représentant des messages autonomes envoyés depuis le ST. Les actions d'appel/de renvoi sont des opérations de blocage. Elles sont par conséquent représentées par une méthode unique dans le ST. Il s'agit des déclarations d'action par défaut, alors que le mot clé « événement » est utilisé pour déclarer une action d'événement. La figure 1 illustre l'apparence que cela prend dans le système de conversation.

Figure 1 Déclarations d'action

// Cord code
config ChatConfig
{
  action void LogonRequest(int user);
  action event void LogonResponse(int user);
  action void LogoffRequest(int user);
  action event void LogoffResponse(int user);
  action void ListRequest(int user);
  action event void ListResponse(int user, Set<int> userList);
  action void BroadcastRequest(int senderUser, string message);
  action void BroadcastAck(int receiverUser, 
    int senderUser, string message);
  // ...
}

Avec les actions déclarées, l’étape suivante consiste à définir le comportement du système. Pour cet exemple, le modèle est décrit en utilisant C#. La modélisation de l'état du système s'appuie sur des champs de classe et celle des transitions d'état, sur des méthodes de règles. Les méthodes de règles déterminent les étapes que vous pouvez entreprendre à partir de l'état actuel dans le programme de modèle, et de quelle manière l'état est mis à jour pour chaque étape.

Du fait que ce système de conversation se compose essentiellement de l'interaction entre les utilisateurs et le système, l'état du modèle n'est qu'un regroupement d'utilisateurs avec leurs états (voir figure 2).

Figure 2 L'état du modèle

/// <summary>
/// A model of the MS-CHAT sample.
/// </summary>
public static class Model
{
  /// <summary>
  /// State of the user.
  /// </summary>
  enum UserState
  {
    WaitingForLogon,
    LoggedOn,
    WaitingForList,
    WatingForLogoff,
  }
  /// <summary>
  /// A class representing a user
  /// </summary>
  partial class User
  {
    /// <summary>
    /// The state in which the user currently is.
    /// </summary>
    internal UserState state;
    /// <summary>
    /// The broadcast messages that are waiting for delivery to this user.
    /// This is a map indexed by the user who broadcasted the message,
    /// mapping into a sequence of broadcast messages from this same user.
    /// </summary>
    internal MapContainer<int, Sequence<string>> waitingForDelivery =
      new MapContainer<int,Sequence<string>>();
  }             
    /// <summary>
    /// A mapping from logged-on users to their associated data.
    /// </summary>
    static MapContainer<int, User> users = new MapContainer<int,User>();
      // ...   
  }

Comme vous pouvez le voir, la définition d'un état de modèle ne diffère pas beaucoup de celle d'une classe C# normale. Les méthodes de règles sont des méthodes C# permettant de décrire dans quel état une action peut être activée. Lorsque cela se produit, elles décrivent également le type de mise à jour qui est appliqué à l'état du modèle. Ici, une « LogonRequest » sert d'exemple pour illustrer comment écrire une méthode de règle :

[Rule]
static void LogonRequest(int userId)
{
  Condition.IsTrue(!users.ContainsKey(userId));
  User user = new User();
  user.state = UserState.WaitingForLogon;
  user.waitingForDelivery = new MapContainer<int, Sequence<string>>();
  users[userId] = user;
}

Cette méthode décrit la condition d'activation et la règle de mise à jour pour l'action « LogonRequest », qui a été précédemment déclarée dans le code Cord. Cette règle stipule essentiellement les points suivants :

  • L'action LogonRequest peut être effectuée lorsque l'userId d'entrée n'existe pas encore dans l'ensemble d'utilisateurs actuel. « Condition.Is­True » est une API fournie par Spec Explorer pour la spécification d'une condition d'activation.
  • Une fois cette condition remplie, un nouvel objet utilisateur est créé avec son état correctement initialisé. Il est ensuite ajouté au regroupement d'utilisateurs global. Il s'agit de la partie « mise à jour » de la règle.

À ce stade, le travail de modélisation est pratiquement terminé. Définissons à présent quelques « machines » afin de pouvoir explorer le comportement du système et de bénéficier d'une certaine visualisation. Dans Spec Explorer, les machines sont des unités d'exploration. Une machine a un nom et un comportement associé défini dans le langage Cord. Vous pouvez également associer des machines entre elles pour former un comportement plus complexe. Examinons quelques exemples de machines pour le modèle de conversation :

machine ModelProgram() : Actions
{
  construct model program from Actions where scope = "Chat.Model"
}

La première machine que nous définissons est une machine dite de « programme de modèle ». Elle utilise la directive de « programme de modèle de construction » pour indiquer à Spec Explorer d'explorer l'ensemble du comportement du modèle basé sur des méthodes de règles qui se trouvent dans l'espace de noms Chat.Model :

machine BroadcastOrderedScenario() : Actions
{
  (LogonRequest({1..2}); LogonResponse){2};
  BroadcastRequest(1, "1a");
  BroadcastRequest(1, "1b");
  (BroadcastAck)*
}

La deuxième machine est un « scénario », un modèle d'actions défini d'une manière de type expression régulière. Afin de segmenter le comportement intégral, les scénarios se composent généralement d'une machine « programme de modèle », comme dans l'exemple suivant :

machine BroadcastOrderedSlice() : Actions
{
  BroadcastOrderedScenario || ModelProgram
}

L'opérateur « || » crée une « composition parallèle synchronisée » entre les deux machines participantes. Il en résulte un comportement qui contient uniquement les étapes pouvant être synchronisées sur les deux machines (par « synchronisées» nous voulons dire qui ont la même action avec la même liste d'arguments). L'exploration de cette machine génère le graphique illustré à la figure 3.

Composing Two Machines
Figure 3 Composition de deux machines

Comme vous pouvez le voir sur le graphique de la figure 3, le comportement composé est conforme à la fois à la machine du scénario et au programme de modèle. C'est une technique puissante pour l'obtention d'un sous-ensemble plus simple d'un comportement complexe. De même, lorsque votre système est doté d'un espace d'état infini (comme dans le cas du système de conversation), la segmentation du comportement intégral peut générer un sous-ensemble fini mieux adapté à des fins de test.

Analysons les différentes entités dans ce graphique. Les états dans un cercle sont des états contrôlables. Il s'agit d'états impliquant des stimuli fournis sur le ST. Les états dans un losange sont des états observables. Il s'agit d'états impliquant qu'un ou plusieurs événements sont attendus du ST. L'oracle de test (le résultat attendu du test) est déjà codé dans le graphique avec les étapes d'événements et leurs arguments. Du fait que l'événement du ST fourni au moment de l'exécution n'est pas déterminé au moment de la modélisation, les états avec plusieurs étapes d'événements sortants sont appelés des états non déterministes. Vous remarquerez que le graphique d'exploration de la figure 3 contient plusieurs états non déterministes : S19, S20, S22, etc.

Le graphique exploré est utile à la compréhension du système, mais il ne convient pas encore pour le test car il n'est pas dans un formulaire « de test normal ». Nous disons qu'un comportement est dans un formulaire de test normal s'il ne contient aucun état comptant plus d'une étape appel sortant-renvoi. Dans le graphique de la figure 3, vous pouvez voir que S0 enfreint visiblement cette règle. Pour convertir un tel comportement en un formulaire de test normal, vous pouvez simplement créer une machine en utilisant la construction de cas de test :

machine TestSuite() : Actions where TestEnabled = true
{
  construct test cases where AllowUndeterminedCoverage = true
  for BroadcastOrderedSlice
}

Cette construction génère un comportement nouveau en traversant le comportement d'origine et en générant un suivi dans un formulaire de test normal. Le critère de traversée est en couverture latérale. Chaque étape du comportement d'origine est couvert au moins à une reprise. Le graphique de la figure 4 illustre le comportement après une telle traversée.

Generating New Behavior
Figure 4 Génération d'un comportement nouveau

Pour obtenir un formulaire de test normal, les états avec plusieurs étapes appel-renvoi sont divisés à raison d'une par étape. Du fait que les événements sont les choix que le ST peut faire au moment de l'exécution, les étapes d'événement ne sont jamais divisées et sont toujours conservées intégralement. Les cas de test doivent être prêts à faire face à tout choix possible.

Spec Explorer peut générer un code de suite de tests à partir d'un comportement de formulaire de test normal. Le formulaire par défaut de code de test généré est un test unitaire Visual Studio. Vous pouvez exécuter directement une telle suite de tests avec les outils de test de Visual Studio, ou avec l'outil de ligne de commande mstest.exe. Le code de test généré est contrôlable de visu et peut être facilement débogué :

#region Test Starting in S0
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
public void TestSuiteS0() {
  this.Manager.BeginTest("TestSuiteS0");
  this.Manager.Comment("reaching state \'S0\'");
  this.Manager.Comment("executing step \'call LogonRequest(2)\'");
  Chat.Adapter.ChatAdapter.LogonRequest(2);
  this.Manager.Comment("reaching state \'S1\'");
  this.Manager.Comment("checking step \'return LogonRequest\'");
  this.Manager.Comment("reaching state \'S4\'");
  // ...
    }

Le générateur de code de test est ultra-personnalisable et peut être configuré pour générer des cas de test qui ciblent différentes structures de test, telles que NUnit.

Le modèle de conversation complet est inclus dans l'installateur Spec Explorer.

À quel moment l'utilisation du MBT est-elle récompensée ?

L'utilisation du model-based testing comporte ses avantages et ses inconvénients. L'avantage le plus évident est qu'après la réalisation du modèle testable, vous pouvez générer des cas de test en actionnant un bouton. De plus, le fait qu'un modèle doive être formalisé à l'avance permet une détection précoce des incohérences au niveau des spécifications et aide les équipes à trouver un accord en termes de comportement attendu. Veuillez noter que lors de l'écriture de cas de test manuels, le « modèle » est également là, mais il n'est pas formalisé et vit dans la tête du testeur. MBT force l'équipe de test à communiquer clairement ses attentes en termes de comportement du système et à les consigner par écrit à l'aide d'une structure bien définie.

Un autre avantage clair repose sur le fait que le projet requiert moins de maintenance. Les modifications apportées au comportement du système ou des fonctions nouvellement ajoutées peuvent être reflétées en mettant le modèle à jour, ce qui est généralement bien plus simple que de modifier des cas de test manuels, un à un. L'identification uniquement des cas de test ayant besoin d'être modifiés prend parfois beaucoup de temps. Prenez également en considération le fait que la création de modèle est indépendante de l'implémentation ou du test réel. Cela signifie que différents membres d'une équipe peuvent travailler simultanément sur différentes tâches.

En contrepartie, il est souvent nécessaire d'ajuster son comportement. C'est probablement là un des principaux défis liés à cette technique. En plus du problème bien connu selon lequel les individus travaillant dans le secteur de l'informatique n'ont pas le temps d'essayer de nouveaux outils, la courbe d'apprentissage pour l'utilisation de cette technique n'est pas négligeable. Selon l'équipe, l'application du MBT peut aussi nécessiter quelques modifications de processus, ce qui peut également entraîner quelques réticences.

L'autre inconvénient repose sur le fait que vous devez effectuer un travail préparatoire plus conséquent. Ainsi, la génération du premier cas de test est plus longue à venir qu'avec des cas de test traditionnels, écrits manuellement. De plus, la complexité du projet de test doit être suffisamment développée pour justifier l'investissement.

Heureusement, certaines règles générales aident selon nous à identifier à partir de quel moment l'utilisation de MBT est véritablement récompensée. Disposer d'un ensemble infini d'états de système avec des spécifications que vous pouvez couvrir de différentes manières est un premier signe. Un système réactif ou distribué, ou bien un système avec des interactions asynchrones ou non déterministes en est un autre. De même, des méthodes comptant de nombreux paramètres complexes peuvent conduire à envisager le MBT.

Lorsque ces conditions sont remplies, le MBT peut faire une réelle différence et réduire considérablement l'effort de test. Microsoft Blueline, un projet au sein duquel des centaines de protocoles ont été vérifiés dans le cadre de l'initiative de conformité du protocole Windows, en est un exemple. Dans ce projet, nous avons utilisé Spec Explorer pour vérifier la précision technique de la documentation du protocole concernant le comportement réel du protocole. L'effort entrepris était considérable et Microsoft a consacré environ 250 personnes-années aux tests. Microsoft Research a validé une étude statistique qui a démontré que l'utilisation du MBT a permis à Microsoft de réaliser une économie de 50 personnes-années de travail de testeur, soit environ 40 % de l'effort en comparaison d'une approche de test traditionnelle.  

Le model-based testing est une technique puissante qui ajoute une méthodologie systématique à des techniques traditionnelles. Spec Explorer est un outil abouti qui tire profit des concepts MBT dans un environnement de pointe hautement intégré en tant que Power Tool Visual Studio gratuit.

Yiming Cao est responsable de développement senior pour l'équipe outils et Microsoft Interop. Il travaille sur l'infrastructure d'ingénierie des protocoles (dont Microsoft Message Analyzer) et Spec Explorer. Avant de rejoindre Microsoft, il a travaillé pour IBM Corp. sur sa suite de collaboration pour les entreprises, puis il a rejoint une startup travaillant dans le domaine des technologies de diffusion de contenu multimédia.

Sergio Mera est gestionnaire de programmes senior pour l'équipe outils et Microsoft Interop. Il travaille sur l'infrastructure d'ingénierie des protocoles (dont Microsoft Message Analyzer) et Spec Explorer. Avant de rejoindre Microsoft, il était chercheur et conférencier pour le département informatique de l'université de Buenos Aires et il a travaillé sur les logiques modales et la démonstration automatique de théorèmes.

Merci à l'expert technique suivant d'avoir relu cet article : Nico Kicillof (Microsoft)
Nico Kicillof (nicok@microsoft.com) est gestionnaire de programmes en chef pour les outils de développement et l'architecture de build Windows Phone. Son travail consiste à créer des outils et des méthodes qui permettent aux ingénieurs de générer, tester et mettre à jour des produits logiciels. Avant de rejoindre Microsoft, il était professeur et vice-président du département informatique de l'université de Buenos Aires.